Skip to content
Draft
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
23 changes: 12 additions & 11 deletions src/dynamic_foraging_processing/pipeline/_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@
#: ``TimeIntervals`` requires both, so they are derived here.
_NWB_START_COLUMN = "quiescent_start_time"

#: Columns NWB's required native ``stop_time`` is taken from, in order of
#: preference: the end of the ITI, falling back to its start on the last trial
#: of the session (where the ITI end is unknown).
_NWB_STOP_COLUMNS = ("ITI_stop_time", "ITI_start_time")
#: Trials-table column NWB's required native ``stop_time`` is taken from: the
#: end of the ITI. This is ``NaN`` on the last trial of the session (whose ITI
#: end is unknown) and the ``NaN`` is propagated rather than substituted, so an
#: unknown trial end reads as unknown instead of as a shortened trial.
_NWB_STOP_COLUMN = "ITI_stop_time"

#: Source repository recorded in the ``processing.json`` data process.
_CODE_URL = "https://github.com/AllenNeuralDynamics/dynamic-foraging-processing"
Expand Down Expand Up @@ -236,8 +237,9 @@ def _trial_extent(row: pd.Series) -> t.Tuple[float, float]:

The trials table has no trial start/stop columns of its own, so the
trial's extent is taken from its period bounds: it starts with the
quiescent period and ends with the ITI, falling back to the ITI start on
the last trial of the session (whose ITI end is unknown).
quiescent period and ends with the ITI. The last trial of the session has
no ITI end, so its stop time is ``NaN`` — an unknown end is reported as
unknown rather than substituted with an earlier landmark.

Parameters
----------
Expand All @@ -247,11 +249,10 @@ def _trial_extent(row: pd.Series) -> t.Tuple[float, float]:
Returns
-------
tuple of (float, float)
The trial start and stop time (seconds).
The trial start and stop time (seconds); the stop time is ``NaN``
where the ITI end is unknown.
"""
stops = [row[column] for column in _NWB_STOP_COLUMNS if pd.notnull(row[column])]
stop = stops[0] if stops else np.nan
return float(row[_NWB_START_COLUMN]), float(stop)
return float(row[_NWB_START_COLUMN]), float(row[_NWB_STOP_COLUMN])

@classmethod
def _add_trials(cls, nwb_file: pynwb.NWBFile, trials: pd.DataFrame) -> None:
Expand All @@ -265,7 +266,7 @@ def _add_trials(cls, nwb_file: pynwb.NWBFile, trials: pd.DataFrame) -> None:
(named ``id``) is replicated as each trial's NWB ``id``. An empty table
(or one missing the period columns the extent is derived from) is skipped.
"""
required = (_NWB_START_COLUMN, *_NWB_STOP_COLUMNS)
required = (_NWB_START_COLUMN, _NWB_STOP_COLUMN)
if trials.empty or any(col not in trials.columns for col in required):
return
descriptions = TrialConfig.column_descriptions()
Expand Down
5 changes: 3 additions & 2 deletions tests/test_pipeline/test_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ def test_add_trials_derives_native_start_and_stop_from_periods():
"""NWB's native trial extent spans the quiescent start to the ITI end.

The last trial has no ITI end (no following quiescent period), so its stop
time falls back to the ITI start.
time stays ``NaN`` rather than falling back to an earlier landmark.
"""
nwb_file = MagicMock()

Expand All @@ -307,7 +307,8 @@ def test_add_trials_derives_native_start_and_stop_from_periods():
(call.kwargs["start_time"], call.kwargs["stop_time"])
for call in nwb_file.add_trial.call_args_list
]
assert extents == [(0.0, 1.0), (1.0, 1.4)]
assert extents[0] == (0.0, 1.0)
assert extents[1][0] == 1.0 and np.isnan(extents[1][1])


def test_add_trials_skips_frame_without_period_columns():
Expand Down