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
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_spike_times_without_nans` to detect NaN values in Units spike times, which indicate conversion bugs or unclean array padding. [#689](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/689)
* Added `check_subject_age_reference` to validate that `Subject.age__reference`, when present, is one of the supported values (`"birth"` or `"gestational"`). This catches invalid references in files written by tools that do not enforce the schema constraint. [#250](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/250)

### Improvements
* Raised the minimum required PyNWB to `>=4.0` to track the latest PyNWB release. [#708](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/708)
Expand Down
8 changes: 7 additions & 1 deletion docs/best_practices/nwbfile_metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,13 @@ If the precise age is unknown, an age range can be given by "[lower bound]/[uppe
that the age is in between 10 and 20 days. If only the lower bound is known, then including only the slash after that lower bound can be used to indicate a
missing bound. For instance, "P90Y/" would indicate that the age is 90 years or older.

Check function: :py:meth:`~nwbinspector.checks._nwbfile_metadata.check_subject_age`
The ``age`` is measured relative to a reference point, controlled by the ``age__reference`` field of the
:ref:`nwb-schema:sec-Subject`. The only supported values are ``"birth"`` (the default) and ``"gestational"``. Use
``"gestational"`` when the age is measured from conception rather than from birth, as is common for embryonic or
prenatal preparations.

Check functions: :py:meth:`~nwbinspector.checks._nwbfile_metadata.check_subject_age` and
:py:meth:`~nwbinspector.checks._nwbfile_metadata.check_subject_age_reference`



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 @@ -59,6 +59,7 @@
check_session_start_time_future_date,
check_session_start_time_old_date,
check_subject_age,
check_subject_age_reference,
check_subject_exists,
check_subject_id_exists,
check_subject_id_no_slashes,
Expand Down Expand Up @@ -132,6 +133,7 @@
"check_keywords",
"check_institution",
"check_subject_age",
"check_subject_age_reference",
"check_subject_sex",
"check_subject_exists",
"check_doi_publications",
Expand Down
19 changes: 19 additions & 0 deletions src/nwbinspector/checks/_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,25 @@ def check_subject_proper_age_range(subject: Subject) -> Optional[InspectorMessag
return None


@register_check(importance=Importance.BEST_PRACTICE_SUGGESTION, neurodata_type=Subject)
def check_subject_age_reference(subject: Subject) -> Optional[InspectorMessage]:
"""
Check if the Subject age reference, when specified, is one of the supported options.

Best Practice: :ref:`best_practice_subject_age`
"""
valid_options = ["birth", "gestational"]
if subject.age__reference is not None and subject.age__reference not in valid_options:
return InspectorMessage(
message=(
f"Subject age reference, '{subject.age__reference}', is not one of the valid options "
f"({valid_options})."
)
)

return None


@register_check(importance=Importance.CRITICAL, neurodata_type=Subject)
def check_subject_id_exists(subject: Subject) -> Optional[InspectorMessage]:
"""
Expand Down
38 changes: 38 additions & 0 deletions tests/unit_tests/test_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
check_session_start_time_future_date,
check_session_start_time_old_date,
check_subject_age,
check_subject_age_reference,
check_subject_exists,
check_subject_id_exists,
check_subject_id_no_slashes,
Expand Down Expand Up @@ -474,6 +475,43 @@ def test_check_subject_age_with_years_fail():
)


def test_check_subject_age_reference_default_pass():
subject = Subject(subject_id="001", age="P1D")
assert check_subject_age_reference(subject) is None


def test_check_subject_age_reference_birth_pass():
subject = Subject(subject_id="001", age="P1D", age__reference="birth")
assert check_subject_age_reference(subject) is None


def test_check_subject_age_reference_gestational_pass():
subject = Subject(subject_id="001", age="P1D", age__reference="gestational")
assert check_subject_age_reference(subject) is None


def test_check_subject_age_reference_none_pass():
# Files written before age__reference existed (or by other tools) may have no reference set.
subject = Subject(subject_id="001", age="P1D")
subject.fields["age__reference"] = None
assert check_subject_age_reference(subject) is None


def test_check_subject_age_reference_fail():
# PyNWB rejects invalid references at construction time, so emulate a file written by another
# tool with an unsupported value by overriding the field after construction.
subject = Subject(subject_id="001", age="P1D")
subject.fields["age__reference"] = "conception"
assert check_subject_age_reference(subject) == InspectorMessage(
message=("Subject age reference, 'conception', is not one of the valid options (['birth', 'gestational'])."),
importance=Importance.BEST_PRACTICE_SUGGESTION,
check_function_name="check_subject_age_reference",
object_type="Subject",
object_name="subject",
location="/general/subject",
)


def test_pass_check_subject_species_exists():
subject = Subject(subject_id="001", species="Homo sapiens")
assert check_subject_species_exists(subject) is None
Expand Down
Loading