From 01e7e1916d966b759c6472fc01733c45016cf228 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 20:53:53 +0000 Subject: [PATCH 1/3] Fix duplicate epochs in probe epoch table When multiple DAQ systems report the same epochs (same epoch_id and epoch_session_id), buildepochtable and buildmultipleepochtables would include duplicate entries. Deduplicate by tracking seen (epoch_id, epoch_session_id) pairs and skipping already-seen epochs. https://claude.ai/code/session_016R2mDR7X4ySBpod8U54i1z --- src/ndi/probe/__init__.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/ndi/probe/__init__.py b/src/ndi/probe/__init__.py index 4462c57..60e7b80 100644 --- a/src/ndi/probe/__init__.py +++ b/src/ndi/probe/__init__.py @@ -104,6 +104,7 @@ def buildepochtable(self) -> list[dict[str, Any]]: et = [] epoch_number = 0 + seen_epoch_ids: set[str] = set() for daqsys in daqsystems: # Get device epoch table @@ -115,6 +116,13 @@ def buildepochtable(self) -> list[dict[str, Any]]: matching_epm = self._find_matching_epochprobemap(epochprobemaps) if matching_epm is not None: + epoch_id = device_entry.get("epoch_id", "") + epoch_session_id = device_entry.get("epoch_session_id", "") + dedup_key = (epoch_id, epoch_session_id) + if dedup_key in seen_epoch_ids: + continue + seen_epoch_ids.add(dedup_key) + epoch_number += 1 # Get clock and timing info from device entry @@ -124,15 +132,15 @@ def buildepochtable(self) -> list[dict[str, Any]]: et.append( { "epoch_number": epoch_number, - "epoch_id": device_entry.get("epoch_id", ""), - "epoch_session_id": device_entry.get("epoch_session_id", ""), + "epoch_id": epoch_id, + "epoch_session_id": epoch_session_id, "epochprobemap": [matching_epm], "epoch_clock": epoch_clock, "t0_t1": t0_t1, "underlying_epochs": { "underlying": daqsys, - "epoch_id": device_entry.get("epoch_id", ""), - "epoch_session_id": device_entry.get("epoch_session_id", ""), + "epoch_id": epoch_id, + "epoch_session_id": epoch_session_id, "epochprobemap": epochprobemaps, "epoch_clock": epoch_clock, "t0_t1": t0_t1, @@ -377,6 +385,7 @@ def buildmultipleepochtables( for probe in probes: et = [] epoch_number = 0 + seen_epoch_ids: set[tuple[str, str]] = set() for _daqsys_id, device_info in device_tables.items(): daqsys = device_info["system"] @@ -387,19 +396,26 @@ def buildmultipleepochtables( matching_epm = probe._find_matching_epochprobemap(epochprobemaps) if matching_epm is not None: + epoch_id = device_entry.get("epoch_id", "") + epoch_session_id = device_entry.get("epoch_session_id", "") + dedup_key = (epoch_id, epoch_session_id) + if dedup_key in seen_epoch_ids: + continue + seen_epoch_ids.add(dedup_key) + epoch_number += 1 et.append( { "epoch_number": epoch_number, - "epoch_id": device_entry.get("epoch_id", ""), - "epoch_session_id": device_entry.get("epoch_session_id", ""), + "epoch_id": epoch_id, + "epoch_session_id": epoch_session_id, "epochprobemap": [matching_epm], "epoch_clock": device_entry.get("epoch_clock", []), "t0_t1": device_entry.get("t0_t1", []), "underlying_epochs": { "underlying": daqsys, - "epoch_id": device_entry.get("epoch_id", ""), - "epoch_session_id": device_entry.get("epoch_session_id", ""), + "epoch_id": epoch_id, + "epoch_session_id": epoch_session_id, "epochprobemap": epochprobemaps, "epoch_clock": device_entry.get("epoch_clock", []), "t0_t1": device_entry.get("t0_t1", []), From 2543ac903d73e3c4aa8f52c816e44590773e3ccd Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 20:58:17 +0000 Subject: [PATCH 2/3] Fix epoch duplicates by matching device name to DAQ system Instead of blindly deduplicating by epoch_id (which could match the wrong DAQ system), check that the matching epochprobemap's device name matches the current DAQ system's name. This mirrors the MATLAB behavior where only epochs from the correct device are included. https://claude.ai/code/session_016R2mDR7X4ySBpod8U54i1z --- src/ndi/probe/__init__.py | 50 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/ndi/probe/__init__.py b/src/ndi/probe/__init__.py index 60e7b80..e7a9620 100644 --- a/src/ndi/probe/__init__.py +++ b/src/ndi/probe/__init__.py @@ -104,11 +104,11 @@ def buildepochtable(self) -> list[dict[str, Any]]: et = [] epoch_number = 0 - seen_epoch_ids: set[str] = set() for daqsys in daqsystems: # Get device epoch table device_et = daqsys.epochtable() + daqsys_name = getattr(daqsys, "name", getattr(daqsys, "_name", "")) for device_entry in device_et: # Check if any epochprobemap matches this probe @@ -116,12 +116,17 @@ def buildepochtable(self) -> list[dict[str, Any]]: matching_epm = self._find_matching_epochprobemap(epochprobemaps) if matching_epm is not None: - epoch_id = device_entry.get("epoch_id", "") - epoch_session_id = device_entry.get("epoch_session_id", "") - dedup_key = (epoch_id, epoch_session_id) - if dedup_key in seen_epoch_ids: + # Only include if the matching epochprobemap's device + # belongs to this DAQ system + epm_devicename = "" + if hasattr(matching_epm, "devicename"): + epm_devicename = matching_epm.devicename + elif hasattr(matching_epm, "devicestring"): + parts = matching_epm.devicestring.split(":") + epm_devicename = parts[0] if parts else "" + + if epm_devicename.lower() != daqsys_name.lower(): continue - seen_epoch_ids.add(dedup_key) epoch_number += 1 @@ -132,15 +137,15 @@ def buildepochtable(self) -> list[dict[str, Any]]: et.append( { "epoch_number": epoch_number, - "epoch_id": epoch_id, - "epoch_session_id": epoch_session_id, + "epoch_id": device_entry.get("epoch_id", ""), + "epoch_session_id": device_entry.get("epoch_session_id", ""), "epochprobemap": [matching_epm], "epoch_clock": epoch_clock, "t0_t1": t0_t1, "underlying_epochs": { "underlying": daqsys, - "epoch_id": epoch_id, - "epoch_session_id": epoch_session_id, + "epoch_id": device_entry.get("epoch_id", ""), + "epoch_session_id": device_entry.get("epoch_session_id", ""), "epochprobemap": epochprobemaps, "epoch_clock": epoch_clock, "t0_t1": t0_t1, @@ -385,37 +390,42 @@ def buildmultipleepochtables( for probe in probes: et = [] epoch_number = 0 - seen_epoch_ids: set[tuple[str, str]] = set() for _daqsys_id, device_info in device_tables.items(): daqsys = device_info["system"] device_et = device_info["epochtable"] + daqsys_name = getattr(daqsys, "name", getattr(daqsys, "_name", "")) for device_entry in device_et: epochprobemaps = device_entry.get("epochprobemap", []) matching_epm = probe._find_matching_epochprobemap(epochprobemaps) if matching_epm is not None: - epoch_id = device_entry.get("epoch_id", "") - epoch_session_id = device_entry.get("epoch_session_id", "") - dedup_key = (epoch_id, epoch_session_id) - if dedup_key in seen_epoch_ids: + # Only include if the matching epochprobemap's device + # belongs to this DAQ system + epm_devicename = "" + if hasattr(matching_epm, "devicename"): + epm_devicename = matching_epm.devicename + elif hasattr(matching_epm, "devicestring"): + parts = matching_epm.devicestring.split(":") + epm_devicename = parts[0] if parts else "" + + if epm_devicename.lower() != daqsys_name.lower(): continue - seen_epoch_ids.add(dedup_key) epoch_number += 1 et.append( { "epoch_number": epoch_number, - "epoch_id": epoch_id, - "epoch_session_id": epoch_session_id, + "epoch_id": device_entry.get("epoch_id", ""), + "epoch_session_id": device_entry.get("epoch_session_id", ""), "epochprobemap": [matching_epm], "epoch_clock": device_entry.get("epoch_clock", []), "t0_t1": device_entry.get("t0_t1", []), "underlying_epochs": { "underlying": daqsys, - "epoch_id": epoch_id, - "epoch_session_id": epoch_session_id, + "epoch_id": device_entry.get("epoch_id", ""), + "epoch_session_id": device_entry.get("epoch_session_id", ""), "epochprobemap": epochprobemaps, "epoch_clock": device_entry.get("epoch_clock", []), "t0_t1": device_entry.get("t0_t1", []), From f4a97db0b859b93adc36fb666ab019155194c080 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 22 Mar 2026 21:05:53 +0000 Subject: [PATCH 3/3] Fix test mock to set DAQ system name for device matching The test mock_daqsys needs name and _name attributes set to match the devicestring in the epochprobemap, so the device name check works correctly. https://claude.ai/code/session_016R2mDR7X4ySBpod8U54i1z --- tests/test_element.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_element.py b/tests/test_element.py index 88b605f..f34b510 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -559,6 +559,8 @@ def test_buildepochtable_with_matching_epoch(self): mock_session = MagicMock() mock_daqsys = MagicMock() + mock_daqsys.name = "dev1" + mock_daqsys._name = "dev1" mock_daqsys.epochtable.return_value = [ { "epoch_number": 1,