Skip to content
Open
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
10 changes: 10 additions & 0 deletions docs/best_practices/nwbfile_metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,16 @@ Check function: :py:meth:`~nwbinspector.checks._nwbfile_metadata.check_subject_a



.. _best_practice_subject_weight:

Subject Weight
~~~~~~~~~~~~~

The ``weight`` of a :ref:`nwb-schema:sec-Subject` should have the form '[numeric] [string]', where the numeric part is the weight value and the string part is the unit. For example, "70 kg" or "250 g". This format ensures that both the weight value and its unit are clearly specified and can be easily parsed.

Check function: :py:meth:`~nwbinspector.checks._nwbfile_metadata.check_subject_weight_format`


.. _best_practice_subject_dob:

Date of Birth
Expand Down
4 changes: 4 additions & 0 deletions src/nwbinspector/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
check_subject_sex,
check_subject_species_exists,
check_subject_species_form,
check_subject_weight_format,
)
from ._ogen import (
check_optogenetic_stimulus_site_has_optogenetic_series,
Expand All @@ -75,6 +76,7 @@
from ._time_series import (
check_data_orientation,
check_missing_unit,
check_rate_is_not_negative,
check_rate_is_not_zero,
check_regular_timestamps,
check_resolution,
Expand Down Expand Up @@ -146,4 +148,6 @@
"check_spatial_series_radians_magnitude",
"check_spatial_series_dims",
"check_spatial_series_degrees_magnitude",
"check_rate_is_not_negative",
"check_subject_weight_format",
]
13 changes: 13 additions & 0 deletions src/nwbinspector/checks/_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,19 @@ def check_subject_species_form(subject: Subject) -> Optional[InspectorMessage]:
return None


@register_check(importance=Importance.CRITICAL, neurodata_type=Subject)
def check_subject_weight_format(subject: Subject) -> Optional[InspectorMessage]:
"""Check if the subject weight has the form '[numeric] [string]'."""
if subject.weight is None:
return None

weight_str = str(subject.weight)
if not re.match(r"^\d+(\.\d+)?\s?\w+$", weight_str):
return InspectorMessage(
message="Subject weight should have the form '[numeric] [string]'."
)
return None

@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=ProcessingModule)
def check_processing_module_name(processing_module: ProcessingModule) -> Optional[InspectorMessage]:
"""Check if the name of a processing module is of a valid modality."""
Expand Down
13 changes: 13 additions & 0 deletions src/nwbinspector/checks/_time_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,16 @@ def check_rate_is_not_zero(time_series: TimeSeries) -> Optional[InspectorMessage
)

return None


@register_check(importance=Importance.CRITICAL, neurodata_type=TimeSeries)
def check_rate_is_not_negative(time_series: TimeSeries) -> Optional[InspectorMessage]:
if time_series.data is None:
return None

if time_series.rate < 0.0:
return InspectorMessage(
f"{time_series.name} has a negative sampling rate value of {time_series.rate}Hz.The sampling rate should have a positive value."
)

return None
47 changes: 47 additions & 0 deletions tests/unit_tests/test_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
check_subject_sex,
check_subject_species_exists,
check_subject_species_form,
check_subject_weight_format,
)
from nwbinspector.checks._nwbfile_metadata import PROCESSING_MODULE_CONFIG
from nwbinspector.testing import make_minimal_nwbfile
Expand Down Expand Up @@ -409,6 +410,52 @@ def test_check_subject_age_iso8601_range_fail_2():
)


def test_check_subject_weight_format_pass():
subject = Subject(weight="70 kg")
assert check_subject_weight_format(subject) is None


def test_check_subject_weight_format_invalid_no_unit():
subject = Subject(weight="70")
assert check_subject_weight_format(subject) == InspectorMessage(
message="Subject weight should have the form '[numeric] [string]'.",
importance=Importance.CRITICAL,
check_function_name="check_subject_weight_format",
object_type="Subject",
object_name=subject.name,
location="/",
)


def test_check_subject_weight_format_no_space():
subject = Subject(weight="70kg")
assert check_subject_weight_format(subject) == InspectorMessage(
message="Subject weight should have the form '[numeric] [string]'.",
importance=Importance.CRITICAL,
check_function_name="check_subject_weight_format",
object_type="Subject",
object_name=subject.name,
location="/",
)


def test_check_subject_weight_format_invalid_non_numeric():
subject = Subject(weight="seventy kg")
assert check_subject_weight_format(subject) == InspectorMessage(
message="Subject weight should have the form '[numeric] [string]'.",
importance=Importance.CRITICAL,
check_function_name="check_subject_weight_format",
object_type="Subject",
object_name=subject.name,
location="/",
)


def test_check_subject_weight_format_none():
subject = Subject(weight=None)
assert check_subject_weight_format(subject) is None


def test_check_subject_proper_age_range_pass():
subject = Subject(subject_id="001", age="P1D/P3D")
assert check_subject_proper_age_range(subject) is None
Expand Down
26 changes: 26 additions & 0 deletions tests/unit_tests/test_time_series.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import h5py
import numpy as np
import pynwb
import pytest
from packaging import version

from nwbinspector import Importance, InspectorMessage
from nwbinspector.checks import (
check_data_orientation,
check_missing_unit,
check_rate_is_not_negative,
check_rate_is_not_zero,
check_regular_timestamps,
check_resolution,
Expand Down Expand Up @@ -235,6 +238,29 @@ def test_check_rate_is_not_zero_fail():
)


def test_check_rate_is_not_negative_pass():
time_series = pynwb.TimeSeries(name="test", unit="test_units", data=[1, 2, 3], rate=4.0)
assert check_rate_is_not_negative(time_series) is None


def test_check_rate_is_not_negative_fail():
# TODO: Install pynwb < 2.5.0 to test this
# Check pynwb version and skip this test if pynwb >= 2.5.0
if version.parse(pynwb.__version__) >= version.parse("2.5.0"):
pytest.skip("This test is not applicable for pynwb >= 2.5.0")

time_series = pynwb.TimeSeries(name="TimeSeriesTest", unit="n.a.", data=[1, 2, 3], rate=-4.0)

assert check_rate_is_not_negative(time_series) == InspectorMessage(
message=f"{time_series.name} has a negative sampling rate value of {time_series.rate}Hz.The sampling rate should have a positive value.",
importance=Importance.CRITICAL,
check_function_name="check_rate_is_not_negative",
object_type="TimeSeries",
object_name="TimeSeriesTest",
location="/",
)


def test_pass_check_timestamps_ascending_pass():
time_series = pynwb.TimeSeries(name="test_time_series", unit="test_units", data=[1, 2, 3], timestamps=[1, 2, 3])
assert check_timestamps_ascending(time_series) is None
Expand Down