From b19b10f3834b9e9b109f7e9d0aa9a7568aff2d71 Mon Sep 17 00:00:00 2001 From: arjunsridhar12345 Date: Fri, 17 Jul 2026 15:22:01 -0700 Subject: [PATCH 1/2] fix: filter by event message type for licks --- .../nwb/acquisition/acquisition_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dynamic_foraging_processing/nwb/acquisition/acquisition_builder.py b/src/dynamic_foraging_processing/nwb/acquisition/acquisition_builder.py index 047bd48..a63fda2 100644 --- a/src/dynamic_foraging_processing/nwb/acquisition/acquisition_builder.py +++ b/src/dynamic_foraging_processing/nwb/acquisition/acquisition_builder.py @@ -106,8 +106,7 @@ def get_lick_times(self, device: str, stream_name: str, port: str) -> np.ndarray ``HarpBehavior``/``DigitalInputState``, with left licks on ``DIPort0`` and right licks on ``DIPort1``. The lickometer board exposes each side as its own device (``HarpLickometerLeft`` / ``HarpLickometerRight``) - with a ``LickState`` stream and a ``Channel0`` column. A lick time is a - timestamp at which the selected column's digital input is high. + with a ``LickState`` stream and a ``Channel0`` column. Parameters ---------- @@ -131,6 +130,7 @@ def get_lick_times(self, device: str, stream_name: str, port: str) -> np.ndarray data = self.loader.dataset.at("Behavior").at(device).at(stream_name).load().data except (KeyError, FileNotFoundError): return np.array([]) + data = data[data["MessageType"] == "EVENT"] licks = data[data[port].fillna(False).astype(bool)] return licks.index.to_numpy() From 97fad88bdbe95e0135f274f9b9f0867405185e2f Mon Sep 17 00:00:00 2001 From: arjunsridhar12345 Date: Fri, 17 Jul 2026 15:22:11 -0700 Subject: [PATCH 2/2] test: update tests --- .../test_acquisition_builder.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/test_nwb/test_acquisition/test_acquisition_builder.py b/tests/test_nwb/test_acquisition/test_acquisition_builder.py index 3ee8c05..4b1f91f 100644 --- a/tests/test_nwb/test_acquisition/test_acquisition_builder.py +++ b/tests/test_nwb/test_acquisition/test_acquisition_builder.py @@ -88,13 +88,18 @@ def _empty_manual_water_frame() -> pd.DataFrame: def _make_digital_input_frame() -> pd.DataFrame: - """Build a DigitalInputState frame: left licks high at 1.0, right at 2.0/2.5.""" + """Build a DigitalInputState frame: left licks high at 1.0, right at 2.0/2.5. + + Includes a non-EVENT ``READ`` row at 0.5 with ``DIPort0`` latched high; it + reports register state rather than a lick and must be filtered out. + """ return pd.DataFrame( { - "DIPort0": [True, False, False], - "DIPort1": [False, True, True], + "MessageType": ["READ", "EVENT", "EVENT", "EVENT"], + "DIPort0": [True, True, False, False], + "DIPort1": [False, False, True, True], }, - index=pd.Index([1.0, 2.0, 2.5], name="time"), + index=pd.Index([0.5, 1.0, 2.0, 2.5], name="time"), ) @@ -196,6 +201,17 @@ def test_get_lick_times_selects_di_port_by_side(): ) +def test_get_lick_times_ignores_non_event_rows(): + """Only EVENT rows are licks; latched-high READ/WRITE rows are ignored.""" + builder = AcquisitionBuilder(loader=_make_loader()) + + # DIPort0 is high in the READ row at 0.5 and the EVENT row at 1.0; only the + # EVENT-row timestamp is returned. + np.testing.assert_array_equal( + builder.get_lick_times("HarpBehavior", "DigitalInputState", "DIPort0"), np.array([1.0]) + ) + + def test_get_lick_times_returns_empty_when_absent(): """A missing DigitalInputState stream yields an empty array.""" dataset = _FakeNode(