Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### New Checks

* Added `check_spatial_series_unit` to validate that SpatialSeries objects (outside of CompassDirection) have a recognized unit string from a curated allowlist of SI length units, angular units, and pixels. [#685](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/685)
* Added `check_imaging_plane_location_allen_ccf`, `check_electrodes_location_allen_ccf`, and `check_intracellular_electrode_location_allen_ccf` to validate location fields against Allen Mouse Brain CCF ontology terms when subject species is mouse. [#671](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/671)
* Added `check_time_intervals_start_time_not_constant` to flag TimeIntervals tables where all start_time values are identical, indicating times were likely not set relative to session start. [#677](https://github.com/NeurodataWithoutBorders/nwbinspector/issues/677)
* Added `check_units_resolution_is_set` to flag when the Units table has spike_times but resolution is not set to a meaningful positive float. [#686](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/686)
Expand Down
13 changes: 13 additions & 0 deletions docs/best_practices/behavior.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ When a :ref:`nwb-schema:sec-SpatialSeries` is in a :ref:`nwb-schema:sec-CompassD
Check function: :py:meth:`~nwbinspector.checks._behavior.check_compass_direction_unit`


.. _best_practice_spatial_series_general_units:

SpatialSeries General Units
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A :ref:`nwb-schema:sec-SpatialSeries` that is not inside a :ref:`nwb-schema:sec-CompassDirection` should have a unit from
the following recognized set: "meters", "centimeters", "millimeters", "micrometers", "degrees", "radians", "pixels",
or "n.a." (not available) as a placeholder when the unit is not known.
These follow the PyNWB convention of full SI words in plural form.

Check function: :py:meth:`~nwbinspector.checks._behavior.check_spatial_series_unit`


.. _best_practice_spatial_series_values:

SpatialSeries Data Values
Expand Down
2 changes: 2 additions & 0 deletions src/nwbinspector/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
check_spatial_series_degrees_magnitude,
check_spatial_series_dims,
check_spatial_series_radians_magnitude,
check_spatial_series_unit,
)
from ._ecephys import (
check_ascending_spike_times,
Expand Down Expand Up @@ -183,6 +184,7 @@
"check_spatial_series_radians_magnitude",
"check_spatial_series_dims",
"check_spatial_series_degrees_magnitude",
"check_spatial_series_unit",
"check_ascending_spike_times",
"check_electrical_series_unscaled_data",
"check_units_resolution_is_valid",
Expand Down
34 changes: 34 additions & 0 deletions src/nwbinspector/checks/_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,37 @@ def check_spatial_series_degrees_magnitude(
)

return None


VALID_SPATIAL_SERIES_UNITS = {
"meters",
"centimeters",
"millimeters",
"micrometers",
"degrees",
"radians",
"pixels",
"n.a.",
}


@register_check(importance=Importance.BEST_PRACTICE_VIOLATION, neurodata_type=SpatialSeries)
def check_spatial_series_unit(spatial_series: SpatialSeries) -> Optional[InspectorMessage]:
"""
Check that SpatialSeries.unit is a recognized spatial unit.

Best Practice: :ref:`best_practice_spatial_series_general_units`
"""
if spatial_series.get_ancestor("CompassDirection") is not None:
return None

if spatial_series.unit not in VALID_SPATIAL_SERIES_UNITS:
return InspectorMessage(
message=(
f"SpatialSeries unit '{spatial_series.unit}' is not a valid spatial unit. "
f"Valid units are: {', '.join(sorted(VALID_SPATIAL_SERIES_UNITS))}. "
"If the unit is not known, use 'n.a.' (not available) as a placeholder."
)
)

return None
66 changes: 66 additions & 0 deletions tests/unit_tests/test_behavior.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
check_spatial_series_degrees_magnitude,
check_spatial_series_dims,
check_spatial_series_radians_magnitude,
check_spatial_series_unit,
)


Expand Down Expand Up @@ -149,3 +150,68 @@ def test_check_spatial_series_radians_magnitude():
location="/",
object_type="SpatialSeries",
)


def test_pass_check_spatial_series_unit():
for unit in ("meters", "centimeters", "millimeters", "micrometers", "degrees", "radians", "pixels", "n.a."):
spatial_series = SpatialSeries(
name="SpatialSeries",
description="description",
data=np.ones((10,)),
rate=3.0,
reference_frame="reference_frame",
unit=unit,
)
assert check_spatial_series_unit(spatial_series) is None


def test_skip_check_spatial_series_unit_in_compass_direction():
compass_direction = CompassDirection(
spatial_series=SpatialSeries(
name="SpatialSeries",
description="description",
data=np.ones((10,)),
rate=3.0,
reference_frame="reference_frame",
unit="kilometers",
)
)
spatial_series = compass_direction.spatial_series["SpatialSeries"]
assert check_spatial_series_unit(spatial_series) is None


def test_fail_check_spatial_series_unit():
spatial_series = SpatialSeries(
name="SpatialSeries",
description="description",
data=np.ones((10,)),
rate=3.0,
reference_frame="reference_frame",
unit="kilometers",
)
result = check_spatial_series_unit(spatial_series)
assert result == InspectorMessage(
message=(
"SpatialSeries unit 'kilometers' is not a valid spatial unit. "
"Valid units are: centimeters, degrees, meters, micrometers, millimeters, n.a., pixels, radians. "
"If the unit is not known, use 'n.a.' (not available) as a placeholder."
),
importance=Importance.BEST_PRACTICE_VIOLATION,
check_function_name="check_spatial_series_unit",
object_type="SpatialSeries",
object_name="SpatialSeries",
location="/",
)


def test_fail_check_spatial_series_unit_none():
spatial_series = SpatialSeries(
name="SpatialSeries",
description="description",
data=np.ones((10,)),
rate=3.0,
reference_frame="reference_frame",
)
spatial_series.fields["unit"] = None
result = check_spatial_series_unit(spatial_series)
assert result is not None
Loading