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
5 changes: 4 additions & 1 deletion docs/source/aind_data_schema_models/harp_types.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ Harp device types
|------|------|------|
| `ANALOGINPUT` | `AnalogInput` | `1236` |
| `ARCHIMEDES` | `Archimedes` | `1136` |
| `AUDIOSWITCH` | `AudioSwitch` | `1248` |
| `BEHAVIOR` | `Behavior` | `1216` |
| `CAMERACONTROLLER` | `CameraController` | `1168` |
| `CAMERACONTROLLERGEN2` | `CameraControllerGen2` | `1170` |
| `CLOCKSYNCHRONIZER` | `ClockSynchronizer` | `1152` |
| `CURRENTDRIVER` | `CurrentDriver` | `1282` |
| `CUTTLEFISH` | `cuTTLefish` | `1403` |
| `CUTTLEFISHFIP` | `cuTTLefishFip` | `1407` |
| `DRIVER12VOLTS` | `Driver12Volts` | `1072` |
Expand All @@ -22,6 +24,7 @@ Harp device types
| `HOBGOBLIN` | `Hobgoblin` | `123` |
| `IBL_BEHAVIOR_CONTROL` | `Ibl_behavior_control` | `2080` |
| `INPUTEXPANDER` | `InputExpander` | `1106` |
| `LASERDRIVERCONTROLLER` | `LaserDriverController` | `1298` |
| `LEDCONTROLLER` | `LedController` | `1088` |
| `LICKETYSPLIT` | `LicketySplit` | `1400` |
| `LOADCELLS` | `LoadCells` | `1232` |
Expand All @@ -33,7 +36,7 @@ Harp device types
| `POKE` | `Poke` | `1024` |
| `PYCONTROLADAPTER` | `PyControlAdapter` | `1184` |
| `RFIDREADER` | `RfidReader` | `2094` |
| `RGBARRAY` | `RgbArray` | `1248` |
| `RGBARRAY` | `RgbArray` | `1264` |
| `SIMPLEANALOGGENERATOR` | `SimpleAnalogGenerator` | `1121` |
| `SNIFFDETECTOR` | `SniffDetector` | `1401` |
| `SOUNDCARD` | `SoundCard` | `1280` |
Expand Down
2 changes: 1 addition & 1 deletion docs/source/components/injection_procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Description of an injection procedure
| `targeted_structure` | Optional[[MouseAnatomyModel](../aind_data_schema_models/external.md#mouseanatomymodel)] | Injection target (Use InjectionTargets) |
| `relative_position` | Optional[List[[AnatomicalRelative](../aind_data_schema_models/coordinates.md#anatomicalrelative)]] | Relative position |
| `dynamics` | List[[InjectionDynamics](#injectiondynamics)] | Injection dynamics (List of injection events, one per location/depth) |
| `protocol_id` | `Optional[str]` | Protocol ID (DOI for protocols.io) |
| <del>`protocol_id`</del> | `Optional[str]` | **[DEPRECATED]** Use protocol_id in Surgery or NonSurgicalInjection instead. Protocol ID (DOI for protocols.io) |


### InjectionDynamics
Expand Down
14 changes: 14 additions & 0 deletions docs/source/components/subject_procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ Description of a non-surgical procedure performed on a subject
| `notes` | `Optional[str]` | Notes |


### NonSurgicalInjection

Injection procedure performed outside of surgery,
which may include one or more injections at different locations/depths

| Field | Type | Title (Description) |
|-------|------|-------------|
| `start_date` | `datetime.date` | Start date |
| `ethics_review_id` | `str` | Ethics review ID |
| `protocol_id` | `Optional[str]` | Protocol ID (DOI for protocols.io) |
| `injections` | List[[Injection](injection_procedures.md#injection)] | Injections |
| `notes` | `Optional[str]` | Notes |


### Surgery

Description of subject procedures performed at one time
Expand Down
2 changes: 1 addition & 1 deletion docs/source/components/surgery_procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Description of an injection procedure into a brain
| `injection_materials` | List[[ViralMaterial](injection_procedures.md#viralmaterial) or [NonViralMaterial](injection_procedures.md#nonviralmaterial)] | Injection material |
| `relative_position` | Optional[List[[AnatomicalRelative](../aind_data_schema_models/coordinates.md#anatomicalrelative)]] | Relative position |
| `dynamics` | List[[InjectionDynamics](injection_procedures.md#injectiondynamics)] | Injection dynamics (List of injection events, one per location/depth) |
| `protocol_id` | `Optional[str]` | Protocol ID (DOI for protocols.io) |
| <del>`protocol_id`</del> | `Optional[str]` | **[DEPRECATED]** Use protocol_id in Surgery or NonSurgicalInjection instead. Protocol ID (DOI for protocols.io) |


### CatheterImplant
Expand Down
2 changes: 1 addition & 1 deletion docs/source/procedures.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Description of all procedures performed on a subject, including surgeries, injec
| Field | Type | Title (Description) |
|-------|------|-------------|
| `subject_id` | `str` | Subject ID (Unique identifier for the subject of data acquisition) |
| `subject_procedures` | List[[Surgery](components/subject_procedures.md#surgery) or [Injection](components/injection_procedures.md#injection) or [TrainingProtocol](components/subject_procedures.md#trainingprotocol) or [WaterRestriction](components/subject_procedures.md#waterrestriction) or [GenericSubjectProcedure](components/subject_procedures.md#genericsubjectprocedure)] | Subject Procedures (Procedures performed on a live subject) |
| `subject_procedures` | List[[Surgery](components/subject_procedures.md#surgery) or [Injection](components/injection_procedures.md#injection) or [NonSurgicalInjection](components/subject_procedures.md#nonsurgicalinjection) or [TrainingProtocol](components/subject_procedures.md#trainingprotocol) or [WaterRestriction](components/subject_procedures.md#waterrestriction) or [GenericSubjectProcedure](components/subject_procedures.md#genericsubjectprocedure)] | Subject Procedures (Procedures performed on a live subject) |
| `specimen_procedures` | List[[SpecimenProcedure](components/specimen_procedures.md#specimenprocedure)] | Specimen Procedures (Procedures performed on tissue extracted after perfusion) |
| `coordinate_system` | Optional[[CoordinateSystem](components/coordinates.md#coordinatesystem)] | Coordinate System (Origin and axis definitions for determining the configured position of devices implanted during procedures. Required when coordinates are provided within the Procedures) |
| `notes` | `Optional[str]` | Notes |
7 changes: 6 additions & 1 deletion src/aind_data_schema/components/injection_procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,9 @@ class Injection(DataModel):
dynamics: List[InjectionDynamics] = Field(
..., title="Injection dynamics", description="List of injection events, one per location/depth"
)
protocol_id: Optional[str] = Field(default=None, title="Protocol ID", description="DOI for protocols.io")
protocol_id: Optional[str] = Field(
default=None,
title="Protocol ID",
description="DOI for protocols.io",
deprecated="Use protocol_id in Surgery or NonSurgicalInjection instead",
)
12 changes: 12 additions & 0 deletions src/aind_data_schema/components/subject_procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ class GenericSubjectProcedure(DataModel):
notes: Optional[str] = Field(default=None, title="Notes")


class NonSurgicalInjection(DataModel):
"""Injection procedure performed outside of surgery,
which may include one or more injections at different locations/depths
"""

start_date: date = Field(..., title="Start date")
ethics_review_id: str = Field(..., title="Ethics review ID")
protocol_id: Optional[str] = Field(default=None, title="Protocol ID", description="DOI for protocols.io")
Comment thread
dbirman marked this conversation as resolved.
injections: List[Injection] = Field(..., title="Injections", min_length=1)
notes: Optional[str] = Field(default=None, title="Notes")


class TrainingProtocol(DataModel):
"""Description of an animal training protocol"""

Expand Down
20 changes: 19 additions & 1 deletion src/aind_data_schema/core/procedures.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""schema for various Procedures"""

import warnings
from typing import List, Literal, Optional

from pydantic import Field, SkipValidation, model_validator
Expand All @@ -13,6 +14,7 @@
Surgery,
TrainingProtocol,
WaterRestriction,
NonSurgicalInjection,
)
from aind_data_schema.utils.merge import merge_notes, merge_coordinate_systems
from aind_data_schema.utils.validators import subject_specimen_id_compatibility
Expand All @@ -31,7 +33,7 @@ class Procedures(DataCoreModel):
title="Subject ID",
)
subject_procedures: DiscriminatedList[
Surgery | Injection | TrainingProtocol | WaterRestriction | GenericSubjectProcedure
Surgery | Injection | NonSurgicalInjection | TrainingProtocol | WaterRestriction | GenericSubjectProcedure
] = Field(default=[], title="Subject Procedures", description="Procedures performed on a live subject")
specimen_procedures: List[SpecimenProcedure] = Field(
default=[], title="Specimen Procedures", description="Procedures performed on tissue extracted after perfusion"
Expand Down Expand Up @@ -72,6 +74,22 @@ def get_device_names(self) -> List[str]:

return list(device_names)

@model_validator(mode="after")
def reject_injections(self):
"""Raise a warning for injections since they should now be wrapped
in a Surgery or NonSurgicalInjection procedure
"""

for procedure in self.subject_procedures:
if isinstance(procedure, Injection):
warnings.warn(
"Injection procedures should be wrapped in a Surgery or NonSurgicalInjection procedure.",
UserWarning,
stacklevel=2,
)

return self

@model_validator(mode="after")
def validate_subject_specimen_ids(self):
"""Validate that the subject_id and specimen_id match"""
Expand Down
23 changes: 23 additions & 0 deletions tests/test_procedures.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,29 @@ def test_required_field_validation_check(self):
p = Procedures(subject_id="12345")
self.assertEqual("12345", p.subject_id)

@patch("aind_data_schema_models.mouse_anatomy.get_emapa_id")
def test_unwrapped_injection_warns(self, mock_get_emapa_id):
"""Unwrapped Injection in subject_procedures should emit a UserWarning"""
mock_get_emapa_id.return_value = "123456"
with self.assertWarns(UserWarning):
Procedures(
subject_id="12345",
subject_procedures=[
Injection(
injection_materials=[NonViralMaterial(name="saline", source=Organization.OTHER)],
dynamics=[
InjectionDynamics(
volume=1,
volume_unit=VolumeUnit.UL,
duration=1,
duration_unit=TimeUnit.S,
profile=InjectionProfile.BOLUS,
)
],
)
],
)

@patch("aind_data_schema_models.mouse_anatomy.get_emapa_id")
def test_injection_material_check(self, mock_get_emapa_id):
"""Check for validation error when injection_materials is empty"""
Expand Down
Loading