Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------
Expand All @@ -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()

Expand Down
24 changes: 20 additions & 4 deletions tests/test_nwb/test_acquisition/test_acquisition_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
)


Expand Down Expand Up @@ -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(
Expand Down