diff --git a/examples/quality_control.json b/examples/quality_control.json new file mode 100644 index 000000000..11cd830a6 --- /dev/null +++ b/examples/quality_control.json @@ -0,0 +1,41 @@ +{ + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/quality_metrics.py", + "schema_version": "0.0.1", + "overall_status": "Pass", + "overall_status_date": "2022-11-22", + "evaluations": [ + { + "evaluation_modality": { + "name": "Behavior videos", + "abbreviation": "behavior-videos" + }, + "evaluation_stage": "Data acquisition", + "evaluator_full_name": "Fred Flinstone", + "evaluation_date": "2022-11-22", + "qc_metrics": { + "Video_1_num_frames": 662, + "Video_2_num_frames": 662, + "Frame_match": true + }, + "stage_status": "Pass", + "notes": null + }, + { + "evaluation_modality": { + "name": "Extracellular electrophysiology", + "abbreviation": "ecephys" + }, + "evaluation_stage": "Data acquisition", + "evaluator_full_name": "Fred Flinstone", + "evaluation_date": "2022-11-22", + "qc_metrics": { + "ProbeA_success": true, + "ProbeB_success": true, + "ProbeC_success": false + }, + "stage_status": "Pass", + "notes": null + } + ], + "notes": null +} \ No newline at end of file diff --git a/examples/quality_control.py b/examples/quality_control.py new file mode 100644 index 000000000..69dabc64a --- /dev/null +++ b/examples/quality_control.py @@ -0,0 +1,44 @@ +"""Example quality control processing""" + +from datetime import date + +from aind_data_schema_models.modalities import Modality + +from aind_data_schema.core.quality_control import QCEvaluation, QualityControl + +t = date(2022, 11, 22) + +q = QualityControl( + overall_status="Pass", + overall_status_date=t, + evaluations=[ + QCEvaluation( + evaluation_modality=Modality.BEHAVIOR_VIDEOS, + evaluation_stage="Data acquisition", + evaluator_full_name="Fred Flinstone", + evaluation_date=t, + qc_metrics={ + "Video_1_num_frames": 662, + "Video_2_num_frames": 662, + "Frame_match": True, + }, + stage_status="Pass", + ), + QCEvaluation( + evaluation_modality=Modality.ECEPHYS, + evaluation_stage="Data acquisition", + evaluator_full_name="Fred Flinstone", + evaluation_date=t, + qc_metrics={ + "ProbeA_success": True, + "ProbeB_success": True, + "ProbeC_success": False, + }, + stage_status="Pass", + ), + ], +) + +serialized = q.model_dump_json() +deserialized = QualityControl.model_validate_json(serialized) +q.write_standard_file() diff --git a/src/aind_data_schema/core/metadata.py b/src/aind_data_schema/core/metadata.py index 858be97fa..24ac54703 100644 --- a/src/aind_data_schema/core/metadata.py +++ b/src/aind_data_schema/core/metadata.py @@ -15,6 +15,7 @@ from aind_data_schema.core.instrument import Instrument from aind_data_schema.core.procedures import Injection, Procedures, Surgery from aind_data_schema.core.processing import Processing +from aind_data_schema.core.quality_control import QualityControl from aind_data_schema.core.rig import Rig from aind_data_schema.core.session import Session from aind_data_schema.core.subject import Subject @@ -104,6 +105,9 @@ class Metadata(AindCoreModel): instrument: Optional[Instrument] = Field( default=None, title="Instrument", description="Instrument, which is a collection of devices" ) + quality_control: Optional[QualityControl] = Field( + default=None, title="Quality Control", description="Description of quality metrics for a data asset" + ) @field_validator( "subject", @@ -114,6 +118,7 @@ class Metadata(AindCoreModel): "processing", "acquisition", "instrument", + "quality_control", mode="before", ) def validate_core_fields(cls, value, info: ValidationInfo): diff --git a/src/aind_data_schema/core/quality_control.py b/src/aind_data_schema/core/quality_control.py new file mode 100644 index 000000000..775590f0a --- /dev/null +++ b/src/aind_data_schema/core/quality_control.py @@ -0,0 +1,43 @@ +""" Schemas for Quality Metrics """ + +from __future__ import annotations + +from datetime import date +from enum import Enum +from typing import List, Literal, Optional + +from aind_data_schema_models.modalities import Modality +from pydantic import Field + +from aind_data_schema.base import AindCoreModel, AindGeneric, AindGenericType, AindModel + + +class Status(str, Enum): + """QC Status""" + + FAIL = "Fail" + PASS = "Pass" + + +class QCEvaluation(AindModel): + """Description of one evaluation stage""" + + evaluation_modality: Modality.ONE_OF = Field(..., title="Modality") + evaluation_stage: str = Field(..., title="Evaluation stage") + evaluator_full_name: str = Field(..., title="Evaluator full name") + evaluation_date: date = Field(..., title="Evaluation date") + qc_metrics: AindGenericType = Field(AindGeneric(), title="QC metrics") + stage_status: Status = Field(..., title="Stage status") + notes: Optional[str] = Field(None, title="Notes") + + +class QualityControl(AindCoreModel): + """Description of quality metrics for a data asset""" + + _DESCRIBED_BY_URL = AindCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/quality_metrics.py" + describedBy: str = Field(_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) + schema_version: Literal["0.0.1"] = Field("0.0.1") + overall_status: Status = Field(..., title="Overall status") + overall_status_date: date = Field(..., title="Date of status") + evaluations: List[QCEvaluation] = Field(..., title="Evaluations") + notes: Optional[str] = Field(None, title="Notes") diff --git a/tests/test_quality_control.py b/tests/test_quality_control.py new file mode 100644 index 000000000..7ed56b4a0 --- /dev/null +++ b/tests/test_quality_control.py @@ -0,0 +1,40 @@ +"""test quality metrics """ + +import unittest +from datetime import date + +from aind_data_schema_models.modalities import Modality +from pydantic import ValidationError + +from aind_data_schema.core.quality_control import QCEvaluation, QualityControl + + +class QualityControlTests(unittest.TestCase): + """test quality metrics schema""" + + def test_constructors(self): + """testing constructors""" + + with self.assertRaises(ValidationError): + q = QualityControl() + + q = QualityControl( + overall_status_date=date.fromisoformat("2020-10-10"), + overall_status="Pass", + evaluations=[ + QCEvaluation( + evaluator_full_name="Bob", + evaluation_date=date.fromisoformat("2020-10-10"), + evaluation_modality=Modality.ECEPHYS, + evaluation_stage="Spike sorting", + qc_metrics={"number_good_units": [622]}, + stage_status="Pass", + ), + ], + ) + + assert q is not None + + +if __name__ == "__main__": + unittest.main()