bagel 0.5.1.dev7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,119 @@
1
+ import inspect
2
+ from pathlib import Path
3
+ from typing import Iterable
4
+
5
+ import pydantic
6
+ from pydantic import ValidationError
7
+
8
+ from bagel import models
9
+ from bagel.logger import log_error, logger
10
+ from bagel.mappings import NB, SUPPORTED_NAMESPACES
11
+ from bagel.utilities import file_utils
12
+
13
+
14
+ def generate_context():
15
+ # Adapted from the dandi-schema context generation function
16
+ # https://github.com/dandi/dandi-schema/blob/c616d87eaae8869770df0cb5405c24afdb9db096/dandischema/metadata.py
17
+ field_preamble = {
18
+ namespace.pf: namespace.url for namespace in SUPPORTED_NAMESPACES
19
+ }
20
+ fields = {}
21
+ for klass_name, klass in inspect.getmembers(models):
22
+ if inspect.isclass(klass) and issubclass(klass, pydantic.BaseModel):
23
+ fields[klass_name] = f"{NB.pf}:{klass_name}"
24
+ for name, field in klass.model_fields.items():
25
+ if name == "schemaKey":
26
+ fields[name] = "@type"
27
+ elif name == "identifier":
28
+ fields[name] = "@id"
29
+ elif name not in fields:
30
+ fields[name] = {"@id": f"{NB.pf}:{name}"}
31
+
32
+ field_preamble.update(**fields)
33
+
34
+ return {"@context": field_preamble}
35
+
36
+
37
+ def add_context_to_graph_dataset(dataset: models.Dataset) -> dict:
38
+ """Add the Neurobagel context to a graph-ready dataset to form a JSONLD dictionary."""
39
+ context = generate_context()
40
+ # We can't just exclude_unset here because the identifier and schemaKey
41
+ # for each instance are created as default values and so technically are never set
42
+ # TODO: we should revisit this because there may be reasons to have None be meaningful in the future
43
+ return {**context, **dataset.model_dump(exclude_none=True)}
44
+
45
+
46
+ def get_subs_missing_from_pheno_data(
47
+ subjects: Iterable, pheno_subjects: Iterable
48
+ ) -> list:
49
+ """Check a list of subject IDs and return any not found in the provided phenotypic subject list."""
50
+ return list(set(subjects).difference(pheno_subjects))
51
+
52
+
53
+ def confirm_subs_match_pheno_data(
54
+ subjects: Iterable, subject_source_for_err: str, pheno_subjects: Iterable
55
+ ):
56
+ """
57
+ Return an error if not all subjects in the subject list are found in the provided phenotypic subject list.
58
+ """
59
+ missing_subs = get_subs_missing_from_pheno_data(
60
+ subjects=subjects,
61
+ pheno_subjects=pheno_subjects,
62
+ )
63
+
64
+ if len(missing_subs) > 0:
65
+ log_error(
66
+ logger,
67
+ f"The specified {subject_source_for_err} contains subject IDs not found in "
68
+ "the provided json-ld file:\n"
69
+ f"{missing_subs}\n"
70
+ "Subject IDs are case sensitive. "
71
+ f"Please check that the {subject_source_for_err} corresponds to the dataset in the provided .jsonld.",
72
+ )
73
+
74
+
75
+ def extract_and_validate_jsonld_dataset(file_path: Path) -> models.Dataset:
76
+ """
77
+ Strip the context from a user-provided JSONLD and validate the remaining contents
78
+ against the data model for a Neurobagel dataset.
79
+ """
80
+ jsonld = file_utils.load_json(file_path)
81
+ jsonld.pop("@context")
82
+ try:
83
+ jsonld_dataset = models.Dataset.model_validate(jsonld)
84
+ except ValidationError as err:
85
+ log_error(
86
+ logger,
87
+ f"Error: {file_path} is not a valid Neurobagel JSONLD dataset. "
88
+ "Please ensure to provide a valid JSONLD file generated by Neurobagel CLI commands.\n"
89
+ f"Validation errors: {str(err)}",
90
+ )
91
+
92
+ return jsonld_dataset
93
+
94
+
95
+ def get_subject_instances(
96
+ dataset: models.Dataset,
97
+ ) -> dict[str, models.Subject]:
98
+ """
99
+ Return a dictionary of subjects for a given Neurobagel dataset from JSONLD data,
100
+ where keys are subject labels and values are the subject objects.
101
+ """
102
+ return {
103
+ subject.hasLabel: subject for subject in getattr(dataset, "hasSamples")
104
+ }
105
+
106
+
107
+ def get_imaging_session_instances(
108
+ jsonld_subject: models.Subject,
109
+ ) -> dict[str, models.ImagingSession]:
110
+ """
111
+ Return a dictionary of imaging sessions for a given subject from JSONLD data,
112
+ where the keys are the session labels and values are the session objects.
113
+ """
114
+ jsonld_sub_sessions_dict = {}
115
+ for jsonld_sub_ses in getattr(jsonld_subject, "hasSession"):
116
+ if jsonld_sub_ses.schemaKey == "ImagingSession":
117
+ jsonld_sub_sessions_dict[jsonld_sub_ses.hasLabel] = jsonld_sub_ses
118
+
119
+ return jsonld_sub_sessions_dict