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_entries/774.fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `check_subject_sex` flagging C. elegans subjects stored as `CElegansSubject` of ndx-subjects, which fixes `sex` to `"O"` and records `XX` (hermaphrodite) or `XO` (male) in `c_elegans_sex`. `sex` `"O"` or unset is now accepted when `c_elegans_sex` is `XX` or `XO`.
2 changes: 2 additions & 0 deletions docs/best_practices/nwbfile_metadata.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ The ``sex`` of the :ref:`nwb-schema:sec-Subject` should be specified as a single
following four possibilities: "M" (male), "F" (female), "U" (unknown), or "O" (other, for asexual species).

C. elegans are an exception to this rule. For C. elegans, the sex should either be "XO" (male) or "XX" (hermaphrodite).
The ``CElegansSubject`` type of the `ndx-subjects <https://github.com/catalystneuro/ndx-subjects>`_ extension
fixes ``sex`` to "O" and records "XO" or "XX" in its ``c_elegans_sex`` field instead, which is also accepted.

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

Expand Down
11 changes: 10 additions & 1 deletion src/nwbinspector/checks/_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,12 @@ def _check_subject_sex_defaults(sex: str) -> Optional[InspectorMessage]:
def _check_subject_sex_c_elegans(sex: str) -> Optional[InspectorMessage]:
"""Check if the subject sex has been specified properly for the C. elegans species."""
if sex not in ("XO", "XX"):
return InspectorMessage(message="For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite).")
return InspectorMessage(
message=(
"For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite), "
"or 'O' with 'XO' or 'XX' in `c_elegans_sex` (CElegansSubject of ndx-subjects)."
)
)

return None

Expand All @@ -346,6 +351,10 @@ def check_subject_sex(subject: Subject) -> Optional[InspectorMessage]:

Best Practice: :ref:`best_practice_subject_sex`
"""
# CElegansSubject of the ndx-subjects extension fixes `sex` to "O" and records
# XX (hermaphrodite) or XO (male) in a separate `c_elegans_sex` field
if getattr(subject, "c_elegans_sex", None) in ("XO", "XX") and subject.sex in (None, "", "O"):
return None
if subject and not subject.sex:
return InspectorMessage(message="Subject.sex is missing.")
if subject.species in ("Caenorhabditis elegans", "C. elegans"):
Expand Down
42 changes: 40 additions & 2 deletions tests/unit_tests/test_nwbfile_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,10 @@ def test_check_subject_sex_caenorhabditis_elegans_default_sex():
subject = Subject(subject_id="001", species="Caenorhabditis elegans", sex="F")

assert check_subject_sex(subject=subject) == InspectorMessage(
message="For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite).",
message=(
"For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite), "
"or 'O' with 'XO' or 'XX' in `c_elegans_sex` (CElegansSubject of ndx-subjects)."
),
importance=Importance.CRITICAL,
check_function_name="check_subject_sex",
object_type="Subject",
Expand All @@ -426,7 +429,10 @@ def test_check_subject_sex_c_elegans_default_sex():
subject = Subject(subject_id="001", species="C. elegans", sex="F")

assert check_subject_sex(subject=subject) == InspectorMessage(
message="For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite).",
message=(
"For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite), "
"or 'O' with 'XO' or 'XX' in `c_elegans_sex` (CElegansSubject of ndx-subjects)."
),
importance=Importance.CRITICAL,
check_function_name="check_subject_sex",
object_type="Subject",
Expand All @@ -447,6 +453,38 @@ def test_check_subject_sex_c_elegans_xx_sex():
assert check_subject_sex(subject) is None


class _CElegansSubject(Subject):
"""Stand-in for CElegansSubject of ndx-subjects, which adds a ``c_elegans_sex`` field."""

def __init__(self, c_elegans_sex=None, **kwargs):
super().__init__(**kwargs)
self.c_elegans_sex = c_elegans_sex


@pytest.mark.parametrize("sex", ["O", None])
@pytest.mark.parametrize("c_elegans_sex", ["XX", "XO"])
def test_check_subject_sex_c_elegans_ndx_subjects(sex, c_elegans_sex):
subject = _CElegansSubject(subject_id="001", species="Caenorhabditis elegans", sex=sex, c_elegans_sex=c_elegans_sex)

assert check_subject_sex(subject) is None


def test_check_subject_sex_c_elegans_ndx_subjects_invalid():
subject = _CElegansSubject(subject_id="001", species="Caenorhabditis elegans", sex="O", c_elegans_sex="F")

assert check_subject_sex(subject=subject) == InspectorMessage(
message=(
"For C. elegans, Subject.sex should be 'XO' (male) or 'XX' (hermaphrodite), "
"or 'O' with 'XO' or 'XX' in `c_elegans_sex` (CElegansSubject of ndx-subjects)."
),
importance=Importance.CRITICAL,
check_function_name="check_subject_sex",
object_type="_CElegansSubject",
object_name="subject",
location="/general/subject",
)


def test_pass_check_subject_age_with_dob():
subject = Subject(subject_id="001", sex="F", date_of_birth=datetime.now().astimezone())
assert check_subject_age(subject) is None
Expand Down
Loading