diff --git a/CHANGELOG.md b/CHANGELOG.md index b407d934f..146bc8517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Upcoming +### New Checks + +* Added `check_ascending_spike_times` for ensuring spike times are sorted in ascending order. [#394](https://github.com/NeurodataWithoutBorders/nwbinspector/issues/394) + # v0.4.30 ### Fixes diff --git a/docs/best_practices/ecephys.rst b/docs/best_practices/ecephys.rst index fffe593af..8dfbb5ad8 100644 --- a/docs/best_practices/ecephys.rst +++ b/docs/best_practices/ecephys.rst @@ -83,3 +83,14 @@ Observation Intervals The ``obs_intervals`` field of the :ref:`nwb-schema:sec-units-src` table is used to indicate periods of time where the underlying electrical signal(s) were not observed. This can happen if the recording site moves away from the unit, or if the recording is stopped. Since the channel is not observed, it is not determinable whether a spike occurred during this time. Therefore, there should not be any identified spike times for units matched to those electrical signal(s) occurring outside of the defined ``obs_intervals``. If this variable is not set, it is assumed that all time is observed. Check function: :py:meth:`~nwbinspector.checks.ecephys.check_spike_times_not_in_unobserved_interval` + + + +.. _best_practice_ascending_spike_times: + +Ascending Spike Times +~~~~~~~~~~~~~~~~~~~~~ + +All spike times should be sorted in ascending order. If they reset to zero, this can be a sign that spikes are not properly aligned with the ``timestamps_reference_time`` of the :ref:`nwb-schema:sec-NWBFile`. + +Check function: :py:meth:`~nwbinspector.checks.ecephys.check_ascending_spike_times` diff --git a/src/nwbinspector/checks/ecephys.py b/src/nwbinspector/checks/ecephys.py index 5044bd8d1..803502a21 100644 --- a/src/nwbinspector/checks/ecephys.py +++ b/src/nwbinspector/checks/ecephys.py @@ -79,3 +79,13 @@ def check_spike_times_not_in_unobserved_interval(units_table: Units, nunits: int "observed intervals." ) ) + + +@register_check(importance=Importance.BEST_PRACTICE_VIOLATION, neurodata_type=Units) +def check_ascending_spike_times(units_table: Units): + """Check that the values in the timestamps array are strictly increasing.""" + if "spike_times" not in units_table: + return + for spike_times_per_unit in units_table["spike_times"]: + if not np.all(spike_times_per_unit[:-1] <= spike_times_per_unit[1:]): + return InspectorMessage(message=("This Units table contains spike times that are not ascending.")) diff --git a/tests/unit_tests/test_ecephys.py b/tests/unit_tests/test_ecephys.py index ccd3ab636..6ef1ddd19 100644 --- a/tests/unit_tests/test_ecephys.py +++ b/tests/unit_tests/test_ecephys.py @@ -15,6 +15,7 @@ check_electrical_series_dims, check_electrical_series_reference_electrodes_table, check_spike_times_not_in_unobserved_interval, + check_ascending_spike_times, ) @@ -215,3 +216,36 @@ def test_check_spike_times_not_in_unobserved_interval_multiple_units(): object_name="TestUnits", location="/", ) + + +def test_check_ascending_spike_times_pass(): + units_table = Units() + units_table.add_unit(spike_times=[0.0, 0.1]) + units_table.add_unit(spike_times=[1.0, 2.0]) + assert check_ascending_spike_times(units_table=units_table) is None + + +def test_check_ascending_spike_times_w_missing_spike_times_pass(): + units_table = Units() + units_table.add_unit(spike_times=[0.0, 0.1]) + units_table.add_unit(spike_times=[]) + assert check_ascending_spike_times(units_table=units_table) is None + + +def test_check_ascending_spike_times_missing_column_skip(): + units_table = Units() + assert check_ascending_spike_times(units_table=units_table) is None + + +def test_check_ascending_spike_times_fail(): + units_table = Units(name="TestUnits") + units_table.add_unit(spike_times=[0.0, 0.1]) + units_table.add_unit(spike_times=[2.0, 1.0]) + assert check_ascending_spike_times(units_table=units_table) == InspectorMessage( + message=("This Units table contains spike times that are not ascending."), + importance=Importance.BEST_PRACTICE_VIOLATION, + check_function_name="check_ascending_spike_times", + object_type="Units", + object_name="TestUnits", + location="/", + )