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
2 changes: 2 additions & 0 deletions dandi/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"species",
"strain",
"subject_id",
# CElegansSubject of ndx-subjects fixes `sex` to "O" and keeps XX/XO here
"c_elegans_sex",
)

metadata_nwb_dandi_fields = ("cell_id", "slice_id", "tissue_sample_id", "probe_ids")
Expand Down
31 changes: 31 additions & 0 deletions dandi/metadata/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,8 +297,31 @@ def timedelta2duration(delta: timedelta) -> str:
}


def _is_c_elegans(species: Any) -> bool:
"""Whether NWB ``subject.species`` designates *Caenorhabditis elegans*"""
if not isinstance(species, str):
return False
species = species.strip().lower()
return (
species in ("c. elegans", "caenorhabditis elegans")
or species.startswith("caenorhabditis elegans ")
or species.endswith("/ncbitaxon_6239")
)


def extract_sex(metadata: dict) -> models.SexType | None:
value = metadata.get("sex", None)
c_elegans_sex = metadata.get("c_elegans_sex")
is_c_elegans = _is_c_elegans(metadata.get("species"))
if (
isinstance(c_elegans_sex, str)
and c_elegans_sex.strip().lower() in ("xx", "xo")
and (value is None or value.strip().lower() in ("", "o", "other"))
):
# CElegansSubject of ndx-subjects records the sex there and fixes
# `sex` to "O"
value = c_elegans_sex
is_c_elegans = True
if value is not None and value != "":
value_orig = value.strip()
value = value_orig.lower()
Expand All @@ -311,6 +334,14 @@ def extract_sex(metadata: dict) -> models.SexType | None:
elif value == "hermaphrodite":
value_id = "http://purl.obolibrary.org/obo/PATO_0001340"
value = "Hermaphrodite"
elif value in ["xx", "xo"] and is_c_elegans:
# nwbinspector requires XX (hermaphrodite) or XO (male) for C. elegans
if value == "xx":
value_id = "http://purl.obolibrary.org/obo/PATO_0001340"
value = "Hermaphrodite"
else:
value_id = "http://purl.obolibrary.org/obo/PATO_0000384"
value = "Male"
elif value in ["unknown", "u"]:
value_id = None
value = "Unknown"
Expand Down
73 changes: 73 additions & 0 deletions dandi/tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,79 @@ def test_extract_sex(sex: str, identifier: str | None, name: str) -> None:
assert rec.name == name


@pytest.mark.ai_generated
@pytest.mark.parametrize(
"species",
[
"Caenorhabditis elegans",
"C. elegans",
"Caenorhabditis elegans - Roundworm",
"http://purl.obolibrary.org/obo/NCBITaxon_6239",
],
)
@pytest.mark.parametrize(
"sex,identifier,name",
[
("XX", "http://purl.obolibrary.org/obo/PATO_0001340", "Hermaphrodite"),
("XO", "http://purl.obolibrary.org/obo/PATO_0000384", "Male"),
("xo", "http://purl.obolibrary.org/obo/PATO_0000384", "Male"),
],
)
def test_extract_sex_c_elegans(
species: str, sex: str, identifier: str, name: str
) -> None:
# nwbinspector requires these chromosomal codes for C. elegans
rec = extract_sex({"sex": sex, "species": species})
assert rec is not None
assert str(rec.identifier) == identifier
assert rec.name == name


@pytest.mark.ai_generated
@pytest.mark.parametrize("sex", ["O", "other", None])
@pytest.mark.parametrize(
"c_elegans_sex,identifier,name",
[
("XX", "http://purl.obolibrary.org/obo/PATO_0001340", "Hermaphrodite"),
("xo", "http://purl.obolibrary.org/obo/PATO_0000384", "Male"),
],
)
def test_extract_sex_ndx_subjects_c_elegans(
sex: str | None, c_elegans_sex: str, identifier: str, name: str
) -> None:
# CElegansSubject of ndx-subjects fixes `sex` to "O" and records the
# actual sex in `c_elegans_sex`
rec = extract_sex(
{
"sex": sex,
"c_elegans_sex": c_elegans_sex,
"species": "Caenorhabditis elegans",
}
)
assert rec is not None
assert str(rec.identifier) == identifier
assert rec.name == name


@pytest.mark.ai_generated
def test_extract_sex_c_elegans_sex_does_not_override_explicit_sex() -> None:
rec = extract_sex({"sex": "M", "c_elegans_sex": "XX"})
assert rec is not None
assert rec.name == "Male"


@pytest.mark.ai_generated
@pytest.mark.parametrize("species", [None, "Mus musculus", "Drosophila melanogaster"])
@pytest.mark.parametrize("sex", ["XX", "XO"])
def test_extract_sex_chromosomal_other_species(species: str | None, sex: str) -> None:
# outside of C. elegans, XX/XO do not unambiguously designate a sex
metadata = {"sex": sex}
if species is not None:
metadata["species"] = species
with pytest.raises(ValueError, match="Cannot interpret sex field"):
extract_sex(metadata)


@pytest.mark.parametrize(
"species",
[
Expand Down
Loading