diff --git a/README.md b/README.md index c07055c79..11e6d57d1 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ If you'd like to propose a large change or addition, or generally have a questio ## Controlled Vocabularies -Controlled vocabularies and other enumerated lists are maintained in a separate repository: [aind-data-schema-models](https://github.com/AllenNeuralDynamics/aind-data-schema-models). This allows us to specify these lists without changing aind-data-schema. Controlled vocabularies include lists of organizations, manufacturers, species, modalities, platforms, units, harp devices, and registries. +Controlled vocabularies and other enumerated lists are maintained in a separate repository: [aind-data-schema-models](https://github.com/AllenNeuralDynamics/aind-data-schema-models). This allows us to specify these lists without changing aind-data-schema. Controlled vocabularies include lists of organizations, manufacturers, species, modalities, units, harp devices, and registries. To upgrade to the latest data models version: ``` diff --git a/docs/source/conf.py b/docs/source/conf.py index 42a7ee173..ca6254c48 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,6 @@ metadata, procedures, processing, - rig, session, subject, quality_control, @@ -31,7 +30,7 @@ metadata, procedures, processing, - rig, + instrument, session, subject, quality_control, @@ -88,7 +87,6 @@ "metadata.nd", "procedures", "processing", - "rig", "session", "subject", "quality_control", diff --git a/docs/source/data_description.rst b/docs/source/data_description.rst index 74aa343db..987e02724 100644 --- a/docs/source/data_description.rst +++ b/docs/source/data_description.rst @@ -11,26 +11,9 @@ data modalities, dates of collection, and more. The data description is created during data transfer based on information you provide to that service and pulling information from internal resources. -**Q: What is the difference between modality and platform?** - -Modalities are types of data being collected. A platform is a standardized way of collecting one or more modalities of -data that we give a name. Platform standardization -- of file formats, hardware setup, etc -- enables us automatically -and reliably process data with centrally managed data pipelines. - - Example 1: the behavior platform leverages Harp and Bonsai to run behavioral experiments and acquire multiple - modalities of data (behavior videos, electrophysiology, photometry, etc). - - Example 2: We use SmartSPIM lightsheet microscopes to collect whole-mesoscale whole brain neuroanatomy data. This - is a single-modality (SPIM) platform (mesoscale anatomy, SmartSPIM colloquially). - Questions for AIND users ------------------------ -**Q: What platform should I use?** - -There is a controlled vocabulary in (aind-data-schema-models)[https://github.com/AllenNeuralDynamics/aind-data-schema-models]. -Pick the one that most closely aligns with how you have collected data. If none exists, talk to Saskia de Vries or David Feng. - **Q: This data is for a AIND project and not part of a grant. Shouldn’t the funder be AIND?** No. The funding for internally funded AIND or AIBS work is listed as “Allen Institute”. @@ -44,16 +27,3 @@ to make sure your grant is on that sheet. In the future we may need to tag cloud resources based on the originating group, which may or may not be in AIND, in order to track usage and spending. - - -**Q: What happened to the “experiment type” asset label? Why are we using platform names instead?** - -Formerly we used a short label called “experiment type” in asset names instead of platform -names. This concept was confusing because it was difficult to distinguish from a “modality”. -Most of our data contains multiple modalities. A recording session may contain trained behavior -event data (e.g. lick times), behavior videos (e.g. face camera), neuropixels recordings, and -fiber photometry recordings. - -Anchoring browsing on data collection platforms is clearer. We will tag sessions in our metadata -database to indicate which modalities are present in which sessions. - \ No newline at end of file diff --git a/docs/source/data_organization.rst b/docs/source/data_organization.rst index 91fd3f1d4..5d7a43108 100644 --- a/docs/source/data_organization.rst +++ b/docs/source/data_organization.rst @@ -86,9 +86,7 @@ name be unique, but we should not use this name to encode essential metadata. All primary data assets have the following naming convention: - ___ - -A platform is a standardized system for collecting one or more modalities of data. + __ A few points: @@ -97,8 +95,7 @@ A few points: - Acquisition date and time are essential for uniqueness - Acquisition date and time are in local time zone - Time-zone is documented in metadata -- All tokens (e.g. ````, ````) must not contain underscores or illegal filename characters. -- ````: a less-than 10 character shorthand for a data acquisition platform +- All tokens (e.g. ````) must not contain underscores or illegal filename characters. Again, this name is strictly for uniqueness. We could use a GUID, but choose to have a relatively simple naming convention to facilitate casual browsing. @@ -123,7 +120,7 @@ Primary data assets are organized as follows: - logs (general log files generated by the instrument or rig that are not modality-specific) - -Platform abbreviation and modality terms come from controlled vocabularies in aind-data-schema-models. +Modality terms come from controlled vocabularies in aind-data-schema-models. Example for simultaneous electrophysiology with optotagging and fiber photometry: @@ -145,9 +142,9 @@ Example for simultaneous electrophysiology with optotagging and fiber photometry - face_camera.mp4 - body_camera.mp4 -Example for lightsheet microscopy data acquired on the ExaSPIM platform: +Example for lightsheet microscopy data: - - exaSPIM_655568_2022-04-26_11-48-09 + - 655568_2022-04-26_11-48-09 - - SPIM - SPIM.ome.zarr @@ -200,10 +197,9 @@ File name guidelines When naming files, we should: - use terms from vocabularies defined in aind-data-schema, e.g. - - platform names and modalities behavior video file names + - modalities, etc + - use isoformat datetimes, e.g. "YYYY-MM-DDThhmmss" - use “yyyy-mm-dd" and “hh-mm-ss" in local time zone for dates and times - separate tokens with underscores, and not include underscores in tokens, e.g. - - Do this: ``EFIP_655568_2022-04-26_11-48-09`` - - Not this: ``EFIP-655568-2022_04_26-11_48_09`` + - Do this: ``EFIP_655568_2022-04-26T114809`` - Do not include illegal filename characters in tokens - diff --git a/docs/source/example_workflow/example_workflow.py b/docs/source/example_workflow/example_workflow.py index 9c597dc5e..9a909c630 100644 --- a/docs/source/example_workflow/example_workflow.py +++ b/docs/source/example_workflow/example_workflow.py @@ -3,7 +3,6 @@ import pandas as pd from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.platforms import Platform from aind_data_schema.core.data_description import Funding, RawDataDescription from aind_data_schema.core.procedures import NanojectInjection, Perfusion, Procedures, Surgery, ViralMaterial @@ -31,13 +30,13 @@ for session_idx, session in sessions_df.iterrows(): # our data always contains planar optical physiology and behavior videos d = RawDataDescription( - modality=[Modality.POPHYS, Modality.BEHAVIOR_VIDEOS], - platform=Platform.BEHAVIOR, + modalities=[Modality.POPHYS, Modality.BEHAVIOR_VIDEOS], subject_id=str(session["mouse_id"]), creation_time=session["end_time"].to_pydatetime(), institution=Organization.OTHER, experimenters=[experimenter], funding_source=[Funding(funder=Organization.NIMH)], + investigators=[experimenter)], ) # we will store our json files in a directory named after the session diff --git a/examples/ephys_rig.json b/ephys_instrument.json similarity index 93% rename from examples/ephys_rig.json rename to ephys_instrument.json index dd29ab961..96157a28c 100644 --- a/examples/ephys_rig.json +++ b/ephys_instrument.json @@ -1,8 +1,7 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/rig.py", - "schema_version": "1.0.5", - "rig_id": "323_EPHYS1_20231003", - "modification_date": "2023-10-03", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", + "instrument_id": "323_EPHYS1_20231003", "mouse_platform": { "device_type": "Disc", "name": "Running Wheel", @@ -11,7 +10,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "surface_material": null, "date_surface_replaced": null, @@ -22,10 +21,140 @@ "decoder": null, "encoder_firmware": null }, - "stimulus_devices": [], - "cameras": [ + "modification_date": "2023-10-03", + "calibrations": [ + { + "calibration_date": "2023-10-02T10:22:13Z", + "device_name": "Red Laser", + "description": "Laser power calibration", + "input": { + "power percent": [ + 10, + 20, + 40 + ] + }, + "output": { + "power mW": [ + 1, + 3, + 6 + ] + }, + "notes": null + }, + { + "calibration_date": "2023-10-02T10:22:13Z", + "device_name": "Blue Laser", + "description": "Laser power calibration", + "input": { + "power percent": [ + 10, + 20, + 40 + ] + }, + "output": { + "power mW": [ + 1, + 2, + 7 + ] + }, + "notes": null + } + ], + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Extracellular electrophysiology", + "abbreviation": "ecephys" + } + ], + "com_ports": [], + "instrument_type": null, + "manufacturer": null, + "temperature_control": null, + "notes": null, + "connections": [], + "components": [ + { + "name": "Ephys_assemblyA", + "device_type": "Ephys assembly", + "manipulator": { + "device_type": "Manipulator", + "name": "Manipulator 1", + "serial_number": "SN2938", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "probes": [ + { + "device_type": "Ephys probe", + "name": "Probe A", + "serial_number": "9291019", + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "probe_model": "Neuropixels 1.0", + "lasers": [], + "headstage": null + } + ] + }, + { + "name": "Ephys_assemblyB", + "device_type": "Ephys assembly", + "manipulator": { + "device_type": "Manipulator", + "name": "Manipulator B", + "serial_number": "SN2939", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "probes": [ + { + "device_type": "Ephys probe", + "name": "Probe B", + "serial_number": "9291020", + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "probe_model": "Neuropixels 1.0", + "lasers": [], + "headstage": null + } + ] + }, { "name": "Face Camera Assembly", + "device_type": "Camera assembly", "camera_target": "Face side left", "camera": { "device_type": "Detector", @@ -43,7 +172,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -89,7 +218,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": "15", "focal_length_unit": "millimeter", @@ -115,7 +244,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Long pass", "diameter": null, @@ -135,6 +264,7 @@ }, { "name": "Body Camera Assembly", + "device_type": "Camera assembly", "camera_target": "Body", "camera": { "device_type": "Detector", @@ -152,7 +282,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -198,7 +328,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": "15", "focal_length_unit": "millimeter", @@ -224,7 +354,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Long pass", "diameter": null, @@ -241,16 +371,14 @@ "description": "850 nm longpass filter" }, "position": null - } - ], - "enclosure": null, - "ephys_assemblies": [ + }, { - "name": "Ephys_assemblyA", + "name": "Laser_assemblyA", + "device_type": "Laser assembly", "manipulator": { "device_type": "Manipulator", - "name": "Manipulator 1", - "serial_number": "SN2938", + "name": "Manipulator A", + "serial_number": "SN2937", "manufacturer": { "name": "New Scale Technologies", "abbreviation": null, @@ -260,66 +388,199 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null }, - "probes": [ + "lasers": [ { - "device_type": "Ephys probe", - "name": "Probe A", - "serial_number": "9291019", - "manufacturer": null, + "device_type": "Laser", + "name": "Red Laser", + "serial_number": null, + "manufacturer": { + "name": "Oxxius", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, - "probe_model": "Neuropixels 1.0", - "lasers": [], - "headstage": null + "wavelength": 473, + "wavelength_unit": "nanometer", + "maximum_power": null, + "power_unit": "milliwatt", + "coupling": null, + "coupling_efficiency": null, + "coupling_efficiency_unit": "percent", + "item_number": null + }, + { + "device_type": "Laser", + "name": "Blue Laser", + "serial_number": null, + "manufacturer": { + "name": "Oxxius", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "wavelength": 638, + "wavelength_unit": "nanometer", + "maximum_power": null, + "power_unit": "milliwatt", + "coupling": null, + "coupling_efficiency": null, + "coupling_efficiency_unit": "percent", + "item_number": null } - ] - }, - { - "name": "Ephys_assemblyB", - "manipulator": { - "device_type": "Manipulator", - "name": "Manipulator B", - "serial_number": "SN2939", + ], + "collimator": { + "device_type": "device", + "name": "Collimator A", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "fiber": { + "device_type": "Patch", + "name": "Bundle Branching Fiber-optic Patch Cord", + "serial_number": null, "manufacturer": { - "name": "New Scale Technologies", + "name": "Doric", "abbreviation": null, - "registry": null, - "registry_identifier": null + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "059n53q30" }, - "model": null, + "model": "BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, - "notes": null + "additional_settings": null, + "notes": null, + "core_diameter": "200", + "numerical_aperture": "0.37", + "photobleaching_date": null + } + }, + { + "device_type": "Neuropixels basestation", + "name": "Basestation Slot 3", + "serial_number": null, + "manufacturer": { + "name": "Interuniversity Microelectronics Center", + "abbreviation": "IMEC", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "02kcbn207" }, - "probes": [ + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "PXI", + "computer_name": "W10DT72942", + "channels": [], + "firmware_version": null, + "hardware_version": null, + "basestation_firmware_version": "2.019", + "bsc_firmware_version": "2.199", + "slot": 3, + "ports": [ { - "device_type": "Ephys probe", - "name": "Probe B", - "serial_number": "9291020", - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "probe_model": "Neuropixels 1.0", - "lasers": [], - "headstage": null + "index": 1, + "probes": [ + "Probe A" + ] + }, + { + "index": 2, + "probes": [ + "Probe B" + ] } ] - } - ], - "fiber_assemblies": [], - "stick_microscopes": [ + }, + { + "device_type": "Harp device", + "name": "Harp Behavior", + "serial_number": null, + "manufacturer": { + "name": "Open Ephys Production Site", + "abbreviation": "OEPS", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "007rkz355" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "USB", + "computer_name": "W10DT72941", + "channels": [ + { + "channel_name": "DO0", + "device_name": "Face Camera", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DO1", + "device_name": "Body Camera", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "AI0", + "device_name": "Running Wheel", + "channel_type": "Analog Input", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + } + ], + "firmware_version": null, + "hardware_version": null, + "harp_device_type": { + "whoami": 1216, + "name": "Behavior" + }, + "core_version": "2.1", + "tag_version": null, + "is_clock_generator": false + }, { "name": "Stick_assembly_1", + "device_type": "Camera assembly", "camera_target": "Brain surface", "camera": { "device_type": "Detector", @@ -337,7 +598,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -383,7 +644,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": null, "focal_length_unit": null, @@ -398,6 +659,7 @@ }, { "name": "Stick_assembly_2", + "device_type": "Camera assembly", "camera_target": "Brain surface", "camera": { "device_type": "Detector", @@ -415,7 +677,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -461,7 +723,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": null, "focal_length_unit": null, @@ -476,6 +738,7 @@ }, { "name": "Stick_assembly_3", + "device_type": "Camera assembly", "camera_target": "Brain surface", "camera": { "device_type": "Detector", @@ -493,7 +756,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -539,7 +802,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": null, "focal_length_unit": null, @@ -554,6 +817,7 @@ }, { "name": "Stick_assembly_4", + "device_type": "Camera assembly", "camera_target": "Brain surface", "camera": { "device_type": "Detector", @@ -571,7 +835,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -617,7 +881,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": null, "focal_length_unit": null, @@ -630,276 +894,5 @@ "filter": null, "position": null } - ], - "laser_assemblies": [ - { - "name": "Laser_assemblyA", - "manipulator": { - "device_type": "Manipulator", - "name": "Manipulator A", - "serial_number": "SN2937", - "manufacturer": { - "name": "New Scale Technologies", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lasers": [ - { - "device_type": "Laser", - "name": "Red Laser", - "serial_number": null, - "manufacturer": { - "name": "Oxxius", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "wavelength": 473, - "wavelength_unit": "nanometer", - "maximum_power": null, - "power_unit": "milliwatt", - "coupling": null, - "coupling_efficiency": null, - "coupling_efficiency_unit": "percent", - "item_number": null - }, - { - "device_type": "Laser", - "name": "Blue Laser", - "serial_number": null, - "manufacturer": { - "name": "Oxxius", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "wavelength": 638, - "wavelength_unit": "nanometer", - "maximum_power": null, - "power_unit": "milliwatt", - "coupling": null, - "coupling_efficiency": null, - "coupling_efficiency_unit": "percent", - "item_number": null - } - ], - "collimator": { - "device_type": "Collimator", - "name": "Collimator A", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "fiber": { - "device_type": "Patch", - "name": "Bundle Branching Fiber-optic Patch Cord", - "serial_number": null, - "manufacturer": { - "name": "Doric", - "abbreviation": null, - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "059n53q30" - }, - "model": "BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "core_diameter": "200", - "numerical_aperture": "0.37", - "photobleaching_date": null - } - } - ], - "patch_cords": [], - "light_sources": [], - "detectors": [], - "objectives": [], - "filters": [], - "lenses": [], - "digital_micromirror_devices": [], - "polygonal_scanners": [], - "pockels_cells": [], - "additional_devices": [], - "daqs": [ - { - "device_type": "Neuropixels basestation", - "name": "Basestation Slot 3", - "serial_number": null, - "manufacturer": { - "name": "Interuniversity Microelectronics Center", - "abbreviation": "IMEC", - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "02kcbn207" - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "data_interface": "PXI", - "computer_name": "W10DT72942", - "channels": [], - "firmware_version": null, - "hardware_version": null, - "basestation_firmware_version": "2.019", - "bsc_firmware_version": "2.199", - "slot": 3, - "ports": [ - { - "index": 1, - "probes": [ - "Probe A" - ] - }, - { - "index": 2, - "probes": [ - "Probe B" - ] - } - ] - }, - { - "device_type": "Harp device", - "name": "Harp Behavior", - "serial_number": null, - "manufacturer": { - "name": "Open Ephys Production Site", - "abbreviation": "OEPS", - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "007rkz355" - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "data_interface": "USB", - "computer_name": "W10DT72941", - "channels": [ - { - "channel_name": "DO0", - "device_name": "Face Camera", - "channel_type": "Digital Output", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "DO1", - "device_name": "Body Camera", - "channel_type": "Digital Output", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "AI0", - "device_name": "Running Wheel", - "channel_type": "Analog Input", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - } - ], - "firmware_version": null, - "hardware_version": null, - "harp_device_type": { - "whoami": 1216, - "name": "Behavior" - }, - "core_version": "2.1", - "tag_version": null, - "is_clock_generator": false - } - ], - "calibrations": [ - { - "calibration_date": "2023-10-02T10:22:13Z", - "device_name": "Red Laser", - "description": "Laser power calibration", - "input": { - "power percent": [ - 10, - 20, - 40 - ] - }, - "output": { - "power mW": [ - 1, - 3, - 6 - ] - }, - "notes": null - }, - { - "calibration_date": "2023-10-02T10:22:13Z", - "device_name": "Blue Laser", - "description": "Laser power calibration", - "input": { - "power percent": [ - 10, - 20, - 40 - ] - }, - "output": { - "power mW": [ - 1, - 2, - 7 - ] - }, - "notes": null - } - ], - "ccf_coordinate_transform": null, - "origin": null, - "rig_axes": null, - "modalities": [ - { - "name": "Extracellular electrophysiology", - "abbreviation": "ecephys" - } - ], - "notes": null + ] } \ No newline at end of file diff --git a/examples/aibs_smartspim_instrument.json b/examples/aibs_smartspim_instrument.json index a799ec7f6..f950cb0be 100644 --- a/examples/aibs_smartspim_instrument.json +++ b/examples/aibs_smartspim_instrument.json @@ -1,8 +1,33 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/instrument.py", - "schema_version": "1.0.5", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", "instrument_id": "440_SmartSPIM2_20231004", + "mouse_platform": null, "modification_date": "2023-10-04", + "calibrations": null, + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Selective plane illumination microscopy", + "abbreviation": "SPIM" + } + ], + "com_ports": [ + { + "hardware_name": "Laser Launch", + "com_port": "COM3" + }, + { + "hardware_name": "ASI Tiger", + "com_port": "COM5" + }, + { + "hardware_name": "MightyZap", + "com_port": "COM4" + } + ], "instrument_type": "SmartSPIM", "manufacturer": { "name": "LifeCanvas", @@ -11,30 +36,9 @@ "registry_identifier": null }, "temperature_control": false, - "optical_tables": [ - { - "device_type": "Optical table", - "name": "Table", - "serial_number": null, - "manufacturer": { - "name": "Technical Manufacturing Corporation", - "abbreviation": "TMC", - "registry": null, - "registry_identifier": null - }, - "model": "CleanTop", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "length": "35", - "width": "29", - "table_size_unit": "inch", - "vibration_control": true - } - ], - "enclosure": null, - "objectives": [ + "notes": null, + "connections": [], + "components": [ { "device_type": "Objective", "name": "TLX Objective", @@ -51,15 +55,13 @@ "model": "TL4X-SAP", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", "numerical_aperture": "0.2", "magnification": "3.6", "immersion": "multi", "objective_type": null - } - ], - "detectors": [ + }, { "device_type": "Detector", "name": "Camera 1", @@ -76,7 +78,7 @@ "model": "C14440-20UP", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -105,9 +107,7 @@ "recording_software": null, "driver": null, "driver_version": null - } - ], - "light_sources": [ + }, { "device_type": "Laser", "name": "Ex_488", @@ -121,7 +121,7 @@ "model": "Stradus", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "All lasers controlled via Vortran VersaLase System", "wavelength": 488, "wavelength_unit": "nanometer", @@ -148,7 +148,7 @@ "model": "Obis", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "All lasers controlled via Vortran VersaLase System", "wavelength": 561, "wavelength_unit": "nanometer", @@ -172,7 +172,7 @@ "model": "Stradus", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "All lasers controlled via Vortran VersaLase System", "wavelength": 647, "wavelength_unit": "nanometer", @@ -182,99 +182,7 @@ "coupling_efficiency": null, "coupling_efficiency_unit": "percent", "item_number": null - } - ], - "lenses": [], - "fluorescence_filters": [ - { - "device_type": "Filter", - "name": "Em_525", - "serial_number": null, - "manufacturer": { - "name": "Semrock", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": "FF03-525/50-25", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "filter_type": "Band pass", - "diameter": "25", - "width": null, - "height": null, - "size_unit": "millimeter", - "thickness": "2.0", - "thickness_unit": "millimeter", - "filter_wheel_index": 0, - "cut_off_wavelength": null, - "cut_on_wavelength": null, - "center_wavelength": null, - "wavelength_unit": "nanometer", - "description": null - }, - { - "device_type": "Filter", - "name": "Em_600", - "serial_number": null, - "manufacturer": { - "name": "Semrock", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": "FF01-600/52-25", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "filter_type": "Band pass", - "diameter": "25", - "width": null, - "height": null, - "size_unit": "millimeter", - "thickness": "2.0", - "thickness_unit": "millimeter", - "filter_wheel_index": 1, - "cut_off_wavelength": null, - "cut_on_wavelength": null, - "center_wavelength": null, - "wavelength_unit": "nanometer", - "description": null }, - { - "device_type": "Filter", - "name": "Em_690", - "serial_number": null, - "manufacturer": { - "name": "Chroma", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": "ET690/50m", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "filter_type": "Band pass", - "diameter": "25", - "width": null, - "height": null, - "size_unit": "millimeter", - "thickness": "2.0", - "thickness_unit": "millimeter", - "filter_wheel_index": 2, - "cut_off_wavelength": null, - "cut_on_wavelength": null, - "center_wavelength": null, - "wavelength_unit": "nanometer", - "description": null - } - ], - "motorized_stages": [ { "device_type": "Motorized stage", "name": "Focus stage", @@ -288,7 +196,7 @@ "model": "LS-100", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "100", "travel_unit": "millimeter", @@ -307,7 +215,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -326,7 +234,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -345,7 +253,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -364,16 +272,14 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", "firmware": null - } - ], - "scanning_stages": [ + }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage Z", "serial_number": null, "manufacturer": { @@ -385,7 +291,7 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", @@ -394,7 +300,7 @@ "stage_axis_name": "Z" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage X", "serial_number": null, "manufacturer": { @@ -406,7 +312,7 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", @@ -415,7 +321,7 @@ "stage_axis_name": "X" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage Y", "serial_number": null, "manufacturer": { @@ -427,16 +333,121 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", "firmware": null, "stage_axis_direction": "Perpendicular axis", "stage_axis_name": "Y" - } - ], - "additional_devices": [ + }, + { + "device_type": "Optical table", + "name": "Table", + "serial_number": null, + "manufacturer": { + "name": "Technical Manufacturing Corporation", + "abbreviation": "TMC", + "registry": null, + "registry_identifier": null + }, + "model": "CleanTop", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "length": "35", + "width": "29", + "table_size_unit": "inch", + "vibration_control": true + }, + { + "device_type": "Filter", + "name": "Em_525", + "serial_number": null, + "manufacturer": { + "name": "Semrock", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF03-525/50-25", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "filter_type": "Band pass", + "diameter": "25", + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": "2.0", + "thickness_unit": "millimeter", + "filter_wheel_index": 0, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Em_600", + "serial_number": null, + "manufacturer": { + "name": "Semrock", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "FF01-600/52-25", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "filter_type": "Band pass", + "diameter": "25", + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": "2.0", + "thickness_unit": "millimeter", + "filter_wheel_index": 1, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, + { + "device_type": "Filter", + "name": "Em_690", + "serial_number": null, + "manufacturer": { + "name": "Chroma", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": "ET690/50m", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "filter_type": "Band pass", + "diameter": "25", + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": "2.0", + "thickness_unit": "millimeter", + "filter_wheel_index": 2, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": null + }, { "device_type": "Additional imaging device", "name": "Lens 1", @@ -450,7 +461,7 @@ "model": "EL-16-40-TC", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Tunable lens" }, @@ -467,7 +478,7 @@ "model": "EL-16-40-TC", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Tunable lens" }, @@ -484,27 +495,9 @@ "model": "Large-uncoated-glass", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Sample Chamber" } - ], - "calibration_date": null, - "calibration_data": null, - "com_ports": [ - { - "hardware_name": "Laser Launch", - "com_port": "COM3" - }, - { - "hardware_name": "ASI Tiger", - "com_port": "COM5" - }, - { - "hardware_name": "MightyZap", - "com_port": "COM4" - } - ], - "daqs": [], - "notes": null + ] } \ No newline at end of file diff --git a/examples/aibs_smartspim_instrument.py b/examples/aibs_smartspim_instrument.py index 8fb42aca9..455ac18c8 100644 --- a/examples/aibs_smartspim_instrument.py +++ b/examples/aibs_smartspim_instrument.py @@ -9,145 +9,204 @@ AdditionalImagingDevice, Detector, Filter, + ImagingInstrumentType, Laser, MotorizedStage, Objective, OpticalTable, ScanningStage, ) +from aind_data_schema_models.modalities import Modality from aind_data_schema.core.instrument import Com, Instrument +objective = Objective( + name="TLX Objective", + numerical_aperture=0.2, + magnification=3.6, + immersion="multi", + manufacturer=Organization.THORLABS, + model="TL4X-SAP", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", +) +camera1 = Detector( + name="Camera 1", + detector_type="Camera", + data_interface="USB", + cooling="Air", + manufacturer=Organization.HAMAMATSU, + model="C14440-20UP", + serial_number="001107", +) +laser1 = Laser( + name="Ex_488", + coupling="Single-mode fiber", + wavelength=488, + maximum_power=150, + serial_number="VL01222A11", + manufacturer=Organization.VORTRAN, + model="Stradus", + notes="All lasers controlled via Vortran VersaLase System", +) +laser2 = Laser( + name="Ex_561", + coupling="Single-mode fiber", + wavelength=561, + maximum_power=150, + serial_number="417927", + manufacturer=Organization.COHERENT_SCIENTIFIC, + model="Obis", + notes="All lasers controlled via Vortran VersaLase System", +) +laser3 = Laser( + name="Ex_647", + coupling="Single-mode fiber", + wavelength=647, + maximum_power=160, + serial_number="VL01222A10", + manufacturer=Organization.VORTRAN, + model="Stradus", + notes="All lasers controlled via Vortran VersaLase System", +) +stage0 = MotorizedStage( + name="Focus stage", + model="LS-100", + manufacturer=Organization.ASI, + travel=100, +) +stage1 = MotorizedStage( + name="Cylindrical lens #1", + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + travel=41, +) +stage2 = MotorizedStage( + name="Cylindrical lens #2", + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + travel=41, +) +stage3 = MotorizedStage( + name="Cylindrical lens #3", + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + travel=41, +) +stage4 = MotorizedStage( + name="Cylindrical lens #4", + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + travel=41, +) +scan_stage1 = ScanningStage( + name="Sample stage Z", + model="LS-50", + manufacturer=Organization.ASI, + stage_axis_direction="Detection axis", + stage_axis_name="Z", + travel=50, +) +scan_stage2 = ScanningStage( + name="Sample stage X", + model="LS-50", + manufacturer=Organization.ASI, + stage_axis_direction="Illumination axis", + stage_axis_name="X", + travel=50, +) +scan_stage3 = ScanningStage( + name="Sample stage Y", + model="LS-50", + manufacturer=Organization.ASI, + stage_axis_direction="Perpendicular axis", + stage_axis_name="Y", + travel=50, +) +table = OpticalTable( + name="Table", + model="CleanTop", # model="VIS2424-IG2-125A", # ~3 months + length=35, # length=24, + width=29, # width=24, + vibration_control=True, + manufacturer=Organization.TMC, +) +filter0 = Filter( + name="Em_525", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF03-525/50-25", + filter_wheel_index=0, +) +filter1 = Filter( + name="Em_600", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-600/52-25", + filter_wheel_index=1, +) +filter2 = Filter( + name="Em_690", + filter_type="Band pass", + manufacturer=Organization.CHROMA, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="ET690/50m", + filter_wheel_index=2, +) +lens1 = AdditionalImagingDevice( + name="Lens 1", + imaging_device_type="Tunable lens", + manufacturer=Organization.OPTOTUNE, + model="EL-16-40-TC", +) +lens2 = AdditionalImagingDevice( + name="Lens 2", + imaging_device_type="Tunable lens", + manufacturer=Organization.OPTOTUNE, + model="EL-16-40-TC", +) +lens3 = AdditionalImagingDevice( + name="Sample chamber", + imaging_device_type="Sample Chamber", + manufacturer=Organization.LIFECANVAS, + model="Large-uncoated-glass", +) + inst = Instrument( instrument_id="440_SmartSPIM2_20231004", modification_date=datetime.date(2023, 10, 4), - instrument_type="SmartSPIM", + instrument_type=ImagingInstrumentType.SMARTSPIM, + modalities=[Modality.SPIM], manufacturer=Organization.LIFECANVAS, - objectives=[ - Objective( - name="TLX Objective", - numerical_aperture=0.2, - magnification=3.6, - immersion="multi", - manufacturer=Organization.THORLABS, - model="TL4X-SAP", - notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", - ), - ], - detectors=[ - Detector( - name="Camera 1", - detector_type="Camera", - data_interface="USB", - cooling="Air", - manufacturer=Organization.HAMAMATSU, - model="C14440-20UP", - serial_number="001107", - ), - ], - light_sources=[ - Laser( - name="Ex_488", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=488, - maximum_power=150, - serial_number="VL01222A11", - manufacturer=Organization.VORTRAN, - model="Stradus", - notes="All lasers controlled via Vortran VersaLase System", - ), - Laser( - name="Ex_561", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=561, - maximum_power=150, - serial_number="417927", - manufacturer=Organization.COHERENT_SCIENTIFIC, - model="Obis", - notes="All lasers controlled via Vortran VersaLase System", - ), - Laser( - name="Ex_647", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=647, - maximum_power=160, - serial_number="VL01222A10", - manufacturer=Organization.VORTRAN, - model="Stradus", - notes="All lasers controlled via Vortran VersaLase System", - ), - ], - motorized_stages=[ - MotorizedStage( - name="Focus stage", - model="LS-100", - manufacturer=Organization.ASI, - travel=100, - ), - MotorizedStage( - name="Cylindrical lens #1", - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - travel=41, - ), - MotorizedStage( - name="Cylindrical lens #2", - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - travel=41, - ), - MotorizedStage( - name="Cylindrical lens #3", - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - travel=41, - ), - MotorizedStage( - name="Cylindrical lens #4", - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - travel=41, - ), - ], - scanning_stages=[ - ScanningStage( - name="Sample stage Z", - model="LS-50", - manufacturer=Organization.ASI, - stage_axis_direction="Detection axis", - stage_axis_name="Z", - travel=50, - ), - ScanningStage( - name="Sample stage X", - model="LS-50", - manufacturer=Organization.ASI, - stage_axis_direction="Illumination axis", - stage_axis_name="X", - travel=50, - ), - ScanningStage( - name="Sample stage Y", - model="LS-50", - manufacturer=Organization.ASI, - stage_axis_direction="Perpendicular axis", - stage_axis_name="Y", - travel=50, - ), - ], - optical_tables=[ - OpticalTable( - name="Table", - model="CleanTop", # model="VIS2424-IG2-125A", # ~3 months - length=35, # length=24, - width=29, # width=24, - vibration_control=True, - manufacturer=Organization.TMC, - ) - ], temperature_control=False, + components=[ + objective, + camera1, + laser1, + laser2, + laser3, + stage0, + stage1, + stage2, + stage3, + stage4, + scan_stage1, + scan_stage2, + scan_stage3, + table, + filter0, + filter1, + filter2, + lens1, + lens2, + lens3, + ], com_ports=[ Com( hardware_name="Laser Launch", @@ -159,58 +218,6 @@ ), Com(hardware_name="MightyZap", com_port="COM4"), ], - fluorescence_filters=[ - Filter( - name="Em_525", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF03-525/50-25", - filter_wheel_index=0, - ), - Filter( - name="Em_600", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF01-600/52-25", - filter_wheel_index=1, - ), - Filter( - name="Em_690", - filter_type="Band pass", - manufacturer=Organization.CHROMA, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="ET690/50m", - filter_wheel_index=2, - ), - ], - additional_devices=[ - AdditionalImagingDevice( - name="Lens 1", - imaging_device_type="Tunable lens", - manufacturer=Organization.OPTOTUNE, - model="EL-16-40-TC", - ), - AdditionalImagingDevice( - name="Lens 2", - imaging_device_type="Tunable lens", - manufacturer=Organization.OPTOTUNE, - model="EL-16-40-TC", - ), - AdditionalImagingDevice( - name="Sample chamber", - imaging_device_type="Sample Chamber", - manufacturer=Organization.LIFECANVAS, - model="Large-uncoated-glass", - ), - ], ) serialized = inst.model_dump_json() deserialized = Instrument.model_validate_json(serialized) diff --git a/examples/aibs_smartspim_procedures.json b/examples/aibs_smartspim_procedures.json index 316ad84f3..879e2c304 100644 --- a/examples/aibs_smartspim_procedures.json +++ b/examples/aibs_smartspim_procedures.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/procedures.py", - "schema_version": "1.2.6", + "schema_version": "2.0.0", "subject_id": "651286", "subject_procedures": [ { diff --git a/examples/aind_smartspim_instrument.json b/examples/aind_smartspim_instrument.json index 6eb6dd442..51d739589 100644 --- a/examples/aind_smartspim_instrument.json +++ b/examples/aind_smartspim_instrument.json @@ -1,8 +1,33 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/instrument.py", - "schema_version": "1.0.5", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", "instrument_id": "440_SmartSPIM1_20231004", + "mouse_platform": null, "modification_date": "2023-10-04", + "calibrations": null, + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Selective plane illumination microscopy", + "abbreviation": "SPIM" + } + ], + "com_ports": [ + { + "hardware_name": "Laser Launch", + "com_port": "COM4" + }, + { + "hardware_name": "ASI Tiger", + "com_port": "COM3" + }, + { + "hardware_name": "MightyZap", + "com_port": "COM9" + } + ], "instrument_type": "SmartSPIM", "manufacturer": { "name": "LifeCanvas", @@ -11,33 +36,9 @@ "registry_identifier": null }, "temperature_control": false, - "optical_tables": [ - { - "device_type": "Optical table", - "name": "Main optical table", - "serial_number": "Unknown", - "manufacturer": { - "name": "MKS Newport", - "abbreviation": null, - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "00k17f049" - }, - "model": "VIS2424-IG2-125A", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "length": "36", - "width": "48", - "table_size_unit": "inch", - "vibration_control": false - } - ], - "enclosure": null, - "objectives": [ + "notes": null, + "connections": [], + "components": [ { "device_type": "Objective", "name": "TLX Objective 1", @@ -51,7 +52,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", "numerical_aperture": "0.2", "magnification": "3.6", @@ -71,15 +72,13 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Thorlabs TL2X-SAP with LifeCanvas dipping cap and correction optics.", "numerical_aperture": "0.12", "magnification": "1.625", "immersion": "multi", "objective_type": null - } - ], - "detectors": [ + }, { "device_type": "Detector", "name": "Camera 1", @@ -96,7 +95,7 @@ "model": "C14440-20UP", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -125,9 +124,7 @@ "recording_software": null, "driver": null, "driver_version": null - } - ], - "light_sources": [ + }, { "device_type": "Laser", "name": "Ex_445", @@ -141,7 +138,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 445, "wavelength_unit": "nanometer", @@ -165,7 +162,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 488, "wavelength_unit": "nanometer", @@ -189,7 +186,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 561, "wavelength_unit": "nanometer", @@ -213,7 +210,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 594, "wavelength_unit": "nanometer", @@ -237,7 +234,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 639, "wavelength_unit": "nanometer", @@ -261,7 +258,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 690, "wavelength_unit": "nanometer", @@ -271,10 +268,7 @@ "coupling_efficiency": null, "coupling_efficiency_unit": "percent", "item_number": null - } - ], - "lenses": [], - "fluorescence_filters": [ + }, { "device_type": "Filter", "name": "Em_469", @@ -288,7 +282,7 @@ "model": "FF01-469/35-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -317,7 +311,7 @@ "model": "FF01-525/45-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -346,7 +340,7 @@ "model": "FF01-593/40-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -375,7 +369,7 @@ "model": "FF01-624/40-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -404,7 +398,7 @@ "model": "ET667/30m", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -436,7 +430,7 @@ "model": "FELH0700", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Long pass", "diameter": "25", @@ -451,9 +445,7 @@ "center_wavelength": null, "wavelength_unit": "nanometer", "description": null - } - ], - "motorized_stages": [ + }, { "device_type": "Motorized stage", "name": "Focus stage", @@ -467,7 +459,7 @@ "model": "LS-100", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "100", "travel_unit": "millimeter", @@ -486,7 +478,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -505,7 +497,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -524,7 +516,7 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", @@ -543,16 +535,14 @@ "model": "L12-20F-4", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "41", "travel_unit": "millimeter", "firmware": null - } - ], - "scanning_stages": [ + }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage Z", "serial_number": "Unknown-2", "manufacturer": { @@ -564,7 +554,7 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", @@ -573,7 +563,7 @@ "stage_axis_name": "Z" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage X", "serial_number": "Unknown-3", "manufacturer": { @@ -585,7 +575,7 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", @@ -594,7 +584,7 @@ "stage_axis_name": "X" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "Sample stage Y", "serial_number": "Unknown-4", "manufacturer": { @@ -606,32 +596,36 @@ "model": "LS-50", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "50", "travel_unit": "millimeter", "firmware": null, "stage_axis_direction": "Perpendicular axis", "stage_axis_name": "Y" - } - ], - "additional_devices": [], - "calibration_date": null, - "calibration_data": null, - "com_ports": [ - { - "hardware_name": "Laser Launch", - "com_port": "COM4" }, { - "hardware_name": "ASI Tiger", - "com_port": "COM3" - }, - { - "hardware_name": "MightyZap", - "com_port": "COM9" + "device_type": "Optical table", + "name": "Main optical table", + "serial_number": "Unknown", + "manufacturer": { + "name": "MKS Newport", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "00k17f049" + }, + "model": "VIS2424-IG2-125A", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "length": "36", + "width": "48", + "table_size_unit": "inch", + "vibration_control": false } - ], - "daqs": [], - "notes": null + ] } \ No newline at end of file diff --git a/examples/aind_smartspim_instrument.py b/examples/aind_smartspim_instrument.py index 104736892..b9f7667af 100644 --- a/examples/aind_smartspim_instrument.py +++ b/examples/aind_smartspim_instrument.py @@ -1,249 +1,279 @@ """ example SmartSPIM instrument """ -import datetime +from datetime import date from aind_data_schema_models.organizations import Organization from aind_data_schema_models.units import SizeUnit -from aind_data_schema.components.devices import Filter, Laser, MotorizedStage, OpticalTable, ScanningStage +from aind_data_schema.components.devices import ( + Filter, + ImagingInstrumentType, + Laser, + MotorizedStage, + OpticalTable, + ScanningStage, +) from aind_data_schema.core.instrument import Com, Detector, Instrument, Objective +from aind_data_schema_models.modalities import Modality + +objective_1 = Objective( + name="TLX Objective 1", + numerical_aperture=0.2, + magnification=3.6, + manufacturer=Organization.LIFECANVAS, + immersion="multi", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", + serial_number="Unknown-1", +) + +objective_2 = Objective( + name="TLX Objective 2", + numerical_aperture=0.12, + magnification=1.625, + manufacturer=Organization.LIFECANVAS, + immersion="multi", + notes="Thorlabs TL2X-SAP with LifeCanvas dipping cap and correction optics.", + serial_number="Unknown-2", +) + +detector_1 = Detector( + detector_type="Camera", + data_interface="USB", + name="Camera 1", + cooling="Air", + manufacturer=Organization.HAMAMATSU, + model="C14440-20UP", + serial_number="001284", +) + +laser_1 = Laser( + name="Ex_445", + coupling="Single-mode fiber", + wavelength=445, + maximum_power=200, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_2 = Laser( + name="Ex_488", + coupling="Single-mode fiber", + wavelength=488, + maximum_power=150, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_3 = Laser( + name="Ex_561", + coupling="Single-mode fiber", + wavelength=561, + maximum_power=150, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_4 = Laser( + name="Ex_594", + coupling="Single-mode fiber", + wavelength=594, + maximum_power=100, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_5 = Laser( + name="Ex_639", + coupling="Single-mode fiber", + wavelength=639, + maximum_power=160, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_6 = Laser( + name="Ex_690", + coupling="Single-mode fiber", + wavelength=690, + maximum_power=160, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +filter_1 = Filter( + name="Em_469", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-469/35-25", + filter_wheel_index=0, + serial_number="Unknown-0", +) + +filter_2 = Filter( + name="Em_525", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-525/45-25", + filter_wheel_index=1, + serial_number="Unknown-1", +) + +filter_3 = Filter( + name="Em_593", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-593/40-25", + filter_wheel_index=2, + serial_number="Unknown-2", +) + +filter_4 = Filter( + name="Em_624", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-624/40-25", + filter_wheel_index=3, + serial_number="Unknown-3", +) + +filter_5 = Filter( + name="Em_667", + filter_type="Band pass", + manufacturer=Organization.CHROMA, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="ET667/30m", + filter_wheel_index=4, + serial_number="Unknown-4", +) + +filter_6 = Filter( + name="Em_700", + filter_type="Long pass", + manufacturer=Organization.THORLABS, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FELH0700", + filter_wheel_index=5, + serial_number="Unknown-5", +) + +motorized_stage_1 = MotorizedStage( + model="LS-100", + manufacturer=Organization.ASI, + serial_number="Unknown-1", + travel=100, + name="Focus stage", +) + +motorized_stage_2 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-5", + travel=41, + name="Cylindrical lens #1", +) + +motorized_stage_3 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-6", + travel=41, + name="Cylindrical lens #2", +) + +motorized_stage_4 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-7", + travel=41, + name="Cylindrical lens #3", +) + +motorized_stage_5 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-8", + travel=41, + name="Cylindrical lens #4", +) + +scanning_stage_1 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-2", + stage_axis_direction="Detection axis", + stage_axis_name="Z", + travel=50, + name="Sample stage Z", +) + +scanning_stage_2 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-3", + stage_axis_direction="Illumination axis", + stage_axis_name="X", + travel=50, + name="Sample stage X", +) + +scanning_stage_3 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-4", + stage_axis_direction="Perpendicular axis", + stage_axis_name="Y", + travel=50, + name="Sample stage Y", +) + +optical_table_1 = OpticalTable( + name="Main optical table", + length=36, + width=48, + vibration_control=False, + model="VIS2424-IG2-125A", + manufacturer=Organization.MKS_NEWPORT, + serial_number="Unknown", +) + +objectives = [objective_1, objective_2] +detectors = [detector_1] +lasers = [laser_1, laser_2, laser_3, laser_4, laser_5, laser_6] +fluorescence_filters = [filter_1, filter_2, filter_3, filter_4, filter_5, filter_6] +motorized_stages = [motorized_stage_1, motorized_stage_2, motorized_stage_3, motorized_stage_4, motorized_stage_5] +scanning_stages = [scanning_stage_1, scanning_stage_2, scanning_stage_3] +optical_tables = [optical_table_1] inst = Instrument( instrument_id="440_SmartSPIM1_20231004", - instrument_type="SmartSPIM", + instrument_type=ImagingInstrumentType.SMARTSPIM, manufacturer=Organization.LIFECANVAS, - modification_date=datetime.date(2023, 10, 4), - objectives=[ - Objective( - name="TLX Objective 1", - numerical_aperture=0.2, - magnification=3.6, - manufacturer=Organization.LIFECANVAS, - immersion="multi", - notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", - serial_number="Unknown-1", - ), - Objective( - name="TLX Objective 2", - numerical_aperture=0.12, - magnification=1.625, - manufacturer=Organization.LIFECANVAS, - immersion="multi", - notes="Thorlabs TL2X-SAP with LifeCanvas dipping cap and correction optics.", - serial_number="Unknown-2", - ), - ], - detectors=[ - Detector( - detector_type="Camera", - data_interface="USB", - name="Camera 1", - cooling="Air", - manufacturer=Organization.HAMAMATSU, - model="C14440-20UP", - serial_number="001284", - ), - ], - light_sources=[ - Laser( - name="Ex_445", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=445, - maximum_power=200, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - Laser( - name="Ex_488", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=488, - maximum_power=150, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - Laser( - name="Ex_561", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=561, - maximum_power=150, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - Laser( - name="Ex_594", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=594, - maximum_power=100, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - Laser( - name="Ex_639", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=639, - maximum_power=160, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - Laser( - name="Ex_690", - device_type="Laser", - coupling="Single-mode fiber", - wavelength=690, - maximum_power=160, - serial_number="VL08223M03", - manufacturer=Organization.VORTRAN, - ), - ], - fluorescence_filters=[ - Filter( - name="Em_469", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF01-469/35-25", - filter_wheel_index=0, - serial_number="Unknown-0", - ), - Filter( - name="Em_525", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF01-525/45-25", - filter_wheel_index=1, - serial_number="Unknown-1", - ), - Filter( - name="Em_593", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF01-593/40-25", - filter_wheel_index=2, - serial_number="Unknown-2", - ), - Filter( - name="Em_624", - filter_type="Band pass", - manufacturer=Organization.SEMROCK, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FF01-624/40-25", - filter_wheel_index=3, - serial_number="Unknown-3", - ), - Filter( - name="Em_667", - filter_type="Band pass", - manufacturer=Organization.CHROMA, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="ET667/30m", - filter_wheel_index=4, - serial_number="Unknown-4", - ), - Filter( - name="Em_700", - filter_type="Long pass", - manufacturer=Organization.THORLABS, - diameter=25, - thickness=2.0, - thickness_unit=SizeUnit.MM, - model="FELH0700", - filter_wheel_index=5, - serial_number="Unknown-5", - ), - ], - motorized_stages=[ - MotorizedStage( - model="LS-100", - manufacturer=Organization.ASI, - serial_number="Unknown-1", - travel=100, - name="Focus stage", - ), - MotorizedStage( - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - serial_number="Unknown-5", - travel=41, - name="Cylindrical lens #1", - ), - MotorizedStage( - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - serial_number="Unknown-6", - travel=41, - name="Cylindrical lens #2", - ), - MotorizedStage( - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - serial_number="Unknown-7", - travel=41, - name="Cylindrical lens #3", - ), - MotorizedStage( - model="L12-20F-4", - manufacturer=Organization.IR_ROBOT_CO, - serial_number="Unknown-8", - travel=41, - name="Cylindrical lens #4", - ), - ], - scanning_stages=[ - ScanningStage( - model="LS-50", - manufacturer=Organization.ASI, - serial_number="Unknown-2", - stage_axis_direction="Detection axis", - stage_axis_name="Z", - travel=50, - name="Sample stage Z", - ), - ScanningStage( - model="LS-50", - manufacturer=Organization.ASI, - serial_number="Unknown-3", - stage_axis_direction="Illumination axis", - stage_axis_name="X", - travel=50, - name="Sample stage X", - ), - ScanningStage( - model="LS-50", - manufacturer=Organization.ASI, - serial_number="Unknown-4", - stage_axis_direction="Perpendicular axis", - stage_axis_name="Y", - travel=50, - name="Sample stage Y", - ), - ], - optical_tables=[ - OpticalTable( - name="Main optical table", - length=36, - width=48, - vibration_control=False, - model="VIS2424-IG2-125A", - manufacturer=Organization.MKS_NEWPORT, - serial_number="Unknown", - ) + modification_date=date(2023, 10, 4), + modalities=[Modality.SPIM], + components=[ + *objectives, + *detectors, + *lasers, + *fluorescence_filters, + *motorized_stages, + *scanning_stages, + *optical_tables, ], com_ports=[ Com(hardware_name="Laser Launch", com_port="COM4"), diff --git a/examples/bergamo_ophys_session.json b/examples/bergamo_ophys_session.json index 7fdff4057..bea3f5399 100644 --- a/examples/bergamo_ophys_session.json +++ b/examples/bergamo_ophys_session.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", - "schema_version": "1.1.5", + "schema_version": "2.0.0", "protocol_id": [], "experimenters": [ { @@ -15,8 +15,8 @@ "session_start_time": "2022-07-12T07:00:00Z", "session_end_time": "2022-07-12T07:00:00Z", "session_type": "BCI Photometry", + "instrument_id": "ophys_inst", "ethics_review_id": "2115", - "rig_id": "ophys_rig", "calibrations": [], "maintenance": [], "subject_id": "652567", diff --git a/examples/bergamo_ophys_session.py b/examples/bergamo_ophys_session.py index 92d8a9d36..e07dcca7c 100644 --- a/examples/bergamo_ophys_session.py +++ b/examples/bergamo_ophys_session.py @@ -28,8 +28,8 @@ session_end_time=t, subject_id="652567", session_type="BCI Photometry", + instrument_id="ophys_inst", ethics_review_id="2115", - rig_id="ophys_rig", mouse_platform_name="Mouse tube", active_mouse_platform=False, data_streams=[ diff --git a/examples/data_description.json b/examples/data_description.json index 3816da4c9..3e4e980ca 100644 --- a/examples/data_description.json +++ b/examples/data_description.json @@ -1,15 +1,11 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/data_description.py", - "schema_version": "1.0.5", + "schema_version": "2.0.0", "license": "CC-BY-4.0", - "platform": { - "name": "Electrophysiology platform", - "abbreviation": "ecephys" - }, "subject_id": "12345", "creation_time": "2022-02-21T16:30:01Z", "label": null, - "name": "ecephys_12345_2022-02-21_16-30-01", + "name": "12345_2022-02-21T163001", "institution": { "name": "Allen Institute for Neural Dynamics", "abbreviation": "AIND", @@ -48,7 +44,7 @@ ], "project_name": null, "restrictions": null, - "modality": [ + "modalities": [ { "name": "Extracellular electrophysiology", "abbreviation": "ecephys" diff --git a/examples/data_description.py b/examples/data_description.py index d2ea029b3..c371581ab 100644 --- a/examples/data_description.py +++ b/examples/data_description.py @@ -4,14 +4,12 @@ from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.platforms import Platform from aind_data_schema.components.identifiers import Person from aind_data_schema.core.data_description import Funding, RawDataDescription d = RawDataDescription( - modality=[Modality.ECEPHYS, Modality.BEHAVIOR_VIDEOS], - platform=Platform.ECEPHYS, + modalities=[Modality.ECEPHYS, Modality.BEHAVIOR_VIDEOS], subject_id="12345", creation_time=datetime(2022, 2, 21, 16, 30, 1, tzinfo=timezone.utc), institution=Organization.AIND, diff --git a/examples/ephys_instrument.json b/examples/ephys_instrument.json new file mode 100644 index 000000000..96157a28c --- /dev/null +++ b/examples/ephys_instrument.json @@ -0,0 +1,898 @@ +{ + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", + "instrument_id": "323_EPHYS1_20231003", + "mouse_platform": { + "device_type": "Disc", + "name": "Running Wheel", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "surface_material": null, + "date_surface_replaced": null, + "radius": "15", + "radius_unit": "centimeter", + "output": null, + "encoder": null, + "decoder": null, + "encoder_firmware": null + }, + "modification_date": "2023-10-03", + "calibrations": [ + { + "calibration_date": "2023-10-02T10:22:13Z", + "device_name": "Red Laser", + "description": "Laser power calibration", + "input": { + "power percent": [ + 10, + 20, + 40 + ] + }, + "output": { + "power mW": [ + 1, + 3, + 6 + ] + }, + "notes": null + }, + { + "calibration_date": "2023-10-02T10:22:13Z", + "device_name": "Blue Laser", + "description": "Laser power calibration", + "input": { + "power percent": [ + 10, + 20, + 40 + ] + }, + "output": { + "power mW": [ + 1, + 2, + 7 + ] + }, + "notes": null + } + ], + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Extracellular electrophysiology", + "abbreviation": "ecephys" + } + ], + "com_ports": [], + "instrument_type": null, + "manufacturer": null, + "temperature_control": null, + "notes": null, + "connections": [], + "components": [ + { + "name": "Ephys_assemblyA", + "device_type": "Ephys assembly", + "manipulator": { + "device_type": "Manipulator", + "name": "Manipulator 1", + "serial_number": "SN2938", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "probes": [ + { + "device_type": "Ephys probe", + "name": "Probe A", + "serial_number": "9291019", + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "probe_model": "Neuropixels 1.0", + "lasers": [], + "headstage": null + } + ] + }, + { + "name": "Ephys_assemblyB", + "device_type": "Ephys assembly", + "manipulator": { + "device_type": "Manipulator", + "name": "Manipulator B", + "serial_number": "SN2939", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "probes": [ + { + "device_type": "Ephys probe", + "name": "Probe B", + "serial_number": "9291020", + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "probe_model": "Neuropixels 1.0", + "lasers": [], + "headstage": null + } + ] + }, + { + "name": "Face Camera Assembly", + "device_type": "Camera assembly", + "camera_target": "Face side left", + "camera": { + "device_type": "Detector", + "name": "Face Camera", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72941", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Monochrome", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Camera lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": "15", + "focal_length_unit": "millimeter", + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": "f/2" + }, + "filter": { + "device_type": "Filter", + "name": "LP filter", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "filter_type": "Long pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": null, + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": "850 nm longpass filter" + }, + "position": null + }, + { + "name": "Body Camera Assembly", + "device_type": "Camera assembly", + "camera_target": "Body", + "camera": { + "device_type": "Detector", + "name": "Body Camera", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72941", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Monochrome", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Camera lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": "15", + "focal_length_unit": "millimeter", + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": "f/2" + }, + "filter": { + "device_type": "Filter", + "name": "LP filter", + "serial_number": null, + "manufacturer": { + "name": "Thorlabs", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "04gsnvb07" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "filter_type": "Long pass", + "diameter": null, + "width": null, + "height": null, + "size_unit": "millimeter", + "thickness": null, + "thickness_unit": null, + "filter_wheel_index": null, + "cut_off_wavelength": null, + "cut_on_wavelength": null, + "center_wavelength": null, + "wavelength_unit": "nanometer", + "description": "850 nm longpass filter" + }, + "position": null + }, + { + "name": "Laser_assemblyA", + "device_type": "Laser assembly", + "manipulator": { + "device_type": "Manipulator", + "name": "Manipulator A", + "serial_number": "SN2937", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lasers": [ + { + "device_type": "Laser", + "name": "Red Laser", + "serial_number": null, + "manufacturer": { + "name": "Oxxius", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "wavelength": 473, + "wavelength_unit": "nanometer", + "maximum_power": null, + "power_unit": "milliwatt", + "coupling": null, + "coupling_efficiency": null, + "coupling_efficiency_unit": "percent", + "item_number": null + }, + { + "device_type": "Laser", + "name": "Blue Laser", + "serial_number": null, + "manufacturer": { + "name": "Oxxius", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "wavelength": 638, + "wavelength_unit": "nanometer", + "maximum_power": null, + "power_unit": "milliwatt", + "coupling": null, + "coupling_efficiency": null, + "coupling_efficiency_unit": "percent", + "item_number": null + } + ], + "collimator": { + "device_type": "device", + "name": "Collimator A", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "fiber": { + "device_type": "Patch", + "name": "Bundle Branching Fiber-optic Patch Cord", + "serial_number": null, + "manufacturer": { + "name": "Doric", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "059n53q30" + }, + "model": "BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "core_diameter": "200", + "numerical_aperture": "0.37", + "photobleaching_date": null + } + }, + { + "device_type": "Neuropixels basestation", + "name": "Basestation Slot 3", + "serial_number": null, + "manufacturer": { + "name": "Interuniversity Microelectronics Center", + "abbreviation": "IMEC", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "02kcbn207" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "PXI", + "computer_name": "W10DT72942", + "channels": [], + "firmware_version": null, + "hardware_version": null, + "basestation_firmware_version": "2.019", + "bsc_firmware_version": "2.199", + "slot": 3, + "ports": [ + { + "index": 1, + "probes": [ + "Probe A" + ] + }, + { + "index": 2, + "probes": [ + "Probe B" + ] + } + ] + }, + { + "device_type": "Harp device", + "name": "Harp Behavior", + "serial_number": null, + "manufacturer": { + "name": "Open Ephys Production Site", + "abbreviation": "OEPS", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "007rkz355" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "USB", + "computer_name": "W10DT72941", + "channels": [ + { + "channel_name": "DO0", + "device_name": "Face Camera", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DO1", + "device_name": "Body Camera", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "AI0", + "device_name": "Running Wheel", + "channel_type": "Analog Input", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + } + ], + "firmware_version": null, + "hardware_version": null, + "harp_device_type": { + "whoami": 1216, + "name": "Behavior" + }, + "core_version": "2.1", + "tag_version": null, + "is_clock_generator": false + }, + { + "name": "Stick_assembly_1", + "device_type": "Camera assembly", + "camera_target": "Brain surface", + "camera": { + "device_type": "Detector", + "name": "stick microscope 1", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72942", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Color", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Probe lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": null, + "focal_length_unit": null, + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": null + }, + "filter": null, + "position": null + }, + { + "name": "Stick_assembly_2", + "device_type": "Camera assembly", + "camera_target": "Brain surface", + "camera": { + "device_type": "Detector", + "name": "stick microscope 2", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72942", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Color", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Probe lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": null, + "focal_length_unit": null, + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": null + }, + "filter": null, + "position": null + }, + { + "name": "Stick_assembly_3", + "device_type": "Camera assembly", + "camera_target": "Brain surface", + "camera": { + "device_type": "Detector", + "name": "stick microscope 3", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72942", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Color", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Probe lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": null, + "focal_length_unit": null, + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": null + }, + "filter": null, + "position": null + }, + { + "name": "Stick_assembly_4", + "device_type": "Camera assembly", + "camera_target": "Brain surface", + "camera": { + "device_type": "Detector", + "name": "stick microscope 4", + "serial_number": null, + "manufacturer": { + "name": "Teledyne FLIR", + "abbreviation": "FLIR", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "detector_type": "Camera", + "data_interface": "USB", + "cooling": "None", + "computer_name": "W10DT72942", + "frame_rate": "50", + "frame_rate_unit": "hertz", + "immersion": null, + "chroma": "Color", + "sensor_width": 1080, + "sensor_height": 570, + "size_unit": "pixel", + "sensor_format": "1/2.9", + "sensor_format_unit": "inches", + "bit_depth": null, + "bin_mode": "None", + "bin_width": null, + "bin_height": null, + "bin_unit": "pixel", + "gain": null, + "crop_offset_x": null, + "crop_offset_y": null, + "crop_width": null, + "crop_height": null, + "crop_unit": "pixel", + "recording_software": null, + "driver": null, + "driver_version": null + }, + "lens": { + "device_type": "Lens", + "name": "Probe lens", + "serial_number": null, + "manufacturer": { + "name": "Edmund Optics", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "01j1gwp17" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "focal_length": null, + "focal_length_unit": null, + "size": null, + "lens_size_unit": "inch", + "optimized_wavelength_range": null, + "wavelength_unit": "nanometer", + "max_aperture": null + }, + "filter": null, + "position": null + } + ] +} \ No newline at end of file diff --git a/examples/ephys_rig.py b/examples/ephys_instrument.py similarity index 93% rename from examples/ephys_rig.py rename to examples/ephys_instrument.py index 4ea6f733d..ab9f33879 100644 --- a/examples/ephys_rig.py +++ b/examples/ephys_instrument.py @@ -27,7 +27,7 @@ Patch, ProbePort, ) -from aind_data_schema.core.rig import Rig +from aind_data_schema.core.instrument import Instrument # Describes a rig with running wheel, 2 behavior cameras, one Harp Behavior board, # one dual-color laser module, one stick microscope, and 2 Neuropixels probes @@ -75,7 +75,7 @@ name="Manipulator A", serial_number="SN2937", manufacturer=Organization.NEW_SCALE_TECHNOLOGIES ), lasers=[red_laser, blue_laser], - collimator=Device(name="Collimator A", device_type="Collimator"), + collimator=Device(name="Collimator A"), fiber=Patch( name="Bundle Branching Fiber-optic Patch Cord", manufacturer=Organization.DORIC, @@ -275,18 +275,26 @@ output={"power mW": [1, 2, 7]}, ) -rig = Rig( - rig_id="323_EPHYS1_20231003", +inst = Instrument( + instrument_id="323_EPHYS1_20231003", modification_date=date(2023, 10, 3), modalities=[Modality.ECEPHYS], - ephys_assemblies=[ephys_assemblyA, ephys_assemblyB], - cameras=[camassm1, camassm2], - laser_assemblies=[laser_assembly], - daqs=[basestation, harp], - stick_microscopes=[microscope_1, microscope_2, microscope_3, microscope_4], + components=[ + ephys_assemblyA, + ephys_assemblyB, + camassm1, + camassm2, + laser_assembly, + basestation, + harp, + microscope_1, + microscope_2, + microscope_3, + microscope_4, + ], mouse_platform=running_wheel, calibrations=[red_laser_calibration, blue_laser_calibration], ) -serialized = rig.model_dump_json() -deserialized = Rig.model_validate_json(serialized) +serialized = inst.model_dump_json() +deserialized = Instrument.model_validate_json(serialized) deserialized.write_standard_file(prefix="ephys") diff --git a/examples/ephys_session.json b/examples/ephys_session.json index 8db47d853..9cdbd5499 100644 --- a/examples/ephys_session.json +++ b/examples/ephys_session.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", - "schema_version": "1.1.5", + "schema_version": "2.0.0", "protocol_id": [], "experimenters": [ { @@ -15,8 +15,8 @@ "session_start_time": "2023-04-25T02:35:00Z", "session_end_time": "2023-04-25T03:16:00Z", "session_type": "Receptive field mapping", + "instrument_id": "323_EPHYS1_20231003", "ethics_review_id": "2109", - "rig_id": "323_EPHYS1_20231003", "calibrations": [], "maintenance": [], "subject_id": "664484", diff --git a/examples/ephys_session.py b/examples/ephys_session.py index 4aa6123fc..978cf1a1e 100644 --- a/examples/ephys_session.py +++ b/examples/ephys_session.py @@ -25,8 +25,8 @@ session_start_time=datetime(year=2023, month=4, day=25, hour=2, minute=35, second=0, tzinfo=timezone.utc), session_end_time=datetime(year=2023, month=4, day=25, hour=3, minute=16, second=0, tzinfo=timezone.utc), session_type="Receptive field mapping", + instrument_id="323_EPHYS1_20231003", ethics_review_id="2109", - rig_id="323_EPHYS1_20231003", active_mouse_platform=False, mouse_platform_name="Running Wheel", stimulus_epochs=[ diff --git a/examples/exaspim_acquisition.json b/examples/exaspim_acquisition.json index 25d5a2a3f..14cf7a9d6 100644 --- a/examples/exaspim_acquisition.json +++ b/examples/exaspim_acquisition.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/acquisition.py", - "schema_version": "1.0.6", + "schema_version": "2.0.0", "protocol_id": [], "experimenters": [ { diff --git a/examples/exaspim_instrument.json b/examples/exaspim_instrument.json index bf2cd4cc9..7cb56eff9 100644 --- a/examples/exaspim_instrument.json +++ b/examples/exaspim_instrument.json @@ -1,8 +1,29 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/instrument.py", - "schema_version": "1.0.5", - "instrument_id": "440_exaSPIM1-20231004", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", + "instrument_id": "440_exaSPIM1_20231004", + "mouse_platform": null, "modification_date": "2023-10-04", + "calibrations": null, + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Selective plane illumination microscopy", + "abbreviation": "SPIM" + } + ], + "com_ports": [ + { + "hardware_name": "Laser Launch", + "com_port": "COM2" + }, + { + "hardware_name": "ASI Tiger", + "com_port": "COM5" + } + ], "instrument_type": "exaSPIM", "manufacturer": { "name": "Custom", @@ -11,33 +32,9 @@ "registry_identifier": null }, "temperature_control": false, - "optical_tables": [ - { - "device_type": "Optical table", - "name": "Table", - "serial_number": null, - "manufacturer": { - "name": "MKS Newport", - "abbreviation": null, - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "00k17f049" - }, - "model": "VIS3648-PG2-325A", - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "length": "36", - "width": "48", - "table_size_unit": "inch", - "vibration_control": true - } - ], - "enclosure": null, - "objectives": [ + "notes": null, + "connections": [], + "components": [ { "device_type": "Objective", "name": "Custom Objective", @@ -51,15 +48,13 @@ "model": "JM_DIAMOND 5.0X/1.3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "manufacturer collaboration between Schneider-Kreuznach and Vieworks", "numerical_aperture": "0.305", "magnification": "5", "immersion": "air", "objective_type": null - } - ], - "detectors": [ + }, { "device_type": "Detector", "name": "Camera 1", @@ -73,7 +68,7 @@ "model": "VNP-604MX", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "Coax", @@ -102,9 +97,7 @@ "recording_software": null, "driver": null, "driver_version": null - } - ], - "light_sources": [ + }, { "device_type": "Laser", "name": "LAS-08307", @@ -118,7 +111,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Housed in commercial laser combiner", "wavelength": 405, "wavelength_unit": "nanometer", @@ -142,7 +135,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Housed in commercial laser combiner", "wavelength": 488, "wavelength_unit": "nanometer", @@ -166,7 +159,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Housed in commercial laser combiner", "wavelength": 561, "wavelength_unit": "nanometer", @@ -190,7 +183,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Housed in commercial laser combiner", "wavelength": 638, "wavelength_unit": "nanometer", @@ -200,10 +193,7 @@ "coupling_efficiency": null, "coupling_efficiency_unit": "percent", "item_number": null - } - ], - "lenses": [], - "fluorescence_filters": [ + }, { "device_type": "Filter", "name": "Multiband filter", @@ -217,7 +207,7 @@ "model": "ZET405/488/561/640mv2", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Custom made filter", "filter_type": "Multiband", "diameter": "44.05", @@ -232,12 +222,94 @@ "center_wavelength": null, "wavelength_unit": "nanometer", "description": null - } - ], - "motorized_stages": [], - "scanning_stages": [ + }, { - "device_type": "Motorized stage", + "device_type": "DAQ Device", + "name": "Dev2", + "serial_number": null, + "manufacturer": { + "name": "National Instruments", + "abbreviation": null, + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "026exqw73" + }, + "model": "PCIe-6738", + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "USB", + "computer_name": "Dev2", + "channels": [ + { + "channel_name": "3", + "device_name": "LAS-08308", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + }, + { + "channel_name": "5", + "device_name": "539251", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + }, + { + "channel_name": "4", + "device_name": "LAS-08309", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + }, + { + "channel_name": "2", + "device_name": "stage-x", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + }, + { + "channel_name": "0", + "device_name": "TL-1", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + }, + { + "channel_name": "6", + "device_name": "LAS-08307", + "channel_type": "Analog Output", + "port": null, + "channel_index": null, + "sample_rate": "10000", + "sample_rate_unit": "hertz", + "event_based_sampling": null + } + ], + "firmware_version": null, + "hardware_version": null + }, + { + "device_type": "Scanning stage", "name": "stage-x", "serial_number": null, "manufacturer": { @@ -249,7 +321,7 @@ "model": "MS-8000", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "1000", "travel_unit": "millimeter", @@ -258,7 +330,7 @@ "stage_axis_name": "X" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "stage-y", "serial_number": null, "manufacturer": { @@ -270,7 +342,7 @@ "model": "MS-8000", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "1000", "travel_unit": "millimeter", @@ -279,7 +351,7 @@ "stage_axis_name": "Y" }, { - "device_type": "Motorized stage", + "device_type": "Scanning stage", "name": "stage-z", "serial_number": null, "manufacturer": { @@ -291,16 +363,14 @@ "model": "LS-100", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "travel": "100", "travel_unit": "millimeter", "firmware": null, "stage_axis_direction": "Illumination axis", "stage_axis_name": "Z" - } - ], - "additional_devices": [ + }, { "device_type": "Additional imaging device", "name": "TL-1", @@ -314,7 +384,7 @@ "model": "EL-16-40-TC-VIS-20D-C", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Tunable lens" }, @@ -334,7 +404,7 @@ "model": "K10CR1", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Rotation mount" }, @@ -351,109 +421,32 @@ "model": "L6Cc", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "imaging_device_type": "Laser combiner" - } - ], - "calibration_date": null, - "calibration_data": null, - "com_ports": [ - { - "hardware_name": "Laser Launch", - "com_port": "COM2" }, { - "hardware_name": "ASI Tiger", - "com_port": "COM5" - } - ], - "daqs": [ - { - "device_type": "DAQ Device", - "name": "Dev2", + "device_type": "Optical table", + "name": "Table", "serial_number": null, "manufacturer": { - "name": "National Instruments", + "name": "MKS Newport", "abbreviation": null, "registry": { "name": "Research Organization Registry", "abbreviation": "ROR" }, - "registry_identifier": "026exqw73" + "registry_identifier": "00k17f049" }, - "model": "PCIe-6738", + "model": "VIS3648-PG2-325A", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, - "data_interface": "USB", - "computer_name": "Dev2", - "channels": [ - { - "channel_name": "3", - "device_name": "LAS-08308", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - }, - { - "channel_name": "5", - "device_name": "539251", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - }, - { - "channel_name": "4", - "device_name": "LAS-08309", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - }, - { - "channel_name": "2", - "device_name": "stage-x", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - }, - { - "channel_name": "0", - "device_name": "TL-1", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - }, - { - "channel_name": "6", - "device_name": "LAS-08307", - "channel_type": "Analog Output", - "port": null, - "channel_index": null, - "sample_rate": "10000", - "sample_rate_unit": "hertz", - "event_based_sampling": null - } - ], - "firmware_version": null, - "hardware_version": null + "length": "36", + "width": "48", + "table_size_unit": "inch", + "vibration_control": true } - ], - "notes": null + ] } \ No newline at end of file diff --git a/examples/exaspim_instrument.py b/examples/exaspim_instrument.py index 0ddacc86a..a3bd14ba5 100644 --- a/examples/exaspim_instrument.py +++ b/examples/exaspim_instrument.py @@ -5,211 +5,242 @@ from aind_data_schema_models.organizations import Organization from aind_data_schema_models.units import SizeUnit, FrequencyUnit -from aind_data_schema.components.devices import DAQChannel, DAQDevice, Detector, Filter, Laser -from aind_data_schema.core import instrument +from aind_data_schema.components.devices import ( + AdditionalImagingDevice, + DAQChannel, + DAQDevice, + Detector, + Filter, + Laser, + Objective, + OpticalTable, + ScanningStage, +) +from aind_data_schema.core.instrument import Instrument, Com +from aind_data_schema_models.modalities import Modality + +objectives = [ + Objective( + name="Custom Objective", + numerical_aperture=0.305, + magnification=5, + immersion="air", + manufacturer=Organization.OTHER, + model="JM_DIAMOND 5.0X/1.3", + notes="manufacturer collaboration between Schneider-Kreuznach and Vieworks", + ), +] + +detectors = [ + Detector( + name="Camera 1", + detector_type="Camera", + data_interface="Coax", + cooling="Air", + manufacturer=Organization.VIEWORKS, + model="VNP-604MX", + serial_number="MB151BAY001", + ), +] + +light_sources = [ + Laser( + name="LAS-08307", + coupling="Single-mode fiber", + wavelength=405, + maximum_power=200, + serial_number="LAS-08307", + manufacturer=Organization.OXXIUS, + notes="Housed in commercial laser combiner", + ), + Laser( + name="LAS-08308", + coupling="Single-mode fiber", + wavelength=488, + maximum_power=200, + serial_number="LAS-08308", + manufacturer=Organization.OXXIUS, + notes="Housed in commercial laser combiner", + ), + Laser( + name="539251", + coupling="Single-mode fiber", + wavelength=561, + maximum_power=200, + serial_number="539251", + manufacturer=Organization.OXXIUS, + notes="Housed in commercial laser combiner", + ), + Laser( + name="LAS-08309", + coupling="Single-mode fiber", + wavelength=638, + maximum_power=200, + serial_number="LAS-08309", + manufacturer=Organization.OXXIUS, + notes="Housed in commercial laser combiner", + ), +] + +fluorescence_filters = [ + Filter( + name="Multiband filter", + filter_type="Multiband", + manufacturer=Organization.CHROMA, + diameter=44.05, + thickness=1, + thickness_unit=SizeUnit.MM, + model="ZET405/488/561/640mv2", + notes="Custom made filter", + filter_wheel_index=0, + ) +] + +daqs = [ + DAQDevice( + model="PCIe-6738", + data_interface="USB", + computer_name="Dev2", + manufacturer=Organization.NATIONAL_INSTRUMENTS, + name="Dev2", + channels=[ + DAQChannel( + channel_name="3", + channel_type="Analog Output", + device_name="LAS-08308", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + DAQChannel( + channel_name="5", + channel_type="Analog Output", + device_name="539251", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + DAQChannel( + channel_name="4", + channel_type="Analog Output", + device_name="LAS-08309", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + DAQChannel( + channel_name="2", + channel_type="Analog Output", + device_name="stage-x", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + DAQChannel( + channel_name="0", + channel_type="Analog Output", + device_name="TL-1", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + DAQChannel( + channel_name="6", + channel_type="Analog Output", + device_name="LAS-08307", + sample_rate=10000, + sample_rate_unit=FrequencyUnit.HZ, + ), + ], + ) +] + +scanning_stages = [ + ScanningStage( + name="stage-x", + stage_axis_direction="Detection axis", + stage_axis_name="X", + travel=1000, + model="MS-8000", + manufacturer=Organization.ASI, + ), + ScanningStage( + name="stage-y", + stage_axis_direction="Perpendicular axis", + stage_axis_name="Y", + travel=1000, + model="MS-8000", + manufacturer=Organization.ASI, + ), + ScanningStage( + name="stage-z", + stage_axis_direction="Illumination axis", + stage_axis_name="Z", + travel=100, + model="LS-100", + manufacturer=Organization.ASI, + ), +] + +additional_devices = [ + AdditionalImagingDevice( + imaging_device_type="Tunable lens", + name="TL-1", + manufacturer=Organization.OPTOTUNE, + model="EL-16-40-TC-VIS-20D-C", + serial_number="01", + ), + AdditionalImagingDevice( + name="RM-1", + imaging_device_type="Rotation mount", + manufacturer=Organization.THORLABS, + model="K10CR1", + serial_number="01", + ), + AdditionalImagingDevice( + name="LC-1", + imaging_device_type="Laser combiner", + manufacturer=Organization.OXXIUS, + model="L6Cc", + serial_number="L6CC-00513", + ), +] -inst = instrument.Instrument( - instrument_id="440_exaSPIM1-20231004", +optical_tables = [ + OpticalTable( + name="Table", + length=36, + width=48, + vibration_control=True, + model="VIS3648-PG2-325A", + manufacturer=Organization.MKS_NEWPORT, + ) +] + +inst = Instrument( + instrument_id="440_exaSPIM1_20231004", + modalities=[Modality.SPIM], instrument_type="exaSPIM", modification_date=datetime.date(2023, 10, 4), manufacturer=Organization.CUSTOM, - objectives=[ - instrument.Objective( - name="Custom Objective", - numerical_aperture=0.305, - magnification=5, - immersion="air", - manufacturer=Organization.OTHER, - model="JM_DIAMOND 5.0X/1.3", - notes="manufacturer collaboration between Schneider-Kreuznach and Vieworks", - ), - ], - detectors=[ - Detector( - name="Camera 1", - detector_type="Camera", - data_interface="Coax", - cooling="Air", - manufacturer=Organization.VIEWORKS, - model="VNP-604MX", - serial_number="MB151BAY001", - ), - ], - light_sources=[ - Laser( - name="LAS-08307", - coupling="Single-mode fiber", - wavelength=405, - maximum_power=200, - serial_number="LAS-08307", - manufacturer=Organization.OXXIUS, - notes="Housed in commercial laser combiner", - ), - Laser( - name="LAS-08308", - coupling="Single-mode fiber", - wavelength=488, - maximum_power=200, - serial_number="LAS-08308", - manufacturer=Organization.OXXIUS, - notes="Housed in commercial laser combiner", - ), - Laser( - name="539251", - coupling="Single-mode fiber", - wavelength=561, - maximum_power=200, - serial_number="539251", - manufacturer=Organization.OXXIUS, - notes="Housed in commercial laser combiner", - ), - Laser( - name="LAS-08309", - coupling="Single-mode fiber", - wavelength=638, - maximum_power=200, - serial_number="LAS-08309", - manufacturer=Organization.OXXIUS, - notes="Housed in commercial laser combiner", - ), - ], - fluorescence_filters=[ - Filter( - name="Multiband filter", - filter_type="Multiband", - manufacturer=Organization.CHROMA, - diameter=44.05, - thickness=1, - thickness_unit=SizeUnit.MM, - model="ZET405/488/561/640mv2", - notes="Custom made filter", - filter_wheel_index=0, - ) - ], - daqs=[ - DAQDevice( - model="PCIe-6738", - data_interface="USB", - computer_name="Dev2", - manufacturer=Organization.NATIONAL_INSTRUMENTS, - name="Dev2", - channels=[ - DAQChannel( - channel_name="3", - channel_type="Analog Output", - device_name="LAS-08308", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="5", - channel_type="Analog Output", - device_name="539251", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="4", - channel_type="Analog Output", - device_name="LAS-08309", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="2", - channel_type="Analog Output", - device_name="stage-x", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="0", - channel_type="Analog Output", - device_name="TL-1", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="6", - channel_type="Analog Output", - device_name="LAS-08307", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - ], - ) - ], - scanning_stages=[ - instrument.ScanningStage( - name="stage-x", - stage_axis_direction="Detection axis", - stage_axis_name="X", - travel=1000, - model="MS-8000", - manufacturer=Organization.ASI, - ), - instrument.ScanningStage( - name="stage-y", - stage_axis_direction="Perpendicular axis", - stage_axis_name="Y", - travel=1000, - model="MS-8000", - manufacturer=Organization.ASI, - ), - instrument.ScanningStage( - name="stage-z", - stage_axis_direction="Illumination axis", - stage_axis_name="Z", - travel=100, - model="LS-100", - manufacturer=Organization.ASI, - ), - ], - additional_devices=[ - instrument.AdditionalImagingDevice( - imaging_device_type="Tunable lens", - name="TL-1", - manufacturer=Organization.OPTOTUNE, - model="EL-16-40-TC-VIS-20D-C", - serial_number="01", - ), - instrument.AdditionalImagingDevice( - name="RM-1", - imaging_device_type="Rotation mount", - manufacturer=Organization.THORLABS, - model="K10CR1", - serial_number="01", - ), - instrument.AdditionalImagingDevice( - name="LC-1", - imaging_device_type="Laser combiner", - manufacturer=Organization.OXXIUS, - model="L6Cc", - serial_number="L6CC-00513", - ), - ], - optical_tables=[ - instrument.OpticalTable( - name="Table", - length=36, - width=48, - vibration_control=True, - model="VIS3648-PG2-325A", - manufacturer=Organization.MKS_NEWPORT, - ) + components=[ + *objectives, + *detectors, + *light_sources, + *fluorescence_filters, + *daqs, + *scanning_stages, + *additional_devices, + *optical_tables, ], com_ports=[ - instrument.Com( + Com( hardware_name="Laser Launch", com_port="COM2", ), - instrument.Com( + Com( hardware_name="ASI Tiger", com_port="COM5", ), ], temperature_control=False, ) + serialized = inst.model_dump_json() -deserialized = instrument.Instrument.model_validate_json(serialized) +deserialized = Instrument.model_validate_json(serialized) deserialized.write_standard_file(prefix="exaspim") diff --git a/examples/fip_behavior_rig.json b/examples/fip_behavior_instrument.json similarity index 93% rename from examples/fip_behavior_rig.json rename to examples/fip_behavior_instrument.json index f7f9baeb1..c4f0194c7 100644 --- a/examples/fip_behavior_rig.json +++ b/examples/fip_behavior_instrument.json @@ -1,8 +1,7 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/rig.py", - "schema_version": "1.0.5", - "rig_id": "447_FIP-Behavior_20000101", - "modification_date": "2000-01-01", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", + "instrument_id": "447_FIP-Behavior_20000101", "mouse_platform": { "device_type": "Tube", "name": "mouse_tube_foraging", @@ -11,134 +10,87 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "surface_material": null, "date_surface_replaced": null, "diameter": "4.0", "diameter_unit": "centimeter" }, - "stimulus_devices": [ + "modification_date": "2000-01-01", + "calibrations": [ { - "device_type": "Reward delivery", - "stage_type": { - "device_type": "Motorized stage", - "name": "NewScaleMotor for LickSpouts", - "serial_number": "xxxx", - "manufacturer": { - "name": "New Scale Technologies", - "abbreviation": null, - "registry": null, - "registry_identifier": null - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "travel": "15.0", - "travel_unit": "millimeter", - "firmware": "https://github.com/AllenNeuralDynamics/python-newscale,branch: axes-on-target,commit #7c17497" + "calibration_date": "2023-10-02T03:15:22Z", + "device_name": "470nm LED", + "description": "LED calibration", + "input": { + "Power setting": [ + 0 + ] }, - "reward_spouts": [ - { - "device_type": "Reward spout", - "name": "Left spout", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "side": "Left", - "spout_diameter": "1.2", - "spout_diameter_unit": "millimeter", - "spout_position": null, - "solenoid_valve": { - "device_type": "Solenoid", - "name": "Solenoid Left", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor": { - "device_type": "Lick detector", - "name": "Janelia_Lick_Detector Left", - "serial_number": null, - "manufacturer": { - "name": "Janelia Research Campus", - "abbreviation": "Janelia", - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "013sk6x84" - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor_type": "Capacitive" - }, - { - "device_type": "Reward spout", - "name": "Right spout", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "side": "Right", - "spout_diameter": "1.2", - "spout_diameter_unit": "millimeter", - "spout_position": null, - "solenoid_valve": { - "device_type": "Solenoid", - "name": "Solenoid Right", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor": { - "device_type": "Lick detector", - "name": "Janelia_Lick_Detector Right", - "serial_number": null, - "manufacturer": { - "name": "Janelia Research Campus", - "abbreviation": "Janelia", - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "013sk6x84" - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor_type": "Capacitive" - } - ] + "output": { + "Power mW": [ + 0.02 + ] + }, + "notes": null + }, + { + "calibration_date": "2023-10-02T03:15:22Z", + "device_name": "415nm LED", + "description": "LED calibration", + "input": { + "Power setting": [ + 0 + ] + }, + "output": { + "Power mW": [ + 0.02 + ] + }, + "notes": null + }, + { + "calibration_date": "2023-10-02T03:15:22Z", + "device_name": "560nm LED", + "description": "LED calibration", + "input": { + "Power setting": [ + 0 + ] + }, + "output": { + "Power mW": [ + 0.02 + ] + }, + "notes": null + } + ], + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Behavior", + "abbreviation": "behavior" + }, + { + "name": "Fiber photometry", + "abbreviation": "fib" } ], - "cameras": [ + "com_ports": [], + "instrument_type": null, + "manufacturer": null, + "temperature_control": null, + "notes": null, + "connections": [], + "components": [ { "name": "BehaviorVideography_FaceSide", + "device_type": "Camera assembly", "camera_target": "Face side left", "camera": { "device_type": "Detector", @@ -153,7 +105,7 @@ "model": "ELP-USBFHD05MT-KL170IR", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "The light intensity sensor was removed; IR illumination is constantly on", "detector_type": "Camera", "data_interface": "USB", @@ -201,7 +153,7 @@ "model": "XC0922LENS", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Focal Length 9-22mm 1/3\" IR F1.4", "focal_length": null, "focal_length_unit": null, @@ -216,6 +168,7 @@ }, { "name": "BehaviorVideography_FaceBottom", + "device_type": "Camera assembly", "camera_target": "Face bottom", "camera": { "device_type": "Detector", @@ -230,7 +183,7 @@ "model": "ELP-USBFHD05MT-KL170IR", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "The light intensity sensor was removed; IR illumination is constantly on", "detector_type": "Camera", "data_interface": "USB", @@ -278,7 +231,7 @@ "model": "XC0922LENS", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Focal Length 9-22mm 1/3\" IR F1.4", "focal_length": null, "focal_length_unit": null, @@ -288,16 +241,207 @@ "wavelength_unit": "nanometer", "max_aperture": "f/1.4" }, - "filter": null, - "position": null - } - ], - "enclosure": null, - "ephys_assemblies": [], - "fiber_assemblies": [], - "stick_microscopes": [], - "laser_assemblies": [], - "patch_cords": [ + "filter": null, + "position": null + }, + { + "device_type": "Harp device", + "name": "Harp Behavior", + "serial_number": null, + "manufacturer": { + "name": "Open Ephys Production Site", + "abbreviation": "OEPS", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "007rkz355" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "data_interface": "USB", + "computer_name": "behavior_computer", + "channels": [ + { + "channel_name": "DO0", + "device_name": "Solenoid Left", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DO1", + "device_name": "Solenoid Right", + "channel_type": "Digital Output", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DI0", + "device_name": "Janelia_Lick_Detector Left", + "channel_type": "Digital Input", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DI1", + "device_name": "Janelia_Lick_Detector Right", + "channel_type": "Digital Input", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + }, + { + "channel_name": "DI3", + "device_name": "Photometry Clock", + "channel_type": "Digital Input", + "port": null, + "channel_index": null, + "sample_rate": null, + "sample_rate_unit": null, + "event_based_sampling": null + } + ], + "firmware_version": "FTDI version:", + "hardware_version": null, + "harp_device_type": { + "whoami": 1216, + "name": "Behavior" + }, + "core_version": "2.1", + "tag_version": null, + "is_clock_generator": false + }, + { + "device_type": "Reward delivery", + "stage_type": { + "device_type": "Motorized stage", + "name": "NewScaleMotor for LickSpouts", + "serial_number": "xxxx", + "manufacturer": { + "name": "New Scale Technologies", + "abbreviation": null, + "registry": null, + "registry_identifier": null + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "travel": "15.0", + "travel_unit": "millimeter", + "firmware": "https://github.com/AllenNeuralDynamics/python-newscale,branch: axes-on-target,commit #7c17497" + }, + "reward_spouts": [ + { + "device_type": "Reward spout", + "name": "Left spout", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "side": "Left", + "spout_diameter": "1.2", + "spout_diameter_unit": "millimeter", + "spout_position": null, + "solenoid_valve": { + "device_type": "device", + "name": "Solenoid Left", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor": { + "device_type": "device", + "name": "Janelia_Lick_Detector Left", + "serial_number": null, + "manufacturer": { + "name": "Janelia Research Campus", + "abbreviation": "Janelia", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "013sk6x84" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor_type": "Capacitive" + }, + { + "device_type": "Reward spout", + "name": "Right spout", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "side": "Right", + "spout_diameter": "1.2", + "spout_diameter_unit": "millimeter", + "spout_position": null, + "solenoid_valve": { + "device_type": "device", + "name": "Solenoid Right", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor": { + "device_type": "device", + "name": "Janelia_Lick_Detector Right", + "serial_number": null, + "manufacturer": { + "name": "Janelia Research Campus", + "abbreviation": "Janelia", + "registry": { + "name": "Research Organization Registry", + "abbreviation": "ROR" + }, + "registry_identifier": "013sk6x84" + }, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor_type": "Capacitive" + } + ] + }, { "device_type": "Patch", "name": "Bundle Branching Fiber-optic Patch Cord", @@ -314,14 +458,12 @@ "model": "BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "core_diameter": "200", "numerical_aperture": "0.37", "photobleaching_date": null - } - ], - "light_sources": [ + }, { "device_type": "Light emitting diode", "name": "470nm LED", @@ -338,7 +480,7 @@ "model": "M470F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 470, "wavelength_unit": "nanometer", @@ -361,7 +503,7 @@ "model": "M415F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 415, "wavelength_unit": "nanometer", @@ -384,15 +526,13 @@ "model": "M565F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 565, "wavelength_unit": "nanometer", "bandwidth": null, "bandwidth_unit": null - } - ], - "detectors": [ + }, { "device_type": "Detector", "name": "Green CMOS", @@ -409,7 +549,7 @@ "model": "BFS-U3-20S40M", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -455,7 +595,7 @@ "model": "BFS-U3-20S40M", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -484,9 +624,7 @@ "recording_software": null, "driver": null, "driver_version": null - } - ], - "objectives": [ + }, { "device_type": "Objective", "name": "Objective", @@ -503,15 +641,13 @@ "model": "CFI Plan Apochromat Lambda D 10x", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "numerical_aperture": "0.45", "magnification": "10", "immersion": "air", "objective_type": null - } - ], - "filters": [ + }, { "device_type": "Filter", "name": "Green emission filter", @@ -525,7 +661,7 @@ "model": "FF01-520/35-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -554,7 +690,7 @@ "model": "FF01-600/37-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -583,7 +719,7 @@ "model": "FF562-Di03-25x36", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -612,7 +748,7 @@ "model": "FF493/574-Di01-25x36", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", "filter_type": "Multiband", "diameter": null, @@ -644,7 +780,7 @@ "model": "FB410-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -676,7 +812,7 @@ "model": "FB470-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -708,7 +844,7 @@ "model": "FB560-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -740,7 +876,7 @@ "model": "#69-898", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -772,7 +908,7 @@ "model": "#69-899", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -787,9 +923,7 @@ "center_wavelength": null, "wavelength_unit": "nanometer", "description": null - } - ], - "lenses": [ + }, { "device_type": "Lens", "name": "Image focusing lens", @@ -806,7 +940,7 @@ "model": "AC254-080-A-ML", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": "80", "focal_length_unit": "millimeter", @@ -815,170 +949,17 @@ "optimized_wavelength_range": null, "wavelength_unit": "nanometer", "max_aperture": null - } - ], - "digital_micromirror_devices": [], - "polygonal_scanners": [], - "pockels_cells": [], - "additional_devices": [ + }, { - "device_type": "Photometry Clock", + "device_type": "device", "name": "Photometry Clock", "serial_number": null, "manufacturer": null, "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, - "notes": null - } - ], - "daqs": [ - { - "device_type": "Harp device", - "name": "Harp Behavior", - "serial_number": null, - "manufacturer": { - "name": "Open Ephys Production Site", - "abbreviation": "OEPS", - "registry": { - "name": "Research Organization Registry", - "abbreviation": "ROR" - }, - "registry_identifier": "007rkz355" - }, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "data_interface": "USB", - "computer_name": "behavior_computer", - "channels": [ - { - "channel_name": "DO0", - "device_name": "Solenoid Left", - "channel_type": "Digital Output", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "DO1", - "device_name": "Solenoid Right", - "channel_type": "Digital Output", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "DI0", - "device_name": "Janelia_Lick_Detector Left", - "channel_type": "Digital Input", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "DI1", - "device_name": "Janelia_Lick_Detector Right", - "channel_type": "Digital Input", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - }, - { - "channel_name": "DI3", - "device_name": "Photometry Clock", - "channel_type": "Digital Input", - "port": null, - "channel_index": null, - "sample_rate": null, - "sample_rate_unit": null, - "event_based_sampling": null - } - ], - "firmware_version": "FTDI version:", - "hardware_version": null, - "harp_device_type": { - "whoami": 1216, - "name": "Behavior" - }, - "core_version": "2.1", - "tag_version": null, - "is_clock_generator": false - } - ], - "calibrations": [ - { - "calibration_date": "2023-10-02T03:15:22Z", - "device_name": "470nm LED", - "description": "LED calibration", - "input": { - "Power setting": [ - 0 - ] - }, - "output": { - "Power mW": [ - 0.02 - ] - }, - "notes": null - }, - { - "calibration_date": "2023-10-02T03:15:22Z", - "device_name": "415nm LED", - "description": "LED calibration", - "input": { - "Power setting": [ - 0 - ] - }, - "output": { - "Power mW": [ - 0.02 - ] - }, - "notes": null - }, - { - "calibration_date": "2023-10-02T03:15:22Z", - "device_name": "560nm LED", - "description": "LED calibration", - "input": { - "Power setting": [ - 0 - ] - }, - "output": { - "Power mW": [ - 0.02 - ] - }, + "additional_settings": null, "notes": null } - ], - "ccf_coordinate_transform": null, - "origin": null, - "rig_axes": null, - "modalities": [ - { - "name": "Behavior", - "abbreviation": "behavior" - }, - { - "name": "Fiber photometry", - "abbreviation": "fib" - } - ], - "notes": null + ] } \ No newline at end of file diff --git a/examples/fip_behavior_instrument.py b/examples/fip_behavior_instrument.py new file mode 100644 index 000000000..a061574d3 --- /dev/null +++ b/examples/fip_behavior_instrument.py @@ -0,0 +1,365 @@ +# -*- coding: utf-8 -*- + +""" example FIP ophys rig """ +from datetime import date, datetime, timezone + +from aind_data_schema_models.modalities import Modality +from aind_data_schema_models.units import FrequencyUnit, SizeUnit + +from aind_data_schema.components.devices import ( + Calibration, + CameraAssembly, + CameraTarget, + Camera, + Organization, + Lens, + HarpDevice, + HarpDeviceType, + DAQChannel, + RewardDelivery, + RewardSpout, + Software, + SpoutSide, + Device, + LickSensorType, + MotorizedStage, + Patch, + LightEmittingDiode, + Detector, + Objective, + Filter, + Tube, +) +from aind_data_schema.core.instrument import Instrument + +camera1 = CameraAssembly( + name="BehaviorVideography_FaceSide", + camera_target=CameraTarget.FACE_SIDE_LEFT, + camera=Camera( + name="Side face camera", + detector_type="Camera", + serial_number="TBD", + manufacturer=Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", + data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", + cooling="Air", + bin_mode="Additive", + recording_software=Software(name="Bonsai", version="2.5"), + ), + lens=Lens( + name="Xenocam 1", + model="XC0922LENS", + serial_number="unknown", + manufacturer=Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', + ), +) + +camera2 = CameraAssembly( + name="BehaviorVideography_FaceBottom", + camera_target=CameraTarget.FACE_BOTTOM, + camera=Camera( + name="Bottom face Camera", + detector_type="Camera", + serial_number="TBD", + manufacturer=Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", + data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", + cooling="Air", + bin_mode="Additive", + recording_software=Software(name="Bonsai", version="2.5"), + ), + lens=Lens( + name="Xenocam 2", + model="XC0922LENS", + serial_number="unknown", + manufacturer=Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', + ), +) + +harp_behavior = HarpDevice( + name="Harp Behavior", + harp_device_type=HarpDeviceType.BEHAVIOR, + core_version="2.1", + firmware_version="FTDI version:", + computer_name="behavior_computer", + is_clock_generator=False, + channels=[ + DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), + DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), + DAQChannel(channel_name="DI0", device_name="Janelia_Lick_Detector Left", channel_type="Digital Input"), + DAQChannel(channel_name="DI1", device_name="Janelia_Lick_Detector Right", channel_type="Digital Input"), + DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), + ], +) + +reward_delivery = RewardDelivery( + reward_spouts=[ + RewardSpout( + name="Left spout", + side=SpoutSide.LEFT, + spout_diameter=1.2, + solenoid_valve=Device(name="Solenoid Left"), + lick_sensor=Device( + name="Janelia_Lick_Detector Left", + manufacturer=Organization.JANELIA, + ), + lick_sensor_type=LickSensorType("Capacitive"), + ), + RewardSpout( + name="Right spout", + side=SpoutSide.RIGHT, + spout_diameter=1.2, + solenoid_valve=Device(name="Solenoid Right"), + lick_sensor=Device( + name="Janelia_Lick_Detector Right", + manufacturer=Organization.JANELIA, + ), + lick_sensor_type=LickSensorType("Capacitive"), + ), + ], + stage_type=MotorizedStage( + name="NewScaleMotor for LickSpouts", + serial_number="xxxx", # grabbing from GUI/SettingFiles + manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, + travel=15.0, # unit is mm + firmware=("https://github.com/AllenNeuralDynamics/python-newscale,branch: axes-on-target,commit #7c17497"), + ), +) + +patch_cord = Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, +) + +light_sources = [ + LightEmittingDiode( + name="470nm LED", + manufacturer=Organization.THORLABS, + model="M470F3", + wavelength=470, + ), + LightEmittingDiode( + name="415nm LED", + manufacturer=Organization.THORLABS, + model="M415F3", + wavelength=415, + ), + LightEmittingDiode( + name="565nm LED", + manufacturer=Organization.THORLABS, + model="M565F3", + wavelength=565, + ), +] + +detectors = [ + Detector( + name="Green CMOS", + serial_number="21396991", + manufacturer=Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_offset_x=0, + crop_offset_y=0, + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, + ), + Detector( + name="Red CMOS", + serial_number="21396991", + manufacturer=Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_offset_x=0, + crop_offset_y=0, + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, + ), +] + +objective = Objective( + name="Objective", + serial_number="128022336", + manufacturer=Organization.NIKON, + model="CFI Plan Apochromat Lambda D 10x", + numerical_aperture=0.45, + magnification=10, + immersion="air", +) + +filters = [ + Filter( + name="Green emission filter", + manufacturer=Organization.SEMROCK, + model="FF01-520/35-25", + filter_type="Band pass", + center_wavelength=520, + diameter=25, + ), + Filter( + name="Red emission filter", + manufacturer=Organization.SEMROCK, + model="FF01-600/37-25", + filter_type="Band pass", + center_wavelength=600, + diameter=25, + ), + Filter( + name="Emission Dichroic", + model="FF562-Di03-25x36", + manufacturer=Organization.SEMROCK, + filter_type="Dichroic", + height=25, + width=36, + cut_off_wavelength=562, + ), + Filter( + name="dual-edge standard epi-fluorescence dichroic beamsplitter", + model="FF493/574-Di01-25x36", + manufacturer=Organization.SEMROCK, + notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", + filter_type="Multiband", + width=36, + height=24, + ), + Filter( + name="Excitation filter 410nm", + manufacturer=Organization.THORLABS, + model="FB410-10", + filter_type="Band pass", + diameter=25, + center_wavelength=410, + ), + Filter( + name="Excitation filter 470nm", + manufacturer=Organization.THORLABS, + model="FB470-10", + filter_type="Band pass", + center_wavelength=470, + diameter=25, + ), + Filter( + name="Excitation filter 560nm", + manufacturer=Organization.THORLABS, + model="FB560-10", + filter_type="Band pass", + diameter=25, + center_wavelength=560, + ), + Filter( + name="450 Dichroic Longpass Filter", + manufacturer=Organization.EDMUND_OPTICS, + model="#69-898", + filter_type="Dichroic", + cut_off_wavelength=450, + width=35.6, + height=25.2, + ), + Filter( + name="500 Dichroic Longpass Filter", + manufacturer=Organization.EDMUND_OPTICS, + model="#69-899", + filter_type="Dichroic", + cut_off_wavelength=500, + width=35.6, + height=23.2, + ), +] + +lens = Lens( + manufacturer=Organization.THORLABS, + model="AC254-080-A-ML", + name="Image focusing lens", + focal_length=80, + focal_length_unit=SizeUnit.MM, + size=1, +) + +additional_device = Device(name="Photometry Clock") + +calibrations = [ + Calibration( + calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), + device_name="470nm LED", + description="LED calibration", + input={"Power setting": [0]}, + output={"Power mW": [0.02]}, + ), + Calibration( + calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), + device_name="415nm LED", + description="LED calibration", + input={"Power setting": [0]}, + output={"Power mW": [0.02]}, + ), + Calibration( + calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), + device_name="560nm LED", + description="LED calibration", + input={"Power setting": [0]}, + output={"Power mW": [0.02]}, + ), + # Water calibration comes here# +] + +inst = Instrument( + instrument_id="447_FIP-Behavior_20000101", + modification_date=date(2000, 1, 1), + modalities=[Modality.BEHAVIOR, Modality.FIB], + mouse_platform=Tube(name="mouse_tube_foraging", diameter=4.0), + components=[ + camera1, + camera2, + harp_behavior, + reward_delivery, + patch_cord, + *light_sources, + *detectors, + objective, + *filters, + lens, + additional_device, + ], + calibrations=calibrations, +) + +inst.write_standard_file(prefix="fip_behavior") diff --git a/examples/fip_behavior_rig.py b/examples/fip_behavior_rig.py deleted file mode 100644 index 6ce341a96..000000000 --- a/examples/fip_behavior_rig.py +++ /dev/null @@ -1,361 +0,0 @@ -# -*- coding: utf-8 -*- - -""" example FIP ophys rig """ -from datetime import date, datetime, timezone - -from aind_data_schema_models.modalities import Modality -from aind_data_schema_models.units import FrequencyUnit, SizeUnit - -import aind_data_schema.components.devices as d -import aind_data_schema.core.rig as r - -r = r.Rig( - rig_id="447_FIP-Behavior_20000101", - modification_date=date(2000, 1, 1), - modalities=[Modality.BEHAVIOR, Modality.FIB], - cameras=[ - d.CameraAssembly( - name="BehaviorVideography_FaceSide", - camera_target=d.CameraTarget.FACE_SIDE_LEFT, - camera=d.Camera( - name="Side face camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 1", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - d.CameraAssembly( - name="BehaviorVideography_FaceBottom", - camera_target=d.CameraTarget.FACE_BOTTOM, - camera=d.Camera( - name="Bottom face Camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 2", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - ], - daqs=[ - d.HarpDevice( - name="Harp Behavior", - harp_device_type=d.HarpDeviceType.BEHAVIOR, - core_version="2.1", - firmware_version="FTDI version:", - computer_name="behavior_computer", - is_clock_generator=False, - channels=[ - d.DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), - d.DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), - d.DAQChannel( - channel_name="DI0", device_name="Janelia_Lick_Detector Left", channel_type="Digital Input" - ), - d.DAQChannel( - channel_name="DI1", device_name="Janelia_Lick_Detector Right", channel_type="Digital Input" - ), - d.DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), - ], - ) - ], - mouse_platform=d.Tube(name="mouse_tube_foraging", diameter=4.0), - stimulus_devices=[ - d.RewardDelivery( - reward_spouts=[ - d.RewardSpout( - name="Left spout", - side=d.SpoutSide.LEFT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Left"), - lick_sensor=d.Device( - name="Janelia_Lick_Detector Left", - device_type="Lick detector", - manufacturer=d.Organization.JANELIA, - ), - lick_sensor_type=d.LickSensorType("Capacitive"), - ), - d.RewardSpout( - name="Right spout", - side=d.SpoutSide.RIGHT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Right"), - lick_sensor=d.Device( - name="Janelia_Lick_Detector Right", - device_type="Lick detector", - manufacturer=d.Organization.JANELIA, - ), - lick_sensor_type=d.LickSensorType("Capacitive"), - ), - ], - stage_type=d.MotorizedStage( - name="NewScaleMotor for LickSpouts", - serial_number="xxxx", # grabbing from GUI/SettingFiles - manufacturer=d.Organization.NEW_SCALE_TECHNOLOGIES, - travel=15.0, # unit is mm - firmware=( - "https://github.com/AllenNeuralDynamics/python-newscale,branch: axes-on-target,commit #7c17497" - ), - ), - ), - ], - # Common - # FIB Specific - patch_cords=[ - d.Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=d.Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - light_sources=[ - d.LightEmittingDiode( - name="470nm LED", - manufacturer=d.Organization.THORLABS, - model="M470F3", - wavelength=470, - ), - d.LightEmittingDiode( - name="415nm LED", - manufacturer=d.Organization.THORLABS, - model="M415F3", - wavelength=415, - ), - d.LightEmittingDiode( - name="565nm LED", - manufacturer=d.Organization.THORLABS, - model="M565F3", - wavelength=565, - ), - ], - detectors=[ - d.Detector( - name="Green CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", - detector_type="Camera", - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_offset_x=0, - crop_offset_y=0, - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ), - d.Detector( - name="Red CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", - detector_type="Camera", - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_offset_x=0, - crop_offset_y=0, - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ), - ], - objectives=[ - d.Objective( - name="Objective", - serial_number="128022336", - manufacturer=d.Organization.NIKON, - model="CFI Plan Apochromat Lambda D 10x", - numerical_aperture=0.45, - magnification=10, - immersion="air", - ) - ], - filters=[ - d.Filter( - name="Green emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-520/35-25", - filter_type="Band pass", - center_wavelength=520, - diameter=25, - ), - d.Filter( - name="Red emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-600/37-25", - filter_type="Band pass", - center_wavelength=600, - diameter=25, - ), - d.Filter( - name="Emission Dichroic", - model="FF562-Di03-25x36", - manufacturer=d.Organization.SEMROCK, - filter_type="Dichroic", - height=25, - width=36, - cut_off_wavelength=562, - ), - d.Filter( - name="dual-edge standard epi-fluorescence dichroic beamsplitter", - model="FF493/574-Di01-25x36", - manufacturer=d.Organization.SEMROCK, - notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", - filter_type="Multiband", - width=36, - height=24, - ), - d.Filter( - name="Excitation filter 410nm", - manufacturer=d.Organization.THORLABS, - model="FB410-10", - filter_type="Band pass", - diameter=25, - center_wavelength=410, - ), - d.Filter( - name="Excitation filter 470nm", - manufacturer=d.Organization.THORLABS, - model="FB470-10", - filter_type="Band pass", - center_wavelength=470, - diameter=25, - ), - d.Filter( - name="Excitation filter 560nm", - manufacturer=d.Organization.THORLABS, - model="FB560-10", - filter_type="Band pass", - diameter=25, - center_wavelength=560, - ), - d.Filter( - name="450 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-898", - filter_type="Dichroic", - cut_off_wavelength=450, - width=35.6, - height=25.2, - ), - d.Filter( - name="500 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-899", - filter_type="Dichroic", - cut_off_wavelength=500, - width=35.6, - height=23.2, - ), - ], - lenses=[ - d.Lens( - manufacturer=d.Organization.THORLABS, - model="AC254-080-A-ML", - name="Image focusing lens", - focal_length=80, - focal_length_unit=SizeUnit.MM, - size=1, - ) - ], - additional_devices=[d.Device(device_type="Photometry Clock", name="Photometry Clock")], - # FIB Specific - # Optogenetics Specific # Xinxin to fill in - # light_sources=[ - # d.LightEmittingDiode( - # name="LED for photostimulation", - # manufacturer=d.Organization.PRIZMATIX, - # model="xxx", - # wavelength=470, - # ), - # ], - # daqs=[ - # d.DAQDevice( - # name="NIDAQ for opto", - # device_type="DAQ Device", - # data_interface="USB2.0", - # manufacturer=d.Organization.NATIONAL_INSTRUMENTS, - # computer_name="behavior_computer", - # channels=[ - # ], - # ) - # ], - # Optogenetics Specific - # Calibrations - calibrations=[ - d.Calibration( - calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), - device_name="470nm LED", - description="LED calibration", - input={"Power setting": [0]}, - output={"Power mW": [0.02]}, - ), - d.Calibration( - calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), - device_name="415nm LED", - description="LED calibration", - input={"Power setting": [0]}, - output={"Power mW": [0.02]}, - ), - d.Calibration( - calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), - device_name="560nm LED", - description="LED calibration", - input={"Power setting": [0]}, - output={"Power mW": [0.02]}, - ), - # Water calibration comes here# - ], -) - -r.write_standard_file(prefix="fip_behavior") diff --git a/examples/fip_ophys_rig.json b/examples/fip_ophys_instrument.json similarity index 92% rename from examples/fip_ophys_rig.json rename to examples/fip_ophys_instrument.json index bf7bd982d..985d44f91 100644 --- a/examples/fip_ophys_rig.json +++ b/examples/fip_ophys_instrument.json @@ -1,8 +1,7 @@ { - "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/rig.py", - "schema_version": "1.0.5", - "rig_id": "428_FIP1_20231003", - "modification_date": "2023-10-03", + "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/inst.py", + "schema_version": "2.0.0", + "instrument_id": "428_FIP1_20231003", "mouse_platform": { "device_type": "Disc", "name": "mouse_disc", @@ -11,7 +10,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "surface_material": null, "date_surface_replaced": null, @@ -22,93 +21,48 @@ "decoder": null, "encoder_firmware": null }, - "stimulus_devices": [ + "modification_date": "2023-10-03", + "calibrations": [ { - "device_type": "Reward delivery", - "stage_type": null, - "reward_spouts": [ - { - "device_type": "Reward spout", - "name": "Left spout", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "side": "Left", - "spout_diameter": "1.2", - "spout_diameter_unit": "millimeter", - "spout_position": null, - "solenoid_valve": { - "device_type": "Solenoid", - "name": "Solenoid Left", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor": { - "device_type": "Lick detector", - "name": "Lick-o-meter Left", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor_type": null - }, - { - "device_type": "Reward spout", - "name": "Right spout", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null, - "side": "Right", - "spout_diameter": "1.2", - "spout_diameter_unit": "millimeter", - "spout_position": null, - "solenoid_valve": { - "device_type": "Solenoid", - "name": "Solenoid Right", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor": { - "device_type": "Lick detector", - "name": "Lick-o-meter Right", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - }, - "lick_sensor_type": null - } - ] + "calibration_date": "2023-10-02T03:15:22Z", + "device_name": "470nm LED", + "description": "LED calibration", + "input": { + "Power setting": [ + 1, + 2, + 3 + ] + }, + "output": { + "Power mW": [ + 5, + 10, + 13 + ] + }, + "notes": null } ], - "cameras": [ + "ccf_coordinate_transform": null, + "origin": null, + "instrument_axes": null, + "modalities": [ + { + "name": "Fiber photometry", + "abbreviation": "fib" + } + ], + "com_ports": [], + "instrument_type": null, + "manufacturer": null, + "temperature_control": null, + "notes": null, + "connections": [], + "components": [ { "name": "BehaviorVideography_FaceSide", + "device_type": "Camera assembly", "camera_target": "Face side left", "camera": { "device_type": "Detector", @@ -123,7 +77,7 @@ "model": "ELP-USBFHD05MT-KL170IR", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "The light intensity sensor was removed; IR illumination is constantly on", "detector_type": "Camera", "data_interface": "USB", @@ -171,7 +125,7 @@ "model": "XC0922LENS", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Focal Length 9-22mm 1/3\" IR F1.4", "focal_length": null, "focal_length_unit": null, @@ -186,6 +140,7 @@ }, { "name": "BehaviorVideography_FaceBottom", + "device_type": "Camera assembly", "camera_target": "Face bottom", "camera": { "device_type": "Detector", @@ -200,7 +155,7 @@ "model": "ELP-USBFHD05MT-KL170IR", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "The light intensity sensor was removed; IR illumination is constantly on", "detector_type": "Camera", "data_interface": "USB", @@ -248,7 +203,7 @@ "model": "XC0922LENS", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "Focal Length 9-22mm 1/3\" IR F1.4", "focal_length": null, "focal_length_unit": null, @@ -260,14 +215,7 @@ }, "filter": null, "position": null - } - ], - "enclosure": null, - "ephys_assemblies": [], - "fiber_assemblies": [], - "stick_microscopes": [], - "laser_assemblies": [], - "patch_cords": [ + }, { "device_type": "Patch", "name": "Bundle Branching Fiber-optic Patch Cord", @@ -284,14 +232,12 @@ "model": "BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "core_diameter": "200", "numerical_aperture": "0.37", "photobleaching_date": null - } - ], - "light_sources": [ + }, { "device_type": "Light emitting diode", "name": "470nm LED", @@ -308,7 +254,7 @@ "model": "M470F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 470, "wavelength_unit": "nanometer", @@ -331,7 +277,7 @@ "model": "M415F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 415, "wavelength_unit": "nanometer", @@ -354,15 +300,13 @@ "model": "M565F3", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "wavelength": 565, "wavelength_unit": "nanometer", "bandwidth": null, "bandwidth_unit": null - } - ], - "detectors": [ + }, { "device_type": "Detector", "name": "Green CMOS", @@ -379,7 +323,7 @@ "model": "BFS-U3-20S40M", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -425,7 +369,7 @@ "model": "BFS-U3-20S40M", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "detector_type": "Camera", "data_interface": "USB", @@ -454,9 +398,7 @@ "recording_software": null, "driver": null, "driver_version": null - } - ], - "objectives": [ + }, { "device_type": "Objective", "name": "Objective", @@ -473,15 +415,13 @@ "model": "CFI Plan Apochromat Lambda D 10x", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "numerical_aperture": "0.45", "magnification": "10", "immersion": "air", "objective_type": null - } - ], - "filters": [ + }, { "device_type": "Filter", "name": "Green emission filter", @@ -495,7 +435,7 @@ "model": "FF01-520/35-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -524,7 +464,7 @@ "model": "FF01-600/37-25", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -553,7 +493,7 @@ "model": "FF562-Di03-25x36", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -582,7 +522,7 @@ "model": "FF493/574-Di01-25x36", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": "493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", "filter_type": "Multiband", "diameter": null, @@ -614,7 +554,7 @@ "model": "FB410-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -646,7 +586,7 @@ "model": "FB470-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -678,7 +618,7 @@ "model": "FB560-10", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Band pass", "diameter": "25", @@ -710,7 +650,7 @@ "model": "#69-898", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -742,7 +682,7 @@ "model": "#69-899", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "filter_type": "Dichroic", "diameter": null, @@ -757,9 +697,7 @@ "center_wavelength": null, "wavelength_unit": "nanometer", "description": null - } - ], - "lenses": [ + }, { "device_type": "Lens", "name": "Image focusing lens", @@ -776,7 +714,7 @@ "model": "AC254-080-A-ML", "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "focal_length": "80", "focal_length_unit": "millimeter", @@ -785,25 +723,7 @@ "optimized_wavelength_range": null, "wavelength_unit": "nanometer", "max_aperture": null - } - ], - "digital_micromirror_devices": [], - "polygonal_scanners": [], - "pockels_cells": [], - "additional_devices": [ - { - "device_type": "Photometry Clock", - "name": "Photometry Clock", - "serial_number": null, - "manufacturer": null, - "model": null, - "path_to_cad": null, - "port_index": null, - "additional_settings": {}, - "notes": null - } - ], - "daqs": [ + }, { "device_type": "Harp device", "name": "Harp Behavior", @@ -820,7 +740,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "data_interface": "USB", "computer_name": "behavior_computer", @@ -885,38 +805,99 @@ "core_version": "2.1", "tag_version": null, "is_clock_generator": false - } - ], - "calibrations": [ + }, { - "calibration_date": "2023-10-02T03:15:22Z", - "device_name": "470nm LED", - "description": "LED calibration", - "input": { - "Power setting": [ - 1, - 2, - 3 - ] - }, - "output": { - "Power mW": [ - 5, - 10, - 13 - ] - }, - "notes": null - } - ], - "ccf_coordinate_transform": null, - "origin": null, - "rig_axes": null, - "modalities": [ + "device_type": "Reward delivery", + "stage_type": null, + "reward_spouts": [ + { + "device_type": "Reward spout", + "name": "Left spout", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "side": "Left", + "spout_diameter": "1.2", + "spout_diameter_unit": "millimeter", + "spout_position": null, + "solenoid_valve": { + "device_type": "device", + "name": "Solenoid Left", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor": { + "device_type": "device", + "name": "Lick-o-meter Left", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor_type": null + }, + { + "device_type": "Reward spout", + "name": "Right spout", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null, + "side": "Right", + "spout_diameter": "1.2", + "spout_diameter_unit": "millimeter", + "spout_position": null, + "solenoid_valve": { + "device_type": "device", + "name": "Solenoid Right", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor": { + "device_type": "device", + "name": "Lick-o-meter Right", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null + }, + "lick_sensor_type": null + } + ] + }, { - "name": "Fiber photometry", - "abbreviation": "fib" + "device_type": "device", + "name": "Photometry Clock", + "serial_number": null, + "manufacturer": null, + "model": null, + "path_to_cad": null, + "port_index": null, + "additional_settings": null, + "notes": null } - ], - "notes": null + ] } \ No newline at end of file diff --git a/examples/fip_ophys_instrument.py b/examples/fip_ophys_instrument.py new file mode 100644 index 000000000..50ec31906 --- /dev/null +++ b/examples/fip_ophys_instrument.py @@ -0,0 +1,332 @@ +""" example FIP ophys rig """ + +from datetime import date, datetime, timezone + +from aind_data_schema_models.modalities import Modality +from aind_data_schema_models.units import FrequencyUnit, SizeUnit + +import aind_data_schema.components.devices as d +import aind_data_schema.core.instrument as r + +camera_assembly_1 = d.CameraAssembly( + name="BehaviorVideography_FaceSide", + camera_target=d.CameraTarget.FACE_SIDE_LEFT, + camera=d.Camera( + name="Side face camera", + detector_type="Camera", + serial_number="TBD", + manufacturer=d.Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", + data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", + cooling="Air", + bin_mode="Additive", + recording_software=d.Software(name="Bonsai", version="2.5"), + ), + lens=d.Lens( + name="Xenocam 1", + model="XC0922LENS", + serial_number="unknown", + manufacturer=d.Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', + ), +) + +camera_assembly_2 = d.CameraAssembly( + name="BehaviorVideography_FaceBottom", + camera_target=d.CameraTarget.FACE_BOTTOM, + camera=d.Camera( + name="Bottom face Camera", + detector_type="Camera", + serial_number="TBD", + manufacturer=d.Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", + data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", + cooling="Air", + bin_mode="Additive", + recording_software=d.Software(name="Bonsai", version="2.5"), + ), + lens=d.Lens( + name="Xenocam 2", + model="XC0922LENS", + serial_number="unknown", + manufacturer=d.Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', + ), +) + +patch_cord = d.Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=d.Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, +) + +light_source_1 = d.LightEmittingDiode( + name="470nm LED", + manufacturer=d.Organization.THORLABS, + model="M470F3", + wavelength=470, +) + +light_source_2 = d.LightEmittingDiode( + name="415nm LED", + manufacturer=d.Organization.THORLABS, + model="M415F3", + wavelength=415, +) + +light_source_3 = d.LightEmittingDiode( + name="565nm LED", + manufacturer=d.Organization.THORLABS, + model="M565F3", + wavelength=565, +) + +detector_1 = d.Detector( + name="Green CMOS", + serial_number="21396991", + manufacturer=d.Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_offset_x=0, + crop_offset_y=0, + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, +) + +detector_2 = d.Detector( + name="Red CMOS", + serial_number="21396991", + manufacturer=d.Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_offset_x=0, + crop_offset_y=0, + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, +) + +objective = d.Objective( + name="Objective", + serial_number="128022336", + manufacturer=d.Organization.NIKON, + model="CFI Plan Apochromat Lambda D 10x", + numerical_aperture=0.45, + magnification=10, + immersion="air", +) + +filter_1 = d.Filter( + name="Green emission filter", + manufacturer=d.Organization.SEMROCK, + model="FF01-520/35-25", + filter_type="Band pass", + center_wavelength=520, + diameter=25, +) + +filter_2 = d.Filter( + name="Red emission filter", + manufacturer=d.Organization.SEMROCK, + model="FF01-600/37-25", + filter_type="Band pass", + center_wavelength=600, + diameter=25, +) + +filter_3 = d.Filter( + name="Emission Dichroic", + model="FF562-Di03-25x36", + manufacturer=d.Organization.SEMROCK, + filter_type="Dichroic", + height=25, + width=36, + cut_off_wavelength=562, +) + +filter_4 = d.Filter( + name="dual-edge standard epi-fluorescence dichroic beamsplitter", + model="FF493/574-Di01-25x36", + manufacturer=d.Organization.SEMROCK, + notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", + filter_type="Multiband", + width=36, + height=24, +) + +filter_5 = d.Filter( + name="Excitation filter 410nm", + manufacturer=d.Organization.THORLABS, + model="FB410-10", + filter_type="Band pass", + diameter=25, + center_wavelength=410, +) + +filter_6 = d.Filter( + name="Excitation filter 470nm", + manufacturer=d.Organization.THORLABS, + model="FB470-10", + filter_type="Band pass", + center_wavelength=470, + diameter=25, +) + +filter_7 = d.Filter( + name="Excitation filter 560nm", + manufacturer=d.Organization.THORLABS, + model="FB560-10", + filter_type="Band pass", + diameter=25, + center_wavelength=560, +) + +filter_8 = d.Filter( + name="450 Dichroic Longpass Filter", + manufacturer=d.Organization.EDMUND_OPTICS, + model="#69-898", + filter_type="Dichroic", + cut_off_wavelength=450, + width=35.6, + height=25.2, +) + +filter_9 = d.Filter( + name="500 Dichroic Longpass Filter", + manufacturer=d.Organization.EDMUND_OPTICS, + model="#69-899", + filter_type="Dichroic", + cut_off_wavelength=500, + width=35.6, + height=23.2, +) + +lens = d.Lens( + manufacturer=d.Organization.THORLABS, + model="AC254-080-A-ML", + name="Image focusing lens", + focal_length=80, + focal_length_unit=SizeUnit.MM, + size=1, +) + +daq = d.HarpDevice( + name="Harp Behavior", + harp_device_type=d.HarpDeviceType.BEHAVIOR, + core_version="2.1", + computer_name="behavior_computer", + is_clock_generator=False, + channels=[ + d.DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), + d.DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), + d.DAQChannel(channel_name="DI0", device_name="Lick-o-meter Left", channel_type="Digital Input"), + d.DAQChannel(channel_name="DI1", device_name="Lick-o-meter Right", channel_type="Digital Input"), + d.DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), + ], +) + +mouse_platform = d.Disc(name="mouse_disc", radius=8.5) + +stimulus_device = d.RewardDelivery( + reward_spouts=[ + d.RewardSpout( + name="Left spout", + side=d.SpoutSide.LEFT, + spout_diameter=1.2, + solenoid_valve=d.Device(name="Solenoid Left"), + lick_sensor=d.Device( + name="Lick-o-meter Left", + ), + ), + d.RewardSpout( + name="Right spout", + side=d.SpoutSide.RIGHT, + spout_diameter=1.2, + solenoid_valve=d.Device(name="Solenoid Right"), + lick_sensor=d.Device( + name="Lick-o-meter Right", + ), + ), + ] +) + +additional_device = d.Device(name="Photometry Clock") + +calibration = d.Calibration( + calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), + device_name="470nm LED", + description="LED calibration", + input={"Power setting": [1, 2, 3]}, + output={"Power mW": [5, 10, 13]}, +) + +instrument = r.Instrument( + instrument_id="428_FIP1_20231003", + modification_date=date(2023, 10, 3), + modalities=[Modality.FIB], + components=[ + camera_assembly_1, + camera_assembly_2, + patch_cord, + light_source_1, + light_source_2, + light_source_3, + detector_1, + detector_2, + objective, + filter_1, + filter_2, + filter_3, + filter_4, + filter_5, + filter_6, + filter_7, + filter_8, + filter_9, + lens, + daq, + stimulus_device, + additional_device, + ], + mouse_platform=mouse_platform, + calibrations=[calibration], +) + +serialized = instrument.model_dump_json() +deserialized = r.Instrument.model_validate_json(serialized) +deserialized.write_standard_file(prefix="fip_ophys") diff --git a/examples/fip_ophys_rig.py b/examples/fip_ophys_rig.py deleted file mode 100644 index 98213e3dc..000000000 --- a/examples/fip_ophys_rig.py +++ /dev/null @@ -1,303 +0,0 @@ -""" example FIP ophys rig """ - -from datetime import date, datetime, timezone - -from aind_data_schema_models.modalities import Modality -from aind_data_schema_models.units import FrequencyUnit, SizeUnit - -import aind_data_schema.components.devices as d -import aind_data_schema.core.rig as r - -rig = r.Rig( - rig_id="428_FIP1_20231003", - modification_date=date(2023, 10, 3), - modalities=[Modality.FIB], - cameras=[ - d.CameraAssembly( - name="BehaviorVideography_FaceSide", - camera_target=d.CameraTarget.FACE_SIDE_LEFT, - camera=d.Camera( - name="Side face camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 1", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - d.CameraAssembly( - name="BehaviorVideography_FaceBottom", - camera_target=d.CameraTarget.FACE_BOTTOM, - camera=d.Camera( - name="Bottom face Camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 2", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - ], - patch_cords=[ - d.Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=d.Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - light_sources=[ - d.LightEmittingDiode( - name="470nm LED", - manufacturer=d.Organization.THORLABS, - model="M470F3", - wavelength=470, - ), - d.LightEmittingDiode( - name="415nm LED", - manufacturer=d.Organization.THORLABS, - model="M415F3", - wavelength=415, - ), - d.LightEmittingDiode( - name="565nm LED", - manufacturer=d.Organization.THORLABS, - model="M565F3", - wavelength=565, - ), - ], - detectors=[ - d.Detector( - name="Green CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", - detector_type="Camera", - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_offset_x=0, - crop_offset_y=0, - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ), - d.Detector( - name="Red CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", - detector_type="Camera", - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_offset_x=0, - crop_offset_y=0, - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ), - ], - objectives=[ - d.Objective( - name="Objective", - serial_number="128022336", - manufacturer=d.Organization.NIKON, - model="CFI Plan Apochromat Lambda D 10x", - numerical_aperture=0.45, - magnification=10, - immersion="air", - ) - ], - filters=[ - d.Filter( - name="Green emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-520/35-25", - filter_type="Band pass", - center_wavelength=520, - diameter=25, - ), - d.Filter( - name="Red emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-600/37-25", - filter_type="Band pass", - center_wavelength=600, - diameter=25, - ), - d.Filter( - name="Emission Dichroic", - model="FF562-Di03-25x36", - manufacturer=d.Organization.SEMROCK, - filter_type="Dichroic", - height=25, - width=36, - cut_off_wavelength=562, - ), - d.Filter( - name="dual-edge standard epi-fluorescence dichroic beamsplitter", - model="FF493/574-Di01-25x36", - manufacturer=d.Organization.SEMROCK, - notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", - filter_type="Multiband", - width=36, - height=24, - ), - d.Filter( - name="Excitation filter 410nm", - manufacturer=d.Organization.THORLABS, - model="FB410-10", - filter_type="Band pass", - diameter=25, - center_wavelength=410, - ), - d.Filter( - name="Excitation filter 470nm", - manufacturer=d.Organization.THORLABS, - model="FB470-10", - filter_type="Band pass", - center_wavelength=470, - diameter=25, - ), - d.Filter( - name="Excitation filter 560nm", - manufacturer=d.Organization.THORLABS, - model="FB560-10", - filter_type="Band pass", - diameter=25, - center_wavelength=560, - ), - d.Filter( - name="450 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-898", - filter_type="Dichroic", - cut_off_wavelength=450, - width=35.6, - height=25.2, - ), - d.Filter( - name="500 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-899", - filter_type="Dichroic", - cut_off_wavelength=500, - width=35.6, - height=23.2, - ), - ], - lenses=[ - d.Lens( - manufacturer=d.Organization.THORLABS, - model="AC254-080-A-ML", - name="Image focusing lens", - focal_length=80, - focal_length_unit=SizeUnit.MM, - size=1, - ) - ], - daqs=[ - d.HarpDevice( - name="Harp Behavior", - harp_device_type=d.HarpDeviceType.BEHAVIOR, - core_version="2.1", - computer_name="behavior_computer", - is_clock_generator=False, - channels=[ - d.DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), - d.DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), - d.DAQChannel(channel_name="DI0", device_name="Lick-o-meter Left", channel_type="Digital Input"), - d.DAQChannel(channel_name="DI1", device_name="Lick-o-meter Right", channel_type="Digital Input"), - d.DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), - ], - ) - ], - mouse_platform=d.Disc(name="mouse_disc", radius=8.5), - stimulus_devices=[ - d.RewardDelivery( - reward_spouts=[ - d.RewardSpout( - name="Left spout", - side=d.SpoutSide.LEFT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Left"), - lick_sensor=d.Device( - name="Lick-o-meter Left", - device_type="Lick detector", - ), - ), - d.RewardSpout( - name="Right spout", - side=d.SpoutSide.RIGHT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Right"), - lick_sensor=d.Device( - name="Lick-o-meter Right", - device_type="Lick detector", - ), - ), - ] - ) - ], - additional_devices=[d.Device(device_type="Photometry Clock", name="Photometry Clock")], - calibrations=[ - d.Calibration( - calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), - device_name="470nm LED", - description="LED calibration", - input={"Power setting": [1, 2, 3]}, - output={"Power mW": [5, 10, 13]}, - ) - ], -) -serialized = rig.model_dump_json() -deserialized = r.Rig.model_validate_json(serialized) -deserialized.write_standard_file(prefix="fip_ophys") diff --git a/examples/mri_session.json b/examples/mri_session.json index 1618b9a8e..9262dee85 100644 --- a/examples/mri_session.json +++ b/examples/mri_session.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", - "schema_version": "1.1.5", + "schema_version": "2.0.0", "protocol_id": [ "dx.doi.org/10.57824/protocols.io.bh7kl4n6" ], @@ -17,8 +17,8 @@ "session_start_time": "2024-03-12T16:27:55.584892Z", "session_end_time": "2024-03-12T16:27:55.584892Z", "session_type": "3D MRI Volume", + "instrument_id": "NA", "ethics_review_id": "1234", - "rig_id": "NA", "calibrations": [], "maintenance": [], "subject_id": "123456", @@ -55,7 +55,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "scanner_location": "Fred Hutch", "magnetic_strength": 7, @@ -95,7 +95,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "scanner_location": "Fred Hutch", "magnetic_strength": 7, diff --git a/examples/mri_session.py b/examples/mri_session.py index 856601d68..05cb09029 100644 --- a/examples/mri_session.py +++ b/examples/mri_session.py @@ -67,7 +67,7 @@ protocol_id=["dx.doi.org/10.57824/protocols.io.bh7kl4n6"], ethics_review_id="1234", session_type="3D MRI Volume", - rig_id="NA", + instrument_id="NA", data_streams=[stream], mouse_platform_name="NA", active_mouse_platform=False, diff --git a/examples/multiplane_ophys_session.json b/examples/multiplane_ophys_session.json index 2125d3248..167d76173 100644 --- a/examples/multiplane_ophys_session.json +++ b/examples/multiplane_ophys_session.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", - "schema_version": "1.1.5", + "schema_version": "2.0.0", "protocol_id": [], "experimenters": [ { @@ -15,8 +15,8 @@ "session_start_time": "2022-07-12T07:00:00Z", "session_end_time": "2022-07-12T07:00:00Z", "session_type": "Mesoscope", + "instrument_id": "MESO.1", "ethics_review_id": "12345", - "rig_id": "MESO.1", "calibrations": [], "maintenance": [], "subject_id": "12345", diff --git a/examples/multiplane_ophys_session.py b/examples/multiplane_ophys_session.py index 19f96d94c..20a255eab 100644 --- a/examples/multiplane_ophys_session.py +++ b/examples/multiplane_ophys_session.py @@ -19,8 +19,8 @@ session_end_time=t, subject_id="12345", session_type="Mesoscope", + instrument_id="MESO.1", ethics_review_id="12345", - rig_id="MESO.1", mouse_platform_name="disc", active_mouse_platform=True, data_streams=[ diff --git a/examples/ophys_procedures.json b/examples/ophys_procedures.json index 14e2fabb5..be9182ed7 100644 --- a/examples/ophys_procedures.json +++ b/examples/ophys_procedures.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/procedures.py", - "schema_version": "1.2.6", + "schema_version": "2.0.0", "subject_id": "625100", "subject_procedures": [ { @@ -100,7 +100,7 @@ "model": null, "path_to_cad": null, "port_index": null, - "additional_settings": {}, + "additional_settings": null, "notes": null, "core_diameter": "200", "core_diameter_unit": "micrometer", diff --git a/examples/ophys_session.json b/examples/ophys_session.json index 13af41e41..dec0278f6 100644 --- a/examples/ophys_session.json +++ b/examples/ophys_session.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/session.py", - "schema_version": "1.1.5", + "schema_version": "2.0.0", "protocol_id": [], "experimenters": [ { @@ -15,8 +15,8 @@ "session_start_time": "2022-07-12T07:00:00Z", "session_end_time": "2022-07-12T07:00:00Z", "session_type": "Parameter Testing", + "instrument_id": "ophys_inst", "ethics_review_id": "2115", - "rig_id": "ophys_rig", "calibrations": [], "maintenance": [], "subject_id": "652567", diff --git a/examples/ophys_session.py b/examples/ophys_session.py index 2cb59865b..7c5fb7822 100644 --- a/examples/ophys_session.py +++ b/examples/ophys_session.py @@ -15,8 +15,8 @@ session_end_time=t, subject_id="652567", session_type="Parameter Testing", + instrument_id="ophys_inst", ethics_review_id="2115", - rig_id="ophys_rig", mouse_platform_name="Disc", active_mouse_platform=False, data_streams=[ diff --git a/examples/procedures.json b/examples/procedures.json index 86e51be85..04c416030 100644 --- a/examples/procedures.json +++ b/examples/procedures.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/procedures.py", - "schema_version": "1.2.6", + "schema_version": "2.0.0", "subject_id": "625100", "subject_procedures": [ { diff --git a/examples/processing.json b/examples/processing.json index 847d3ca4b..90906324b 100644 --- a/examples/processing.json +++ b/examples/processing.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/processing.py", - "schema_version": "1.1.6", + "schema_version": "2.0.0", "processing_pipeline": { "data_processes": [ { diff --git a/examples/quality_control.json b/examples/quality_control.json index ed0a9052b..8054975ad 100644 --- a/examples/quality_control.json +++ b/examples/quality_control.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/quality_control.py", - "schema_version": "1.2.2", + "schema_version": "2.0.0", "evaluations": [ { "modality": { diff --git a/examples/subject.json b/examples/subject.json index 132f5b337..e6ad27bee 100644 --- a/examples/subject.json +++ b/examples/subject.json @@ -1,6 +1,6 @@ { "describedBy": "https://raw.githubusercontent.com/AllenNeuralDynamics/aind-data-schema/main/src/aind_data_schema/core/subject.py", - "schema_version": "1.0.3", + "schema_version": "2.0.0", "subject_id": "12345", "sex": "Male", "date_of_birth": "2022-11-22", diff --git a/pyproject.toml b/pyproject.toml index 172b5eaf0..9ef618ce8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ readme = "README.md" dynamic = ["version"] dependencies = [ - 'aind-data-schema-models>=0.5.4, <1.0.0', + 'aind-data-schema-models>=2.0.0', 'dictdiffer', 'pydantic>=2.7', 'inflection', diff --git a/src/aind_data_schema/components/devices.py b/src/aind_data_schema/components/devices.py index 37ba8388c..a4277b4f8 100644 --- a/src/aind_data_schema/components/devices.py +++ b/src/aind_data_schema/components/devices.py @@ -264,7 +264,7 @@ class MyomatrixArrayType(str, Enum): class Device(DataModel): """Generic device""" - device_type: str = Field(..., title="Device type") # Needs to be set by child classes that inherits + device_type: Literal["device"] = "device" name: str = Field(..., title="Device name") serial_number: Optional[str] = Field(default=None, title="Serial number") manufacturer: Optional[Organization.ONE_OF] = Field(default=None, title="Manufacturer") @@ -273,7 +273,7 @@ class Device(DataModel): default=None, title="Path to CAD diagram", description="For CUSTOM manufactured devices" ) port_index: Optional[str] = Field(default=None, title="Port index") - additional_settings: GenericModelType = Field(GenericModel(), title="Additional parameters") + additional_settings: Optional[GenericModelType] = Field(default=None, title="Additional parameters") notes: Optional[str] = Field(default=None, title="Notes") @@ -449,6 +449,7 @@ class CameraAssembly(DataModel): # required fields name: str = Field(..., title="Camera assembly name") + device_type: Literal["Camera assembly"] = "Camera assembly" camera_target: CameraTarget = Field(..., title="Camera target") camera: Camera = Field(..., title="Camera") lens: Lens = Field(..., title="Lens") @@ -628,6 +629,7 @@ class LaserAssembly(DataModel): """Assembly for optogenetic stimulation""" name: str = Field(..., title="Laser assembly name") + device_type: Literal["Laser assembly"] = "Laser assembly" manipulator: Manipulator = Field(..., title="Manipulator") lasers: List[Laser] = Field(..., title="Lasers connected to this module") collimator: Device = Field(..., title="Collimator") @@ -656,6 +658,7 @@ class EphysAssembly(DataModel): """Module for electrophysiological recording""" name: str = Field(..., title="Ephys assembly name") + device_type: Literal["Ephys assembly"] = "Ephys assembly" manipulator: Manipulator = Field(..., title="Manipulator") probes: List[EphysProbe] = Field(..., title="Probes that are held by this module") @@ -677,6 +680,7 @@ class FiberAssembly(DataModel): """Module for inserted fiber photometry recording""" name: str = Field(..., title="Fiber assembly name") + device_type: Literal["Fiber assembly"] = "Fiber assembly" manipulator: Manipulator = Field(..., title="Manipulator") fibers: List[FiberProbe] = Field(..., title="Probes that are held by this module") @@ -920,6 +924,7 @@ def validate_other(cls, value: Optional[str], info: ValidationInfo) -> Optional[ class ScanningStage(MotorizedStage): """Description of a scanning motorized stages""" + device_type: Literal["Scanning stage"] = "Scanning stage" stage_axis_direction: StageAxisDirection = Field(..., title="Direction of stage axis") stage_axis_name: StageAxisName = Field(..., title="Name of stage axis") @@ -949,6 +954,3 @@ class MyomatrixArray(Device): device_type: Literal["Myomatrix Array"] = "Myomatrix Array" array_type: MyomatrixArrayType = Field(..., title="Array type") - - -LIGHT_SOURCES = Annotated[Union[Laser, LightEmittingDiode, Lamp], Field(discriminator="device_type")] diff --git a/src/aind_data_schema/core/acquisition.py b/src/aind_data_schema/core/acquisition.py index d8faa65b2..7fcbd58a6 100644 --- a/src/aind_data_schema/core/acquisition.py +++ b/src/aind_data_schema/core/acquisition.py @@ -46,7 +46,7 @@ class Acquisition(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/acquisition.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.0.6"]] = Field(default="1.0.6") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") protocol_id: List[str] = Field(default=[], title="Protocol ID", description="DOI for protocols.io") experimenters: List[Person] = Field( default=[], @@ -61,7 +61,7 @@ class Acquisition(DataCoreModel): description="List of calibration measurements taken prior to acquisition.", ) maintenance: List[Maintenance] = Field( - default=[], title="Maintenance", description="List of maintenance on rig prior to acquisition." + default=[], title="Maintenance", description="List of maintenance on instrument prior to acquisition." ) session_start_time: AwareDatetimeWithDefault = Field(..., title="Session start time") session_end_time: AwareDatetimeWithDefault = Field(..., title="Session end time") diff --git a/src/aind_data_schema/core/data_description.py b/src/aind_data_schema/core/data_description.py index 328e3e06f..17c743b19 100644 --- a/src/aind_data_schema/core/data_description.py +++ b/src/aind_data_schema/core/data_description.py @@ -9,11 +9,9 @@ DataRegex, Group, build_data_name, - datetime_from_name_string, ) from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.platforms import Platform from pydantic import Field, SkipValidation, model_validator from aind_data_schema.base import DataCoreModel, DataModel, AwareDatetimeWithDefault @@ -40,14 +38,9 @@ class DataDescription(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/data_description.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.0.5"]] = Field(default="1.0.5") - license: Literal["CC-BY-4.0"] = Field("CC-BY-4.0", title="License") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") + license: Literal["CC-BY-4.0"] = Field(default="CC-BY-4.0", title="License") - platform: Platform.ONE_OF = Field( - ..., - description="Name for a standardized primary data collection system", - title="Platform", - ) subject_id: str = Field( ..., pattern=DataRegex.NO_UNDERSCORES.value, @@ -109,11 +102,11 @@ class DataDescription(DataCoreModel): description="Detail any restrictions on publishing or sharing these data", title="Restrictions", ) - modality: List[Modality.ONE_OF] = Field( + modalities: List[Modality.ONE_OF] = Field( ..., description="A short name for the specific manner, characteristic, pattern of application, or the employment" "of any technology or formal procedure to generate data for a study", - title="Modality", + title="Modalities", ) related_data: List[RelatedData] = Field( default=[], @@ -132,7 +125,7 @@ def parse_name(cls, name): if m is None: raise ValueError(f"name({name}) does not match pattern") - creation_time = datetime_from_name_string(m.group("c_date"), m.group("c_time")) + creation_time = datetime.fromisoformat(m.group("c_datetime")) return dict( label=m.group("label"), @@ -142,11 +135,13 @@ def parse_name(cls, name): @model_validator(mode="after") def build_name(self): """sets the name of the file""" - print(self) - if self.label is not None and self.name is None: - self.name = build_data_name(self.label, creation_datetime=self.creation_time) - elif self.name is None: - raise ValueError("Either label or name must be set") + if self.name is None: + self.name = build_data_name(self.subject_id, creation_datetime=self.creation_time) + + # check that the name matches the name regex + if not re.match(DataRegex.DATA.value, self.name): + raise ValueError(f"Name({self.name}) does not match allowed Regex pattern") + return self @@ -174,7 +169,7 @@ def parse_name(cls, name): if m is None: raise ValueError(f"name({name}) does not match pattern") - creation_time = datetime_from_name_string(m.group("c_date"), m.group("c_time")) + creation_time = datetime.fromisoformat(m.group("c_datetime")) return dict( process_name=m.group("process_name"), @@ -244,8 +239,7 @@ def get_or_default(field_name: str) -> Any: group=get_or_default("group"), investigators=get_or_default("investigators"), restrictions=get_or_default("restrictions"), - modality=get_or_default("modality"), - platform=get_or_default("platform"), + modalities=get_or_default("modalities"), project_name=get_or_default("project_name"), subject_id=get_or_default("subject_id"), related_data=get_or_default("related_data"), @@ -255,7 +249,7 @@ def get_or_default(field_name: str) -> Any: class RawDataDescription(DataDescription): - """A logical collection of data files as acquired from a rig or instrument""" + """A logical collection of data files as acquired from an instrument""" data_level: Literal[DataLevel.RAW] = Field( default=DataLevel.RAW, description="level of processing that data has undergone", title="Data Level" @@ -264,8 +258,7 @@ class RawDataDescription(DataDescription): @model_validator(mode="after") def build_name(self): """sets the name of the file""" - platform_abbreviation = self.platform.abbreviation - self.name = build_data_name(f"{platform_abbreviation}_{self.subject_id}", creation_datetime=self.creation_time) + self.name = build_data_name(f"{self.subject_id}", creation_datetime=self.creation_time) return self @classmethod @@ -277,13 +270,9 @@ def parse_name(cls, name): if m is None: raise ValueError(f"name({name}) does not match pattern") - creation_time = datetime_from_name_string(m.group("c_date"), m.group("c_time")) - - platform_abbreviation = m.group("platform_abbreviation") - platform = Platform.from_abbreviation(platform_abbreviation) + creation_time = datetime.fromisoformat(m.group("c_datetime")) return dict( - platform=platform, subject_id=m.group("subject_id"), creation_time=creation_time, ) @@ -323,7 +312,7 @@ def parse_name(cls, name): if m is None: raise ValueError(f"name({name}) does not match pattern") - creation_time = datetime_from_name_string(m.group("c_date"), m.group("c_time")) + creation_time = datetime.fromisoformat(m.group("c_datetime")) return dict( project_abbreviation=m.group("project_abbreviation"), diff --git a/src/aind_data_schema/core/instrument.py b/src/aind_data_schema/core/instrument.py index c62667805..c820b704f 100644 --- a/src/aind_data_schema/core/instrument.py +++ b/src/aind_data_schema/core/instrument.py @@ -1,27 +1,67 @@ -""" schema describing imaging instrument """ +"""Core Instrument model""" from datetime import date -from typing import List, Literal, Optional +from typing import List, Literal, Optional, Set, Union -from aind_data_schema_models.organizations import Organization -from pydantic import Field, SkipValidation, ValidationInfo, field_validator +from aind_data_schema_models.modalities import Modality +from pydantic import Field, SkipValidation, ValidationInfo, field_serializer, field_validator, model_validator +from typing_extensions import Annotated +from aind_data_schema_models.organizations import Organization from aind_data_schema.base import DataCoreModel, DataModel +from aind_data_schema.components.coordinates import Axis, Origin from aind_data_schema.components.devices import ( - LIGHT_SOURCES, AdditionalImagingDevice, + Calibration, + CameraAssembly, + CameraTarget, DAQDevice, Detector, + Device, + DigitalMicromirrorDevice, Enclosure, + EphysAssembly, + FiberAssembly, Filter, + HarpDevice, ImagingInstrumentType, + Lamp, + Laser, + LaserAssembly, Lens, + LightEmittingDiode, + Monitor, MotorizedStage, + MousePlatform, + NeuropixelsBasestation, Objective, + Olfactometer, + OpenEphysAcquisitionBoard, OpticalTable, + Patch, + PockelsCell, + PolygonalScanner, + RewardDelivery, ScanningStage, + Speaker, ) +# Define the mapping of modalities to their required device types +# The list of list pattern is used to allow for multiple options within a group, so e.g. +# FIB requires a light (one of the options) plus a detector and a patch cord +DEVICES_REQUIRED = { + Modality.ECEPHYS.abbreviation: [EphysAssembly], + Modality.FIB.abbreviation: [[Laser, LightEmittingDiode, Lamp], [Detector], [Patch]], + Modality.POPHYS.abbreviation: [[Laser, LightEmittingDiode, Lamp], [Detector], [Objective]], + Modality.SLAP.abbreviation: [[Laser, LightEmittingDiode, Lamp], [Detector], [Objective]], + Modality.BEHAVIOR_VIDEOS.abbreviation: [CameraAssembly], + Modality.BEHAVIOR.abbreviation: [[Olfactometer, RewardDelivery, Speaker, Monitor]], + Modality.SPIM.abbreviation: [Objective], +} + +MOUSE_PLATFORMS = Annotated[Union[tuple(MousePlatform.__subclasses__())], Field(discriminator="device_type")] +instrument_id_PATTERN = r"^[a-zA-Z0-9]+_[a-zA-Z0-9-]+_\d{8}$" + class Com(DataModel): """Description of a communication system""" @@ -30,70 +70,124 @@ class Com(DataModel): com_port: str = Field(..., title="COM port") +class Connection(DataModel): + """Connection between two devices""" + + device_names: List[str] = Field(..., title="Names of connected devices") + inputs: Optional[List[bool]] = Field(default=None, title="Input status") + outputs: Optional[List[bool]] = Field(default=None, title="Output status") + channels: Optional[List[int]] = Field(default=None, title="Connection channels") + + class Instrument(DataCoreModel): - """Description of an instrument, which is a collection of devices""" + """Description of an instrument""" - _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/instrument.py" + # metametadata + _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/inst.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.0.5"]] = Field(default="1.0.5") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") - instrument_id: Optional[str] = Field( - default=None, - description="Unique instrument identifier, name convention: --", + # instrument definition + instrument_id: str = Field( + ..., + description="Unique instrument identifier, name convention: __", title="Instrument ID", + pattern=instrument_id_PATTERN, ) + mouse_platform: Optional[MOUSE_PLATFORMS] = Field(default=None, title="Mouse platform") modification_date: date = Field(..., title="Date of modification") - instrument_type: ImagingInstrumentType = Field(..., title="Instrument type") - manufacturer: Organization.ONE_OF = Field(..., title="Instrument manufacturer") - temperature_control: Optional[bool] = Field(default=None, title="Temperature control") - optical_tables: List[OpticalTable] = Field(default=[], title="Optical table") - enclosure: Optional[Enclosure] = Field(default=None, title="Enclosure") - objectives: List[Objective] = Field(..., title="Objectives") - detectors: List[Detector] = Field(default=[], title="Detectors") - light_sources: List[LIGHT_SOURCES] = Field(default=[], title="Light sources") - lenses: List[Lens] = Field(default=[], title="Lenses") - fluorescence_filters: List[Filter] = Field(default=[], title="Fluorescence filters") - motorized_stages: List[MotorizedStage] = Field(default=[], title="Motorized stages") - scanning_stages: List[ScanningStage] = Field(default=[], title="Scanning motorized stages") - additional_devices: List[AdditionalImagingDevice] = Field(default=[], title="Additional devices") - calibration_date: Optional[date] = Field( - default=None, - description="Date of most recent calibration", - title="Calibration date", - ) - calibration_data: Optional[str] = Field( + calibrations: Optional[List[Calibration]] = Field(default=None, title="Full calibration of devices") + ccf_coordinate_transform: Optional[str] = Field( default=None, - description="Path to calibration data from most recent calibration", - title="Calibration data", + title="CCF coordinate transform", + description="Path to file that details the CCF-to-lab coordinate transform", ) + origin: Optional[Origin] = Field(default=None, title="Origin point for instrument position transforms") + instrument_axes: Optional[List[Axis]] = Field(default=None, title="Instrument axes", min_length=3, max_length=3) + modalities: List[Modality.ONE_OF] = Field(..., title="Modalities") com_ports: List[Com] = Field(default=[], title="COM ports") - daqs: List[DAQDevice] = Field(default=[], title="DAQ") - notes: Optional[str] = Field(default=None, validate_default=True) + instrument_type: Optional[ImagingInstrumentType] = Field(default=None, title="Instrument type") + manufacturer: Optional[Organization.ONE_OF] = Field(default=None, title="Instrument manufacturer") + temperature_control: Optional[bool] = Field(default=None, title="Temperature control") + notes: Optional[str] = Field(default=None, title="Notes") - @field_validator("daqs", mode="after") - def validate_device_names(cls, value: List[DAQDevice], info: ValidationInfo) -> List[DAQDevice]: - """validate that all DAQ channels are connected to devices that - actually exist - """ - daqs = value - all_devices = ( - info.data["motorized_stages"] - + info.data["scanning_stages"] - + info.data["light_sources"] - + info.data["detectors"] - + info.data["additional_devices"] - + daqs - ) - all_device_names = [device.name for device in all_devices] - for daq in daqs: - for channel in daq.channels: - if channel.device_name not in all_device_names: + connections: List[Connection] = Field( + default=[], + title="Connections", + description="List of all connections between devices in the instrument", + ) + + components: List[ + Annotated[ + Union[ + Monitor, + Olfactometer, + RewardDelivery, + Speaker, + CameraAssembly, + Enclosure, + EphysAssembly, + FiberAssembly, + LaserAssembly, + Patch, + Laser, + LightEmittingDiode, + Lamp, + Detector, + Objective, + Filter, + Lens, + DigitalMicromirrorDevice, + PolygonalScanner, + PockelsCell, + HarpDevice, + NeuropixelsBasestation, + OpenEphysAcquisitionBoard, + OpticalTable, + MotorizedStage, + ScanningStage, + AdditionalImagingDevice, + DAQDevice, + Device, # note that order matters in the Union, DAQDevice and Device should go last + ], + Field(discriminator="device_type"), + ] + ] = Field( + default=[], + title="Components", + description="List of all devices in the rig", + ) + + @field_serializer("modalities", when_used="json") + def serialize_modalities(self, modalities: Set[Modality.ONE_OF]): + """Dynamically serialize modalities based on their type.""" + return sorted(modalities, key=lambda x: x.get("name") if isinstance(x, dict) else x.name) + + @model_validator(mode="after") + def validate_cameras_other(self): + """check if any cameras contain an 'other' field""" + + if self.notes is None: + for component in self.components: + if isinstance(component, CameraAssembly) and component.camera_target == CameraTarget.OTHER: raise ValueError( - f"Device name validation error: '{channel.device_name}' " - + f"is connected to '{channel.channel_name}' on '{daq.name}', but " - + "this device is not part of the rig." + f"Notes cannot be empty if a camera target contains an 'Other' field. " + f"Describe the camera target from ({component.name}) in the notes field" ) - return daqs + + return self + + @field_validator("connections", mode="after") + def validate_device_names(cls, value: List[Connection], info: ValidationInfo) -> List[Connection]: + """validate that all connections map between devices that actually exist""" + device_names = [device.name for device in info.data.get("components", [])] + + for connection in value: + for device_name in connection.device_names: + if device_name not in device_names: + raise ValueError(f"Device name validation error: '{device_name}' is not part of the inst.") + + return value @field_validator("notes", mode="after") def validate_other(cls, value: Optional[str], info: ValidationInfo) -> Optional[str]: @@ -108,3 +202,57 @@ def validate_other(cls, value: Optional[str], info: ValidationInfo) -> Optional[ "Notes cannot be empty if manufacturer is Other. Describe the manufacturer in the notes field." ) return value + + @model_validator(mode="after") + def validate_modalities(cls, value): + """ + Validate that devices exist for the modalities specified. + + Args: + cls: The class being validated. + value: The set of modalities to validate. + info: Validation information, including other fields. + + Returns: + The validated set of modalities. + + Raises: + ValueError: If a required device type is missing for any modality. + """ + + # Return if there are no modalities listed, this is largely for testing + if len(value.modalities) == 0: + return value + + # Retrieve the components from the validation info + components = value.components + errors = [] + + # Validate each modality + for modality in value.modalities: + required_device_groups = DEVICES_REQUIRED.get(modality.abbreviation) + if not required_device_groups: + # Skip modalities that don't require validation + continue + + # Check each group of required devices + for required_group in required_device_groups: + if not isinstance(required_group, list): + required_group = [required_group] + + # Check if at least one required device is present + if not any( + any(isinstance(component, device_type) for device_type in required_group) + for component in components + ): + errors.append( + f"Device type validation error: modality '{modality.abbreviation}' " + "requires at least one device of type(s) " + ) + errors.append(f"{', '.join(device.__name__ for device in required_group)} in the rig components.") + + # Raise an error if there are validation issues + if errors: + raise ValueError("\n".join(errors)) + + return value diff --git a/src/aind_data_schema/core/metadata.py b/src/aind_data_schema/core/metadata.py index e9ade103a..b268869c5 100644 --- a/src/aind_data_schema/core/metadata.py +++ b/src/aind_data_schema/core/metadata.py @@ -8,8 +8,7 @@ from typing import Dict, List, Literal, Optional, get_args from uuid import UUID, uuid4 -from aind_data_schema_models.modalities import ExpectedFiles, FileRequirement -from aind_data_schema_models.platforms import Platform +from aind_data_schema_models.modalities import ExpectedFiles, FileRequirement, Modality from pydantic import ( Field, PrivateAttr, @@ -24,24 +23,21 @@ from aind_data_schema.base import DataCoreModel, is_dict_corrupt, AwareDatetimeWithDefault from aind_data_schema.core.acquisition import Acquisition from aind_data_schema.core.data_description import DataDescription -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.instrument import Instrument from aind_data_schema.core.session import Session from aind_data_schema.core.subject import Subject -from aind_data_schema.utils.compatibility_check import RigSessionCompatibility +from aind_data_schema.utils.compatibility_check import InstrumentSessionCompatibility CORE_FILES = [ "subject", "data_description", "procedures", - "session", - "rig", + "instrument", "processing", "acquisition", - "instrument", "quality_control", ] @@ -72,7 +68,7 @@ class Metadata(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/metadata.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.1.7"]] = Field(default="1.1.7") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") id: UUID = Field( default_factory=uuid4, alias="_id", @@ -121,14 +117,13 @@ class Metadata(DataCoreModel): default=None, title="Procedures", description="All procedures performed on a subject." ) session: Optional[Session] = Field(default=None, title="Session", description="Description of a session.") - rig: Optional[Rig] = Field(default=None, title="Rig", description="Rig.") + instrument: Optional[Instrument] = Field( + default=None, title="Instrument", description="Devices used to acquire data." + ) processing: Optional[Processing] = Field(default=None, title="Processing", description="All processes run on data.") acquisition: Optional[Acquisition] = Field( default=None, title="Acquisition", description="Imaging acquisition session" ) - 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" ) @@ -217,7 +212,7 @@ def validate_metadata(self): def validate_expected_files_by_modality(self): """Validator checks that all required/excluded files match the metadata model""" if self.data_description: - modalities = self.data_description.modality + modalities = self.data_description.modalities requirement_dict = {} @@ -231,7 +226,7 @@ def validate_expected_files_by_modality(self): if file not in requirement_dict: requirement_dict[file] = (abbreviation, file_requirement) else: - (prev_modality, prev_requirement) = requirement_dict[file] + (_, prev_requirement) = requirement_dict[file] if (file_requirement == FileRequirement.REQUIRED) or ( file_requirement == FileRequirement.OPTIONAL @@ -260,7 +255,7 @@ def validate_smartspim_metadata(self): if ( self.data_description - and self.data_description.platform == Platform.SMARTSPIM + and any([modality == Modality.SPIM for modality in self.data_description.modalities]) and self.procedures and any( isinstance(surgery, Injection) and getattr(surgery, "injection_materials", None) is None @@ -278,7 +273,7 @@ def validate_ecephys_metadata(self): """Validator for metadata""" if ( self.data_description - and self.data_description.platform == Platform.ECEPHYS + and any([modality == Modality.ECEPHYS for modality in self.data_description.modalities]) and self.procedures and any( isinstance(surgery, Injection) and getattr(surgery, "injection_materials", None) is None @@ -291,10 +286,10 @@ def validate_ecephys_metadata(self): return self @model_validator(mode="after") - def validate_rig_session_compatibility(self): + def validate_instrument_session_compatibility(self): """Validator for metadata""" - if self.rig and self.session: - check = RigSessionCompatibility(self.rig, self.session) + if self.instrument and self.session: + check = InstrumentSessionCompatibility(self.instrument, self.session) check.run_compatibility_check() return self diff --git a/src/aind_data_schema/core/model.py b/src/aind_data_schema/core/model.py index a029d0c0c..2bb563cbd 100644 --- a/src/aind_data_schema/core/model.py +++ b/src/aind_data_schema/core/model.py @@ -57,13 +57,13 @@ class Model(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/model.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: Literal["0.0.1"] = Field(default="0.0.1") + schema_version: Literal["2.0.0"] = Field(default="2.0.0") name: str = Field(..., title="Name") license: str = Field(..., title="License") developers: Optional[List[Person]] = Field(default=None, title="Name of developer(s)") developer_institution: Optional[Organization.ONE_OF] = Field(default=None, title="Institute where developed") - modality: List[Modality.ONE_OF] = Field(..., title="Modality") + modalities: List[Modality.ONE_OF] = Field(..., title="Modalities") architecture: ModelArchitecture = Field(..., title="Model architecture") intended_use: str = Field(..., title="Intended model use", description="Semantic description of intended use") limitations: Optional[str] = Field(default=None, title="Model limitations") diff --git a/src/aind_data_schema/core/procedures.py b/src/aind_data_schema/core/procedures.py index 4382f1dd8..c5ec45bad 100644 --- a/src/aind_data_schema/core/procedures.py +++ b/src/aind_data_schema/core/procedures.py @@ -5,7 +5,7 @@ from enum import Enum from typing import List, Literal, Optional, Set, Union -from aind_data_schema_models.mouse_anatomy import MouseAnatomicalStructure +from aind_data_schema_models.mouse_anatomy import MouseAnatomyModel from aind_data_schema_models.organizations import Organization from aind_data_schema_models.pid_names import PIDName from aind_data_schema_models.species import Species @@ -328,7 +328,9 @@ class CatheterImplant(DataModel): catheter_material: CatheterMaterial = Field(..., title="Catheter material") catheter_design: CatheterDesign = Field(..., title="Catheter design") catheter_port: CatheterPort = Field(..., title="Catheter port") - targeted_structure: MouseAnatomicalStructure.BLOOD_VESSELS = Field(..., title="Targeted blood vessel") + targeted_structure: MouseAnatomyModel = Field( + ..., title="Targeted blood vessel", description="Use options from MouseBloodVessels" + ) class Craniotomy(DataModel): @@ -613,9 +615,9 @@ class WaterRestriction(DataModel): class MyomatrixContact(DataModel): """ "Description of a contact on a myomatrix thread""" - body_part: MouseAnatomicalStructure.BODY_PARTS = Field(..., title="Body part of contact insertion") + body_part: MouseAnatomyModel = Field(..., title="Body part of contact insertion", description="Use MouseBodyParts") side: Side = Field(..., title="Body side") - muscle: MouseAnatomicalStructure.EMG_MUSCLES = Field(..., title="Muscle of contact insertion") + muscle: MouseAnatomyModel = Field(..., title="Muscle of contact insertion", description="Use MouseEmgMuscles") in_muscle: bool = Field(..., title="In muscle") notes: Optional[str] = Field(default=None, title="Notes") @@ -623,7 +625,9 @@ class MyomatrixContact(DataModel): class MyomatrixThread(DataModel): """Description of a thread of a myomatrix array""" - ground_electrode_location: MouseAnatomicalStructure.BODY_PARTS = Field(..., title="Location of ground electrode") + ground_electrode_location: MouseAnatomyModel = Field( + ..., title="Location of ground electrode", description="Use MouseBodyParts" + ) contacts: List[MyomatrixContact] = Field(..., title="Contacts") @@ -704,7 +708,7 @@ class Procedures(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/procedures.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.2.6"]] = Field(default="1.2.6") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") subject_id: str = Field( ..., description="Unique identifier for the subject. If this is not a Allen LAS ID, indicate this in the Notes.", diff --git a/src/aind_data_schema/core/processing.py b/src/aind_data_schema/core/processing.py index 39f5d307d..d3f8a3c23 100644 --- a/src/aind_data_schema/core/processing.py +++ b/src/aind_data_schema/core/processing.py @@ -123,7 +123,7 @@ class Processing(DataCoreModel): _DESCRIBED_BY_URL: str = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/processing.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.1.6"]] = Field(default="1.1.6") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") processing_pipeline: PipelineProcess = Field( ..., description="Pipeline used to process data", title="Processing Pipeline" diff --git a/src/aind_data_schema/core/quality_control.py b/src/aind_data_schema/core/quality_control.py index 72d6e64ae..bd9435bf4 100644 --- a/src/aind_data_schema/core/quality_control.py +++ b/src/aind_data_schema/core/quality_control.py @@ -199,7 +199,7 @@ class QualityControl(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/quality_control.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.2.2"]] = Field(default="1.2.2") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") evaluations: List[QCEvaluation] = Field(..., title="Evaluations") notes: Optional[str] = Field(default=None, title="Notes") diff --git a/src/aind_data_schema/core/rig.py b/src/aind_data_schema/core/rig.py deleted file mode 100644 index 1e594aa6a..000000000 --- a/src/aind_data_schema/core/rig.py +++ /dev/null @@ -1,254 +0,0 @@ -"""Core Rig model""" - -from datetime import date -from typing import List, Literal, Optional, Set, Union - -from aind_data_schema_models.modalities import Modality -from pydantic import Field, SkipValidation, ValidationInfo, field_serializer, field_validator, model_validator -from typing_extensions import Annotated - -from aind_data_schema.base import DataCoreModel -from aind_data_schema.components.coordinates import Axis, Origin -from aind_data_schema.components.devices import ( - LIGHT_SOURCES, - Calibration, - CameraAssembly, - CameraTarget, - DAQDevice, - Detector, - Device, - DigitalMicromirrorDevice, - Enclosure, - EphysAssembly, - FiberAssembly, - Filter, - HarpDevice, - LaserAssembly, - Lens, - Monitor, - MousePlatform, - NeuropixelsBasestation, - Objective, - Olfactometer, - OpenEphysAcquisitionBoard, - Patch, - PockelsCell, - PolygonalScanner, - RewardDelivery, - Speaker, -) - -MOUSE_PLATFORMS = Annotated[Union[tuple(MousePlatform.__subclasses__())], Field(discriminator="device_type")] -STIMULUS_DEVICES = Annotated[Union[Monitor, Olfactometer, RewardDelivery, Speaker], Field(discriminator="device_type")] -RIG_DAQ_DEVICES = Annotated[ - Union[HarpDevice, NeuropixelsBasestation, OpenEphysAcquisitionBoard, DAQDevice], Field(discriminator="device_type") -] -RIG_ID_PATTERN = r"^[a-zA-Z0-9]+_[a-zA-Z0-9-]+_\d{8}$" - - -class Rig(DataCoreModel): - """Description of a rig""" - - _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/rig.py" - describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.0.5"]] = Field(default="1.0.5") - rig_id: str = Field( - ..., - description="Unique rig identifier, name convention: --", - title="Rig ID", - pattern=RIG_ID_PATTERN, - ) - modification_date: date = Field(..., title="Date of modification") - mouse_platform: MOUSE_PLATFORMS - stimulus_devices: List[STIMULUS_DEVICES] = Field(default=[], title="Stimulus devices") - cameras: List[CameraAssembly] = Field(default=[], title="Camera assemblies") - enclosure: Optional[Enclosure] = Field(default=None, title="Enclosure") - ephys_assemblies: List[EphysAssembly] = Field(default=[], title="Ephys probes") - fiber_assemblies: List[FiberAssembly] = Field(default=[], title="Inserted fiber optics") - stick_microscopes: List[CameraAssembly] = Field(default=[], title="Stick microscopes") - laser_assemblies: List[LaserAssembly] = Field(default=[], title="Laser modules") - patch_cords: List[Patch] = Field(default=[], title="Patch cords") - light_sources: List[LIGHT_SOURCES] = Field(default=[], title="Light sources") - detectors: List[Detector] = Field(default=[], title="Detectors") - objectives: List[Objective] = Field(default=[], title="Objectives") - filters: List[Filter] = Field(default=[], title="Filters") - lenses: List[Lens] = Field(default=[], title="Lenses") - digital_micromirror_devices: List[DigitalMicromirrorDevice] = Field(default=[], title="DMDs") - polygonal_scanners: List[PolygonalScanner] = Field(default=[], title="Polygonal scanners") - pockels_cells: List[PockelsCell] = Field(default=[], title="Pockels cells") - additional_devices: List[Device] = Field(default=[], title="Additional devices") - daqs: List[RIG_DAQ_DEVICES] = Field(default=[], title="Data acquisition devices") - calibrations: List[Calibration] = Field(..., title="Full calibration of devices") - ccf_coordinate_transform: Optional[str] = Field( - default=None, - title="CCF coordinate transform", - description="Path to file that details the CCF-to-lab coordinate transform", - ) - origin: Optional[Origin] = Field(default=None, title="Origin point for rig position transforms") - rig_axes: Optional[List[Axis]] = Field(default=None, title="Rig axes", min_length=3, max_length=3) - modalities: Set[Modality.ONE_OF] = Field(..., title="Modalities") - notes: Optional[str] = Field(default=None, title="Notes") - - @field_serializer("modalities", when_used="json") - def serialize_modalities(self, modalities: Set[Modality.ONE_OF]): - """Dynamically serialize modalities based on their type.""" - return sorted(modalities, key=lambda x: x.get("name") if isinstance(x, dict) else x.name) - - @model_validator(mode="after") - def validate_cameras_other(self): - """check if any cameras contain an 'other' field""" - - if self.notes is None: - for camera_assembly in self.cameras + self.stick_microscopes: - if camera_assembly.camera_target == CameraTarget.OTHER: - raise ValueError( - f"Notes cannot be empty if a camera target contains an 'Other' field. " - f"Describe the camera target from ({camera_assembly.name}) in the notes field" - ) - - return self - - @field_validator("daqs", mode="after") - def validate_device_names(cls, value: List[DAQDevice], info: ValidationInfo) -> List[DAQDevice]: - """validate that all DAQ channels are connected to devices that - actually exist - """ - daqs = value - non_reward_delivery_stimulus_devices = [ - d for d in info.data.get("stimulus_devices", []) if not isinstance(d, RewardDelivery) - ] - standard_devices = ( - daqs - + info.data.get("light_sources", []) - + info.data.get("patch_cords", []) - + info.data.get("detectors", []) - + info.data.get("digital_micromirror_devices", []) - + info.data.get("polygonal_scanners", []) - + info.data.get("pockels_cells", []) - + info.data.get("additional_devices", []) - + non_reward_delivery_stimulus_devices - ) - camera_devices = info.data.get("cameras", []) + info.data.get("stick_microscopes", []) - standard_device_names = [device.name for device in standard_devices] - camera_names = [camera.camera.name for camera in camera_devices] - ephys_assembly_names = [ - probe.name for ephys_assembly in info.data.get("ephys_assemblies", []) for probe in ephys_assembly.probes - ] - laser_assembly_names = [ - laser.name for laser_assembly in info.data.get("laser_assemblies", []) for laser in laser_assembly.lasers - ] - mouse_platform_names = [] if info.data.get("mouse_platform") is None else [info.data["mouse_platform"].name] - reward_deliveries = [d for d in info.data.get("stimulus_devices", []) if isinstance(d, RewardDelivery)] - reward_delivery_device_names = [] - for rd in reward_deliveries: - for rs in rd.reward_spouts: - reward_delivery_device_names += [rs.name, rs.solenoid_valve.name, rs.lick_sensor.name] - - all_device_names = ( - standard_device_names - + camera_names - + ephys_assembly_names - + laser_assembly_names - + mouse_platform_names - + reward_delivery_device_names - ) - - for daq in daqs: - for channel in daq.channels: - if channel.device_name not in all_device_names: - raise ValueError( - f"Device name validation error: '{channel.device_name}' " - + f"is connected to '{channel.channel_name}' on '{daq.name}', but " - + "this device is not part of the rig." - ) - return daqs - - @staticmethod - def _validate_ephys_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate ecephys modality has ephys_assemblies and stick_microscopes""" - errors = [] - if Modality.ECEPHYS in value: - for k, v in { - "ephys_assemblies": len(info.data.get("ephys_assemblies", [])) > 0, - }.items(): - if v is False: - errors.append(f"{k} field must be utilized for Ecephys modality") - return errors - - @staticmethod - def _validate_fib_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate FIB modality has light_sources, detectors, and patch_cords""" - errors = [] - if Modality.FIB in value: - for k, v in { - "light_sources": len(info.data.get("light_sources", [])) > 0, - "detectors": len(info.data.get("detectors", [])) > 0, - "patch_cords": len(info.data.get("patch_cords", [])) > 0, - }.items(): - if v is False: - errors.append(f"{k} field must be utilized for FIB modality") - return errors - - @staticmethod - def _validate_pophys_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate POPHYS modality has light_sources, detectors, and objectives""" - errors = [] - if Modality.POPHYS in value: - for k, v in { - "light_sources": len(info.data.get("light_sources", [])) > 0, - "detectors": len(info.data.get("detectors", [])) > 0, - "objectives": len(info.data.get("objectives", [])) > 0, - }.items(): - if v is False: - errors.append(f"{k} field must be utilized for POPHYS modality") - return errors - - @staticmethod - def _validate_slap_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate SLAP modality has light_sources, detectors, and objectives""" - errors = [] - if Modality.SLAP in value: - for k, v in { - "light_sources": len(info.data.get("light_sources", [])) > 0, - "detectors": len(info.data.get("detectors", [])) > 0, - "objectives": len(info.data.get("objectives", [])) > 0, - }.items(): - if v is False: - errors.append(f"{k} field must be utilized for SLAP modality") - return errors - - @staticmethod - def _validate_behavior_videos_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate BEHAVIOR_VIDEOS modality has cameras""" - errors = [] - if Modality.BEHAVIOR_VIDEOS in value: - if len(info.data.get("cameras", [])) == 0: - errors.append("cameras field must be utilized for Behavior Videos modality") - return errors - - @staticmethod - def _validate_behavior_modality(value: Set[Modality.ONE_OF], info: ValidationInfo) -> List[str]: - """Validate that BEHAVIOR modality has stimulus_devices""" - errors = [] - if Modality.BEHAVIOR in value: - if len(info.data.get("stimulus_devices", [])) == 0: - errors.append("stimulus_devices field must be utilized for Behavior modality") - - return errors - - @field_validator("modalities", mode="after") - def validate_modalities(cls, value: Set[Modality.ONE_OF], info: ValidationInfo) -> Set[Modality.ONE_OF]: - """Validate each modality in modalities field has associated data""" - ephys_errors = cls._validate_ephys_modality(value, info) - fib_errors = cls._validate_fib_modality(value, info) - pophys_errors = cls._validate_pophys_modality(value, info) - slap_errors = cls._validate_slap_modality(value, info) - behavior_vids_errors = cls._validate_behavior_videos_modality(value, info) - behavior_errors = cls._validate_behavior_modality(value, info) - - errors = ephys_errors + fib_errors + pophys_errors + slap_errors + behavior_vids_errors + behavior_errors - if len(errors) > 0: - message = "\n ".join(errors) - raise ValueError(message) - - return value diff --git a/src/aind_data_schema/core/session.py b/src/aind_data_schema/core/session.py index 9a09ff20c..80df9aa4c 100644 --- a/src/aind_data_schema/core/session.py +++ b/src/aind_data_schema/core/session.py @@ -204,7 +204,7 @@ class DomeModule(DataModel): rotation_angle: Optional[Decimal] = Field(default=None, title="Rotation Angle (deg)") coordinate_transform: Optional[str] = Field( default=None, - title="Transform from local manipulator axes to rig", + title="Transform from local manipulator axes to instrument", description="Path to coordinate transform", ) calibration_date: Optional[datetime] = Field( @@ -248,7 +248,7 @@ class LaserConfig(DataModel): """Description of laser settings in a session""" device_type: Literal["Laser"] = "Laser" - name: str = Field(..., title="Name", description="Must match rig json") + name: str = Field(..., title="Name", description="Must match instrument json") wavelength: int = Field(..., title="Wavelength (nm)") wavelength_unit: SizeUnit = Field(default=SizeUnit.NM, title="Wavelength unit") excitation_power: Optional[Decimal] = Field(default=None, title="Excitation power (mW)") @@ -272,7 +272,7 @@ class RewardSolution(str, Enum): class RewardSpoutConfig(DataModel): """Reward spout session information""" - side: SpoutSide = Field(..., title="Spout side", description="Must match rig") + side: SpoutSide = Field(..., title="Spout side", description="Must match instrument") starting_position: RelativePosition = Field(..., title="Starting position") variable_position: bool = Field( ..., @@ -302,7 +302,7 @@ def validate_other(cls, value: Optional[str], info: ValidationInfo) -> Optional[ class SpeakerConfig(DataModel): """Description of auditory speaker configuration""" - name: str = Field(..., title="Name", description="Must match rig json") + name: str = Field(..., title="Name", description="Must match instrument json") volume: Optional[Decimal] = Field(default=None, title="Volume (dB)") volume_unit: Optional[SoundIntensityUnit] = Field(default=None, title="Volume unit") @@ -395,7 +395,7 @@ class Stream(DataModel): stick_microscopes: List[DomeModule] = Field( default=[], title="Stick microscopes", - description="Must match stick microscope assemblies in rig file", + description="Must match stick microscope assemblies in instrument file", ) manipulator_modules: List[ManipulatorModule] = Field(default=[], title="Manipulator modules") detectors: List[DetectorConfig] = Field(default=[], title="Detectors") @@ -546,7 +546,7 @@ class Session(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/session.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.1.5"]] = Field(default="1.1.5") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") protocol_id: List[str] = Field(default=[], title="Protocol ID", description="DOI for protocols.io") experimenters: List[Person] = Field( default=[], @@ -555,17 +555,17 @@ class Session(DataCoreModel): session_start_time: AwareDatetimeWithDefault = Field(..., title="Session start time") session_end_time: Optional[AwareDatetimeWithDefault] = Field(default=None, title="Session end time") session_type: str = Field(..., title="Session type") + instrument_id: str = Field(..., title="Instrument ID") ethics_review_id: Optional[str] = Field(default=None, title="Ethics review ID") - rig_id: str = Field(..., title="Rig ID") calibrations: List[Calibration] = Field( default=[], title="Calibrations", - description="Calibrations of rig devices prior to session", + description="Calibrations of instrument devices prior to session", ) maintenance: List[Maintenance] = Field( default=[], title="Maintenance", - description="Maintenance of rig devices prior to session", + description="Maintenance of instrument devices prior to session", ) subject_id: str = Field(..., title="Subject ID") animal_weight_prior: Optional[Decimal] = Field( diff --git a/src/aind_data_schema/core/subject.py b/src/aind_data_schema/core/subject.py index ed15af1dc..dc52a3d79 100644 --- a/src/aind_data_schema/core/subject.py +++ b/src/aind_data_schema/core/subject.py @@ -89,7 +89,7 @@ class Subject(DataCoreModel): _DESCRIBED_BY_URL = DataCoreModel._DESCRIBED_BY_BASE_URL.default + "aind_data_schema/core/subject.py" describedBy: str = Field(default=_DESCRIBED_BY_URL, json_schema_extra={"const": _DESCRIBED_BY_URL}) - schema_version: SkipValidation[Literal["1.0.3"]] = Field(default="1.0.3") + schema_version: SkipValidation[Literal["2.0.0"]] = Field(default="2.0.0") subject_id: str = Field( ..., description="Unique identifier for the subject. If this is not a Allen LAS ID, indicate this in the Notes.", diff --git a/src/aind_data_schema/utils/compatibility_check.py b/src/aind_data_schema/utils/compatibility_check.py index 97f25a663..9a38d0dca 100644 --- a/src/aind_data_schema/utils/compatibility_check.py +++ b/src/aind_data_schema/utils/compatibility_check.py @@ -2,31 +2,32 @@ from typing import Optional -from aind_data_schema.core.rig import RewardDelivery, Rig +from aind_data_schema.components.devices import CameraAssembly, EphysAssembly +from aind_data_schema.core.instrument import Instrument from aind_data_schema.core.session import Session -class RigSessionCompatibility: - """Class of methods to check compatibility between rig and session""" +class InstrumentSessionCompatibility: + """Class of methods to check compatibility between instrument and session""" - def __init__(self, rig: Rig, session: Session) -> None: - """Initiate RigSessionCompatibility class""" - self.rig = rig + def __init__(self, instrument: Instrument, session: Session) -> None: + """Initiate InstrumentSessionCompatibility class""" + self.inst = instrument self.session = session - def _compare_rig_id(self) -> Optional[ValueError]: - """Compares rig_id""" - if self.session.rig_id != self.rig.rig_id: - return ValueError(f"Rig ID in session {self.session.rig_id} does not match the rig's {self.rig.rig_id}.") + def _compare_instrument_id(self) -> Optional[ValueError]: + """Compares instrument_id""" + if self.session.instrument_id != self.inst.instrument_id: + return ValueError(f"Instrument ID in session {self.session.instrument_id} does not match the instrument's {self.inst.instrument_id}.") # noqa: E501 else: return None def _compare_mouse_platform_name(self) -> Optional[ValueError]: """Compares mouse_platform_name""" - if self.session.mouse_platform_name != self.rig.mouse_platform.name: + if self.session.mouse_platform_name != self.inst.mouse_platform.name: return ValueError( f"Mouse platform name in session {self.session.mouse_platform_name} " - f"does not match the rig's {self.rig.mouse_platform.name}" + f"does not match the instrument's {self.inst.mouse_platform.name}" ) def _compare_daq_names(self) -> Optional[ValueError]: @@ -34,11 +35,11 @@ def _compare_daq_names(self) -> Optional[ValueError]: session_daqs = [ daq for stream in getattr(self.session, "data_streams", []) for daq in getattr(stream, "daq_names", []) ] - rig_daqs = [getattr(daq, "name", None) for daq in getattr(self.rig, "daqs", [])] - if not set(session_daqs).issubset(set(rig_daqs)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + if not set(session_daqs).issubset(set(instrument_component_names)): return ValueError( - f"daq names in session do not match daq names in rig. " - f"session_daqs: {set(session_daqs)} rig_daqs: {set(rig_daqs)}" + f"daq names in session do not match daq names in inst. " + f"session_daqs: {set(session_daqs)} instrument components: {set(instrument_component_names)}" ) def _compare_camera_names(self) -> Optional[ValueError]: @@ -48,15 +49,15 @@ def _compare_camera_names(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for camera in getattr(stream, "camera_names", []) ] - rig_cameras = [ - name - for camera_device in getattr(self.rig, "cameras", []) - for name in (camera_device.camera.name, camera_device.name) + instrument_camera_names = [ + comp.camera.name if isinstance(comp, CameraAssembly) else None + for comp in getattr(self.inst, "components", []) ] - if not set(session_cameras).issubset(set(rig_cameras)): + + if not set(session_cameras).issubset(set(instrument_camera_names)): return ValueError( - f"camera names in session do not match camera names in rig. " - f"session_cameras: {set(session_cameras)} rig_cameras: {set(rig_cameras)}" + f"camera names in session do not match camera names in inst. " + f"session_cameras: {set(session_cameras)} instrument_cameras: {set(instrument_camera_names)}" ) def _compare_light_sources(self) -> Optional[ValueError]: @@ -66,13 +67,12 @@ def _compare_light_sources(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for light_source in getattr(stream, "light_sources", []) ] - rig_light_sources = [ - getattr(light_source, "name", None) for light_source in getattr(self.rig, "light_sources", []) - ] - if not set(session_light_sources).issubset(set(rig_light_sources)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + if not set(session_light_sources).issubset(set(instrument_component_names)): return ValueError( - f"light source names in session do not match light source names in rig. " - f"session_light_sources: {set(session_light_sources)} rig_light_sources: {set(rig_light_sources)}" + f"light source names in session do not match light source names in inst. " + f"session_light_sources: {set(session_light_sources)} " + f"instrument_light_sources: {set(instrument_component_names)}" ) def _compare_ephys_assemblies(self) -> Optional[ValueError]: @@ -82,12 +82,15 @@ def _compare_ephys_assemblies(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for ephys_module in getattr(stream, "ephys_modules") ] - rig_ephys_assemblies = [ephys_assembly.name for ephys_assembly in getattr(self.rig, "ephys_assemblies", [])] - if not set(session_ephys_assemblies).issubset(set(rig_ephys_assemblies)): + instrument_component_names = [ + comp.name if isinstance(comp, EphysAssembly) else None for comp in getattr(self.inst, "components", []) + ] + + if not set(session_ephys_assemblies).issubset(set(instrument_component_names)): return ValueError( - f"ephys assembly names in session do not match ephys assembly names in rig. " + f"ephys assembly names in session do not match ephys assembly names in inst. " f"session_ephys_assemblies: {set(session_ephys_assemblies)}" - f" rig_ephys_assemblies: {set(rig_ephys_assemblies)}" + f" instrument_ephys_assemblies: {set(instrument_component_names)}" ) def _compare_stick_microscopes(self) -> Optional[ValueError]: @@ -97,16 +100,16 @@ def _compare_stick_microscopes(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for stick_microscope in getattr(stream, "stick_microscopes", []) ] - rig_stick_microscopes = [ - name - for camera_device in getattr(self.rig, "stick_microscopes", []) - for name in (camera_device.camera.name, camera_device.name) + instrument_camera_names = [ + comp.camera.name if isinstance(comp, CameraAssembly) else None + for comp in getattr(self.inst, "components", []) ] - if not set(session_stick_microscopes).issubset(set(rig_stick_microscopes)): + + if not set(session_stick_microscopes).issubset(set(instrument_camera_names)): return ValueError( - f"stick microscope names in session do not match stick microscope names in rig. " + f"stick microscope names in session do not match stick microscope names in inst. " f"session_stick_microscopes: {set(session_stick_microscopes)} " - f"rig_stick_microscopes: {set(rig_stick_microscopes)}" + f"instrument_stick_microscopes: {set(instrument_camera_names)}" ) def _compare_manipulator_modules(self) -> Optional[ValueError]: @@ -116,12 +119,13 @@ def _compare_manipulator_modules(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for manipulator_module in getattr(stream, "manipulator_modules", []) ] - rig_manipulator_modules = [laser_assembly.name for laser_assembly in getattr(self.rig, "laser_assemblies", [])] - if not set(session_manipulator_modules).issubset(set(rig_manipulator_modules)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + + if not set(session_manipulator_modules).issubset(set(instrument_component_names)): return ValueError( - f"manipulator module names in session do not match manipulator names (laser assemblies) in rig. " + f"manipulator module names in session do not match manipulator names (laser assemblies) in inst. " f"session_manipulators: {set(session_manipulator_modules)}" - f" rig_manipulators: {set(rig_manipulator_modules)}" + f" instrument_manipulators: {set(instrument_component_names)}" ) def _compare_detectors(self) -> Optional[ValueError]: @@ -131,11 +135,12 @@ def _compare_detectors(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for detector in getattr(stream, "detectors", []) ] - rig_detectors = [detector.name for detector in getattr(self.rig, "detectors", [])] - if not set(session_detectors).issubset(set(rig_detectors)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + + if not set(session_detectors).issubset(set(instrument_component_names)): return ValueError( - f"detector names in session do not match detector names in rig. " - f"session_detectors: {set(session_detectors)} rig_detectors: {set(rig_detectors)}" + f"detector names in session do not match detector names in inst. " + f"session_detectors: {set(session_detectors)} instrument_detectors: {set(instrument_component_names)}" ) def _compare_patch_cords(self) -> Optional[ValueError]: @@ -145,11 +150,12 @@ def _compare_patch_cords(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for fiber_connection in getattr(stream, "fiber_connections", []) ] - rig_patch_cords = [patch_cord.name for patch_cord in getattr(self.rig, "patch_cords", [])] - if not set(session_patch_cords).issubset(set(rig_patch_cords)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + + if not set(session_patch_cords).issubset(set(instrument_component_names)): return ValueError( - f"patch cord names in session do not match patch cord names in rig. " - f"session_patch_cords: {set(session_patch_cords)} rig_patch_cords: {set(rig_patch_cords)}" + f"patch cord names in session do not match patch cord names in inst. " + f"session_patch_cords: {set(session_patch_cords)} instrument_patch_cords: {set(instrument_component_names)}" # noqa: E501 ) def _compare_fiber_modules(self) -> Optional[ValueError]: @@ -159,11 +165,13 @@ def _compare_fiber_modules(self) -> Optional[ValueError]: for stream in getattr(self.session, "data_streams", []) for fiber_module in getattr(stream, "fiber_modules", []) ] - rig_fiber_modules = [fiber_assembly.name for fiber_assembly in getattr(self.rig, "fiber_assemblies", [])] - if not set(session_fiber_modules).issubset(set(rig_fiber_modules)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + + if not set(session_fiber_modules).issubset(set(instrument_component_names)): return ValueError( - f"fiber module names in session do not match fiber assembly names in rig. " - f"session_fiber_modules: {set(session_fiber_modules)} rig_fiber_assemblies: {set(rig_fiber_modules)}" + f"fiber module names in session do not match fiber assembly names in inst. " + f"session_fiber_modules: {set(session_fiber_modules)} " + f"instrument_fiber_assemblies: {set(instrument_component_names)}" ) def _compare_stimulus_devices(self) -> Optional[ValueError]: @@ -173,27 +181,19 @@ def _compare_stimulus_devices(self) -> Optional[ValueError]: for stimulus_epoch in getattr(self.session, "stimulus_epochs", []) for stimulus_device_name in getattr(stimulus_epoch, "stimulus_device_names") ] - rig_stimulus_devices = [ - stimulus_device.name - for stimulus_device in getattr(self.rig, "stimulus_devices", []) - if not isinstance(stimulus_device, RewardDelivery) - ] + [ - reward_spout.name - for stimulus_device in getattr(self.rig, "stimulus_devices", []) - if isinstance(stimulus_device, RewardDelivery) - for reward_spout in getattr(stimulus_device, "reward_spouts", []) - ] - if not set(session_stimulus_devices).issubset(set(rig_stimulus_devices)): + instrument_component_names = [getattr(comp, "name", None) for comp in getattr(self.inst, "components", [])] + + if not set(session_stimulus_devices).issubset(set(instrument_component_names)): return ValueError( - f"stimulus device names in session do not match stimulus device names in rig. " + f"stimulus device names in session do not match stimulus device names in inst. " f"session_stimulus_devices: {set(session_stimulus_devices)} " - f"rig_stimulus_devices: {set(rig_stimulus_devices)}" + f"instrument_stimulus_devices: {set(instrument_component_names)}" ) def run_compatibility_check(self) -> None: - """Runs compatibility check.Creates a dictionary of fields and whether it matches in rig and session.""" + """Runs compatibility check.Creates a dictionary of fields and whether it matches in instrument and session.""" comparisons = [ - self._compare_rig_id(), + self._compare_instrument_id(), self._compare_mouse_platform_name(), self._compare_daq_names(), self._compare_camera_names(), diff --git a/src/aind_data_schema/utils/diagrams.py b/src/aind_data_schema/utils/diagrams.py index ee9f81526..cbe14122f 100644 --- a/src/aind_data_schema/utils/diagrams.py +++ b/src/aind_data_schema/utils/diagrams.py @@ -14,7 +14,7 @@ # Import all modules in core package for mod in core.__loader__.get_resource_reader().contents(): if "__" not in mod: - importlib.import_module(f"aind_data_schema.core.{mod.replace('.py','')}") + importlib.import_module(f"aind_data_schema.core.{mod.replace('.py', '')}") def save_diagram( diff --git a/src/aind_data_schema/utils/json_writer.py b/src/aind_data_schema/utils/json_writer.py index 5b9a3cbc7..a374c3682 100644 --- a/src/aind_data_schema/utils/json_writer.py +++ b/src/aind_data_schema/utils/json_writer.py @@ -14,7 +14,7 @@ # Import all modules in core package for mod in core.__loader__.get_resource_reader().contents(): if "__" not in mod: - importlib.import_module(f"aind_data_schema.core.{mod.replace('.py','')}") + importlib.import_module(f"aind_data_schema.core.{mod.replace('.py', '')}") class SchemaWriter: diff --git a/src/aind_data_schema/utils/schema_version_bump.py b/src/aind_data_schema/utils/schema_version_bump.py index 201ff13e9..2360f7ec3 100644 --- a/src/aind_data_schema/utils/schema_version_bump.py +++ b/src/aind_data_schema/utils/schema_version_bump.py @@ -8,7 +8,7 @@ import dictdiffer import semver -from aind_data_schema.base import AindCoreModel +from aind_data_schema.base import DataCoreModel from aind_data_schema.utils.json_writer import SchemaWriter CURRENT_DIR = Path(os.path.dirname(os.path.realpath(__file__))) @@ -34,12 +34,12 @@ def __init__(self, commit_message: str = "", json_schemas_location: Path = Path( self.commit_message = commit_message self.json_schemas_location = json_schemas_location - def _get_schema_json(self, model: AindCoreModel) -> dict: + def _get_schema_json(self, model: DataCoreModel) -> dict: """ Get the json schema of a model Parameters ---------- - model : AindCoreModel + model : DataCoreModel The model to get the json schema of Returns @@ -58,14 +58,14 @@ def _get_schema_json(self, model: AindCoreModel) -> dict: raise FileNotFoundError(f"Schema file not found: {main_branch_schema_path}") return main_branch_schema_contents - def _get_list_of_models_that_changed(self) -> List[AindCoreModel]: + def _get_list_of_models_that_changed(self) -> List[DataCoreModel]: """ Get a list of core models that have been updated by comparing the json schema of the models to the json schema in the schemas folder. Returns ------- - List[AindCoreModel] - A list of AindCoreModels that changed. + List[DataCoreModel] + A list of DataCoreModels that changed. """ schemas_that_need_updating = [] for core_model in SchemaWriter.get_schemas(): @@ -81,18 +81,18 @@ def _get_list_of_models_that_changed(self) -> List[AindCoreModel]: print(f"Schemas that need updating: {[model.__name__ for model in schemas_that_need_updating]}") return schemas_that_need_updating - def _get_incremented_versions_map(self, models_that_changed: List[AindCoreModel]) -> Dict[AindCoreModel, str]: + def _get_incremented_versions_map(self, models_that_changed: List[DataCoreModel]) -> Dict[DataCoreModel, str]: """ Parameters ---------- - models_that_changed : List[AindCoreModel] + models_that_changed : List[DataCoreModel] A list of models that have been updated and need to have their version numbers incremented. Returns ------- - Dict[AindCoreModel, str] - A mapping of the AindCoreModel to its new version number. + Dict[DataCoreModel, str] + A mapping of the DataCoreModel to its new version number. """ version_bump_map = {} @@ -169,13 +169,13 @@ def _write_new_file(new_file_contents: list, python_file_path: str) -> None: for line in new_file_contents: f.write(line) - def _update_files(self, version_bump_map: Dict[AindCoreModel, str]) -> None: + def _update_files(self, version_bump_map: Dict[DataCoreModel, str]) -> None: """ Using the information in the version_bump_map, will update the python files in the core directory. Parameters ---------- - version_bump_map : Dict[AindCoreModel, str] + version_bump_map : Dict[DataCoreModel, str] The models that need updating are in the dictionary keys and the new version number is the dictionary value. diff --git a/tests/resources/ephys_instrument.py b/tests/resources/ephys_instrument.py new file mode 100644 index 000000000..ab9f33879 --- /dev/null +++ b/tests/resources/ephys_instrument.py @@ -0,0 +1,300 @@ +"""Generates an example JSON file for an ephys rig""" + +from datetime import date, datetime, timezone + +from aind_data_schema_models.harp_types import HarpDeviceType +from aind_data_schema_models.modalities import Modality +from aind_data_schema_models.organizations import Organization +from aind_data_schema_models.units import FrequencyUnit, SizeUnit + +from aind_data_schema.components.devices import ( + Calibration, + Camera, + CameraAssembly, + CameraTarget, + DAQChannel, + Device, + Disc, + EphysAssembly, + EphysProbe, + Filter, + HarpDevice, + Laser, + LaserAssembly, + Lens, + Manipulator, + NeuropixelsBasestation, + Patch, + ProbePort, +) +from aind_data_schema.core.instrument import Instrument + +# Describes a rig with running wheel, 2 behavior cameras, one Harp Behavior board, +# one dual-color laser module, one stick microscope, and 2 Neuropixels probes + +behavior_computer = "W10DT72941" +ephys_computer = "W10DT72942" + +running_wheel = Disc(name="Running Wheel", radius=15) + +digital_out0 = DAQChannel(channel_name="DO0", device_name="Face Camera", channel_type="Digital Output") + +digital_out1 = DAQChannel(channel_name="DO1", device_name="Body Camera", channel_type="Digital Output") + +analog_input = DAQChannel(channel_name="AI0", device_name="Running Wheel", channel_type="Analog Input") + +harp = HarpDevice( + name="Harp Behavior", + harp_device_type=HarpDeviceType.BEHAVIOR, + core_version="2.1", + computer_name=behavior_computer, + channels=[digital_out0, digital_out1, analog_input], + is_clock_generator=False, +) + +port1 = ProbePort(index=1, probes=["Probe A"]) + +port2 = ProbePort(index=2, probes=["Probe B"]) + +basestation = NeuropixelsBasestation( + name="Basestation Slot 3", + basestation_firmware_version="2.019", + bsc_firmware_version="2.199", + slot=3, + ports=[port1, port2], + computer_name=ephys_computer, +) + +red_laser = Laser(name="Red Laser", wavelength=473, manufacturer=Organization.OXXIUS) + +blue_laser = Laser(name="Blue Laser", wavelength=638, manufacturer=Organization.OXXIUS) + +laser_assembly = LaserAssembly( + name="Laser_assemblyA", + manipulator=Manipulator( + name="Manipulator A", serial_number="SN2937", manufacturer=Organization.NEW_SCALE_TECHNOLOGIES + ), + lasers=[red_laser, blue_laser], + collimator=Device(name="Collimator A"), + fiber=Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, + ), +) + +probe_camera_1 = Camera( + name="stick microscope 1", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=ephys_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Color", +) + +probe_camera_2 = Camera( + name="stick microscope 2", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=ephys_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Color", +) + +probe_camera_3 = Camera( + name="stick microscope 3", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=ephys_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Color", +) + +probe_camera_4 = Camera( + name="stick microscope 4", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=ephys_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Color", +) + +stick_lens = Lens(name="Probe lens", manufacturer=Organization.EDMUND_OPTICS) + +microscope_1 = CameraAssembly( + name="Stick_assembly_1", + camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS TO BE FILLED OUT + camera=probe_camera_1, + lens=stick_lens, +) + +microscope_2 = CameraAssembly( + name="Stick_assembly_2", + camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS TO BE FILLED OUT + camera=probe_camera_2, + lens=stick_lens, +) + +microscope_3 = CameraAssembly( + name="Stick_assembly_3", + camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS TO BE FILLED OUT + camera=probe_camera_3, + lens=stick_lens, +) + +microscope_4 = CameraAssembly( + name="Stick_assembly_4", + camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS TO BE FILLED OUT + camera=probe_camera_4, + lens=stick_lens, +) + +probeA = EphysProbe(name="Probe A", serial_number="9291019", probe_model="Neuropixels 1.0") + +probeB = EphysProbe(name="Probe B", serial_number="9291020", probe_model="Neuropixels 1.0") + +ephys_assemblyA = EphysAssembly( + name="Ephys_assemblyA", + manipulator=Manipulator( + name="Manipulator 1", serial_number="SN2938", manufacturer=Organization.NEW_SCALE_TECHNOLOGIES + ), + probes=[probeA], +) + +ephys_assemblyB = EphysAssembly( + name="Ephys_assemblyB", + manipulator=Manipulator( + name="Manipulator B", serial_number="SN2939", manufacturer=Organization.NEW_SCALE_TECHNOLOGIES + ), + probes=[probeB], +) + +filt = Filter( + name="LP filter", + filter_type="Long pass", + manufacturer=Organization.THORLABS, + description="850 nm longpass filter", +) + +lens = Lens( + name="Camera lens", + focal_length=15, + focal_length_unit=SizeUnit.MM, + manufacturer=Organization.EDMUND_OPTICS, + max_aperture="f/2", +) + +face_camera = Camera( + name="Face Camera", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=behavior_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Monochrome", +) + +camassm1 = CameraAssembly( + name="Face Camera Assembly", + camera=face_camera, + camera_target="Face side left", + filter=filt, + lens=lens, +) + +body_camera = Camera( + name="Body Camera", + detector_type="Camera", + data_interface="USB", + manufacturer=Organization.FLIR, + computer_name=behavior_computer, + frame_rate=50, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1080, + sensor_height=570, + sensor_format="1/2.9", + sensor_format_unit="inches", + chroma="Monochrome", +) + +camassm2 = CameraAssembly( + name="Body Camera Assembly", + camera=body_camera, + camera_target="Body", + filter=filt, + lens=lens, +) + +# If a timezone isn't specified, the timezone of the computer running this +# script will be used as default + +red_laser_calibration = Calibration( + calibration_date=datetime(2023, 10, 2, 10, 22, 13, tzinfo=timezone.utc), + device_name="Red Laser", + description="Laser power calibration", + input={"power percent": [10, 20, 40]}, + output={"power mW": [1, 3, 6]}, +) + +blue_laser_calibration = Calibration( + calibration_date=datetime(2023, 10, 2, 10, 22, 13, tzinfo=timezone.utc), + device_name="Blue Laser", + description="Laser power calibration", + input={"power percent": [10, 20, 40]}, + output={"power mW": [1, 2, 7]}, +) + +inst = Instrument( + instrument_id="323_EPHYS1_20231003", + modification_date=date(2023, 10, 3), + modalities=[Modality.ECEPHYS], + components=[ + ephys_assemblyA, + ephys_assemblyB, + camassm1, + camassm2, + laser_assembly, + basestation, + harp, + microscope_1, + microscope_2, + microscope_3, + microscope_4, + ], + mouse_platform=running_wheel, + calibrations=[red_laser_calibration, blue_laser_calibration], +) +serialized = inst.model_dump_json() +deserialized = Instrument.model_validate_json(serialized) +deserialized.write_standard_file(prefix="ephys") diff --git a/tests/resources/spim_instrument.py b/tests/resources/spim_instrument.py new file mode 100644 index 000000000..b8672db34 --- /dev/null +++ b/tests/resources/spim_instrument.py @@ -0,0 +1,287 @@ +import datetime +from aind_data_schema_models.organizations import Organization +from aind_data_schema_models.units import SizeUnit + +from aind_data_schema.components.devices import ( + Filter, + ImagingInstrumentType, + Laser, + MotorizedStage, + OpticalTable, + ScanningStage, +) +from aind_data_schema.core.instrument import Com, Detector, Instrument, Objective +from aind_data_schema_models.modalities import Modality + +objective_1 = Objective( + name="TLX Objective 1", + numerical_aperture=0.2, + magnification=3.6, + manufacturer=Organization.LIFECANVAS, + immersion="multi", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", + serial_number="Unknown-1", +) + +objective_2 = Objective( + name="TLX Objective 2", + numerical_aperture=0.12, + magnification=1.625, + manufacturer=Organization.LIFECANVAS, + immersion="multi", + notes="Thorlabs TL2X-SAP with LifeCanvas dipping cap and correction optics.", + serial_number="Unknown-2", +) + +detector_1 = Detector( + detector_type="Camera", + data_interface="USB", + name="Camera 1", + cooling="Air", + manufacturer=Organization.HAMAMATSU, + model="C14440-20UP", + serial_number="001284", +) + +laser_1 = Laser( + name="Ex_445", + coupling="Single-mode fiber", + wavelength=445, + maximum_power=200, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_2 = Laser( + name="Ex_488", + coupling="Single-mode fiber", + wavelength=488, + maximum_power=150, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_3 = Laser( + name="Ex_561", + coupling="Single-mode fiber", + wavelength=561, + maximum_power=150, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_4 = Laser( + name="Ex_594", + coupling="Single-mode fiber", + wavelength=594, + maximum_power=100, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_5 = Laser( + name="Ex_639", + coupling="Single-mode fiber", + wavelength=639, + maximum_power=160, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +laser_6 = Laser( + name="Ex_690", + coupling="Single-mode fiber", + wavelength=690, + maximum_power=160, + serial_number="VL08223M03", + manufacturer=Organization.VORTRAN, +) + +filter_1 = Filter( + name="Em_469", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-469/35-25", + filter_wheel_index=0, + serial_number="Unknown-0", +) + +filter_2 = Filter( + name="Em_525", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-525/45-25", + filter_wheel_index=1, + serial_number="Unknown-1", +) + +filter_3 = Filter( + name="Em_593", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-593/40-25", + filter_wheel_index=2, + serial_number="Unknown-2", +) + +filter_4 = Filter( + name="Em_624", + filter_type="Band pass", + manufacturer=Organization.SEMROCK, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FF01-624/40-25", + filter_wheel_index=3, + serial_number="Unknown-3", +) + +filter_5 = Filter( + name="Em_667", + filter_type="Band pass", + manufacturer=Organization.CHROMA, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="ET667/30m", + filter_wheel_index=4, + serial_number="Unknown-4", +) + +filter_6 = Filter( + name="Em_700", + filter_type="Long pass", + manufacturer=Organization.THORLABS, + diameter=25, + thickness=2.0, + thickness_unit=SizeUnit.MM, + model="FELH0700", + filter_wheel_index=5, + serial_number="Unknown-5", +) + +motorized_stage_1 = MotorizedStage( + model="LS-100", + manufacturer=Organization.ASI, + serial_number="Unknown-1", + travel=100, + name="Focus stage", +) + +motorized_stage_2 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-5", + travel=41, + name="Cylindrical lens #1", +) + +motorized_stage_3 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-6", + travel=41, + name="Cylindrical lens #2", +) + +motorized_stage_4 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-7", + travel=41, + name="Cylindrical lens #3", +) + +motorized_stage_5 = MotorizedStage( + model="L12-20F-4", + manufacturer=Organization.IR_ROBOT_CO, + serial_number="Unknown-8", + travel=41, + name="Cylindrical lens #4", +) + +scanning_stage_1 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-2", + stage_axis_direction="Detection axis", + stage_axis_name="Z", + travel=50, + name="Sample stage Z", +) + +scanning_stage_2 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-3", + stage_axis_direction="Illumination axis", + stage_axis_name="X", + travel=50, + name="Sample stage X", +) + +scanning_stage_3 = ScanningStage( + model="LS-50", + manufacturer=Organization.ASI, + serial_number="Unknown-4", + stage_axis_direction="Perpendicular axis", + stage_axis_name="Y", + travel=50, + name="Sample stage Y", +) + +optical_table_1 = OpticalTable( + name="Main optical table", + length=36, + width=48, + vibration_control=False, + model="VIS2424-IG2-125A", + manufacturer=Organization.MKS_NEWPORT, + serial_number="Unknown", +) + +objectives = [objective_1, objective_2] +detectors = [detector_1] +lasers = [laser_1, laser_2, laser_3, laser_4, laser_5, laser_6] +fluorescence_filters = [filter_1, filter_2, filter_3, filter_4, filter_5, filter_6] +motorized_stages = [motorized_stage_1, motorized_stage_2, motorized_stage_3, motorized_stage_4, motorized_stage_5] +scanning_stages = [scanning_stage_1, scanning_stage_2, scanning_stage_3] +optical_tables = [optical_table_1] + +inst = Instrument( + instrument_id="440_SmartSPIM1_20231004", + instrument_type=ImagingInstrumentType.SMARTSPIM, + manufacturer=Organization.LIFECANVAS, + modification_date=datetime.date(2023, 10, 4), + modalities=[Modality.SPIM], + components=[ + *objectives, + *detectors, + *lasers, + *fluorescence_filters, + *motorized_stages, + *scanning_stages, + *optical_tables, + ], + com_ports=[ + Com(hardware_name="Laser Launch", com_port="COM4"), + Com( + hardware_name="ASI Tiger", + com_port="COM3", + ), + Com( + hardware_name="MightyZap", + com_port="COM9", + ), + ], + temperature_control=False, +) diff --git a/tests/test_bump_schema_versions.py b/tests/test_bump_schema_versions.py index 8f1c2e542..848219efe 100644 --- a/tests/test_bump_schema_versions.py +++ b/tests/test_bump_schema_versions.py @@ -8,7 +8,7 @@ from aind_data_schema.core.session import Session from aind_data_schema.core.subject import Subject -from aind_data_schema.core.rig import Rig +from aind_data_schema.core.instrument import Instrument from aind_data_schema.utils.json_writer import SchemaWriter from aind_data_schema.utils.schema_version_bump import SchemaVersionHandler @@ -134,11 +134,13 @@ def test_update_files(self, mock_write: MagicMock): new_subject_version = str(Version.parse(old_subject_version).bump_patch()) old_session_version = Session.model_fields["schema_version"].default new_session_version = str(Version.parse(old_session_version).bump_patch()) - old_rig_version = Rig.model_fields["schema_version"].default - new_rig_version = str(Version.parse(old_rig_version).bump_minor()) + old_inst_version = Instrument.model_fields["schema_version"].default + new_inst_version = str(Version.parse(old_inst_version).bump_minor()) # Pycharm raises a warning about types that we can ignore # noinspection PyTypeChecker - handler._update_files({Subject: new_subject_version, Session: new_session_version, Rig: new_rig_version}) + handler._update_files( + {Subject: new_subject_version, Session: new_session_version, Instrument: new_inst_version} + ) expected_line_change0 = ( f'schema_version: SkipValidation[Literal["{new_subject_version}"]] = Field(default="{new_subject_version}")' diff --git a/tests/test_data_description.py b/tests/test_data_description.py index 23764ca38..ecade7767 100644 --- a/tests/test_data_description.py +++ b/tests/test_data_description.py @@ -10,7 +10,6 @@ from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.platforms import Platform from pydantic import ValidationError from pydantic import __version__ as pyd_version @@ -44,67 +43,106 @@ def setUpClass(cls): cls.data_descriptions = dict(data_descriptions) BAD_NAME = "fizzbuzz" - BASIC_NAME = "ecephys_1234_3033-12-21_04-22-11" - DERIVED_NAME = "ecephys_1234_3033-12-21_04-22-11_spikesorted-ks25_2022-10-12_23-23-11" - ANALYSIS_NAME = "project_analysis_3033-12-21_04-22-11" + BASIC_NAME = "1234_3033-12-21T042211" + DERIVED_NAME = "1234_3033-12-21T042211_spikesorted-ks25_2022-10-12T232311" + ANALYSIS_NAME = "project_analysis_3033-12-21T042211" - def test_constructors(self): - """test building from component parts""" + def test_funding_construction(self): + """Test Funding construction""" f = Funding(funder=Organization.NINDS, grant_number="grant001") + self.assertIsNotNone(f) + def test_raw_data_description_construction(self): + """Test RawDataDescription construction""" dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") da = RawDataDescription( creation_time=dt, institution=Organization.AIND, data_level="raw", funding_source=[f], - modality=[Modality.ECEPHYS], - platform=Platform.ECEPHYS, + modalities=[Modality.ECEPHYS], subject_id="12345", investigators=[Person(name="Jane Smith")], ) + self.assertIsNotNone(da) + def test_derived_data_description_construction(self): + """Test DerivedDataDescription construction""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") + da = RawDataDescription( + creation_time=dt, + institution=Organization.AIND, + data_level="raw", + funding_source=[f], + modalities=[Modality.ECEPHYS], + subject_id="12345", + investigators=[Person(name="Jane Smith")], + ) r1 = DerivedDataDescription( input_data_name=da.name, process_name="spikesort-ks25", creation_time=dt, institution=Organization.AIND, funding_source=[f], - modality=da.modality, - platform=da.platform, + modalities=da.modalities, subject_id=da.subject_id, investigators=[Person(name="Jane Smith")], ) + self.assertIsNotNone(r1) + def test_nested_derived_data_description_construction(self): + """Test nested DerivedDataDescription construction""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") + da = RawDataDescription( + creation_time=dt, + institution=Organization.AIND, + data_level="raw", + funding_source=[f], + modalities=[Modality.ECEPHYS], + subject_id="12345", + investigators=[Person(name="Jane Smith")], + ) + r1 = DerivedDataDescription( + input_data_name=da.name, + process_name="spikesort-ks25", + creation_time=dt, + institution=Organization.AIND, + funding_source=[f], + modalities=da.modalities, + subject_id=da.subject_id, + investigators=[Person(name="Jane Smith")], + ) r2 = DerivedDataDescription( input_data_name=r1.name, process_name="some-model", creation_time=dt, institution=Organization.AIND, funding_source=[f], - modality=r1.modality, - platform=r1.platform, + modalities=r1.modalities, subject_id="12345", investigators=[Person(name="Jane Smith")], ) - r3 = DerivedDataDescription( input_data_name=r2.name, process_name="a-paper", creation_time=dt, institution=Organization.AIND, funding_source=[f], - modality=r2.modality, - platform=r2.platform, + modalities=r2.modalities, subject_id="12345", investigators=[Person(name="Jane Smith")], ) - assert r3 is not None + self.assertIsNotNone(r3) + def test_data_description_construction(self): + """Test DataDescription construction""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") dd = DataDescription( - label="test_data", - modality=[Modality.SPIM], - platform=Platform.EXASPIM, + modalities=[Modality.SPIM], subject_id="1234", data_level="raw", creation_time=dt, @@ -112,16 +150,16 @@ def test_constructors(self): funding_source=[f], investigators=[Person(name="Jane Smith")], ) + self.assertIsNotNone(dd) - assert dd is not None - - # test construction fails + def test_data_description_construction_failure(self): + """Test DataDescription construction failure""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") with self.assertRaises(ValidationError): DataDescription( - label="test_data", - modality=[Modality.SPIM], - platform="fake platform", - subject_id="1234", + modalities=[Modality.SPIM], + subject_id="", data_level="raw", creation_time=dt, institution=Organization.AIND, @@ -129,73 +167,88 @@ def test_constructors(self): investigators=[Person(name="Jane Smith")], ) + def test_analysis_description_construction(self): + """Test AnalysisDescription construction""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") ad = AnalysisDescription( analysis_name="analysis", project_name="project", creation_time=dt, subject_id="1234", - modality=[Modality.SPIM], - platform=Platform.EXASPIM, + modalities=[Modality.SPIM], institution=Organization.AIND, funding_source=[f], investigators=[Person(name="Jane Smith")], ) self.assertEqual(ad.name, build_data_name("project_analysis", dt)) + def test_analysis_description_invalid_name(self): + """Test AnalysisDescription invalid name""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") with self.assertRaises(ValueError): AnalysisDescription( analysis_name="ana lysis", project_name="pro_ject", subject_id="1234", - modality=[Modality.SPIM], - platform="exaspim", + modalities=[Modality.SPIM], creation_time=dt, institution=Organization.AIND, funding_source=[f], investigators=[Person(name="Jane Smith")], ) + def test_data_description_missing_fields(self): + """Test DataDescription missing fields""" + dt = datetime.datetime.now() with self.assertRaises(ValueError): DataDescription() - with self.assertRaises(ValueError): DataDescription(creation_time=dt) + def test_derived_data_description_missing_fields(self): + """Test DerivedDataDescription missing fields""" + dt = datetime.datetime.now() with self.assertRaises(ValueError): DerivedDataDescription() - with self.assertRaises(ValueError): DerivedDataDescription(creation_time=dt) + def test_analysis_description_missing_fields(self): + """Test AnalysisDescription missing fields""" + dt = datetime.datetime.now() with self.assertRaises(ValueError): AnalysisDescription() - with self.assertRaises(ValueError): AnalysisDescription(creation_time=dt) + def test_raw_data_description_invalid_platform(self): + """Test RawDataDescription invalid platform""" with self.assertRaises(ValueError): RawDataDescription(platform="exaspim") + def test_analysis_description_empty_fields(self): + """Test AnalysisDescription empty fields""" + dt = datetime.datetime.now() + f = Funding(funder=Organization.NINDS, grant_number="grant001") with self.assertRaises(ValueError): AnalysisDescription( analysis_name="", project_name="project", subject_id="1234", - modality=[Modality.SPIM], - platform="exaspim", + modalities=[Modality.SPIM], creation_time=dt, institution=Organization.AIND, funding_source=[f], investigators=[Person(name="Jane Smith")], ) - with self.assertRaises(ValueError): AnalysisDescription( analysis_name="analysis", project_name="", subject_id="1234", - modality=[Modality.SPIM], - platform="exaspim", + modalities=[Modality.SPIM], creation_time=dt, institution=Organization.AIND, funding_source=[f], @@ -206,9 +259,7 @@ def test_pattern_errors(self): """Tests that errors are raised if malformed strings are input""" with self.assertRaises(ValidationError) as e: DataDescription( - label="test_data", - modality=[Modality.SPIM], - platform=Platform.EXASPIM, + modalities=[Modality.SPIM], subject_id="1234", data_level="raw", project_name="a_32r&!#R$&#", @@ -234,22 +285,6 @@ def test_model_constructors(self): assert Modality.from_abbreviation("ecephys") == Modality.ECEPHYS assert Organization().name_map["Allen Institute for Neural Dynamics"] == Organization.AIND - def test_name_label_error(self): - """Tests an error is raised if label and name are None""" - - with self.assertRaises(ValidationError) as e: - DataDescription( - modality=[Modality.SPIM], - platform=Platform.EXASPIM, - subject_id="1234", - data_level="raw", - creation_time=datetime.datetime(2020, 10, 10, 10, 10, 10), - institution=Organization.AIND, - funding_source=[Funding(funder=Organization.NINDS, grant_number="grant001")], - investigators=[Person(name="Jane Smith")], - ) - self.assertTrue("Value error, Either label or name must be set" in repr(e.exception)) - def test_round_trip(self): """make sure we can round trip from json""" @@ -260,8 +295,7 @@ def test_round_trip(self): institution=Organization.AIND, data_level="raw", funding_source=[Funding(funder=Organization.NINDS, grant_number="grant001")], - modality=[Modality.SPIM], - platform=Platform.EXASPIM, + modalities=[Modality.SPIM], subject_id="12345", investigators=[Person(name="Jane Smith")], ) @@ -274,14 +308,13 @@ def test_parse_name(self): """tests for parsing names""" toks = DataDescription.parse_name(self.BASIC_NAME) - assert toks["label"] == "ecephys_1234" + assert toks["label"] == "1234" assert toks["creation_time"] == datetime.datetime(3033, 12, 21, 4, 22, 11) with self.assertRaises(ValueError): DataDescription.parse_name(self.BAD_NAME) toks = RawDataDescription.parse_name(self.BASIC_NAME) - assert toks["platform"] == Platform.ECEPHYS assert toks["subject_id"] == "1234" assert toks["creation_time"] == datetime.datetime(3033, 12, 21, 4, 22, 11) @@ -289,7 +322,7 @@ def test_parse_name(self): RawDataDescription.parse_name(self.BAD_NAME) toks = DerivedDataDescription.parse_name(self.DERIVED_NAME) - assert toks["input_data_name"] == "ecephys_1234_3033-12-21_04-22-11" + assert toks["input_data_name"] == "1234_3033-12-21T042211" assert toks["process_name"] == "spikesorted-ks25" assert toks["creation_time"] == datetime.datetime(2022, 10, 12, 23, 23, 11) @@ -308,16 +341,16 @@ def test_unique_abbreviations(self): """Tests that abbreviations are unique""" modality_abbreviations = [m().abbreviation for m in Modality.ALL] self.assertEqual(len(set(modality_abbreviations)), len(modality_abbreviations)) - platform_abbreviations = [p().abbreviation for p in Platform.ALL] - self.assertEqual(len(set(platform_abbreviations)), len(platform_abbreviations)) + + +class DerivedDataDescriptionTest(unittest.TestCase): + """test DerivedDataDescription""" def test_from_data_description(self): """Tests DerivedDataDescription.from_data_description method""" d1 = DataDescription( - label="test_data", - modality=[Modality.SPIM], - platform=Platform.EXASPIM, + modalities=[Modality.SPIM], subject_id="1234", data_level="raw", creation_time=datetime.datetime(2020, 10, 10, 10, 10, 10), @@ -329,25 +362,38 @@ def test_from_data_description(self): process_name = "spikesorter" dd1 = DerivedDataDescription.from_data_description(d1, process_name=process_name) - dd2 = DerivedDataDescription.from_data_description(d1, process_name=process_name, subject_id="12345") - self.assertTrue("test_data_2020-10-10_10-10-10_spikesorter_" in dd1.name) + # check that the original name is in the derived name + self.assertTrue("1234_2020-10-10T101010_spikesorter_" in dd1.name) + # check that the subject ID is retained self.assertEqual("1234", dd1.subject_id) - self.assertEqual("12345", dd2.subject_id) + # check that the data level is upgraded + self.assertEqual("derived", dd1.data_level) def test_derived_data_description_build_name(self): """Tests build name method in derived data description class""" dd = DerivedDataDescription( - input_data_name="input", - creation_time=datetime.datetime(2020, 10, 10, 10, 10, 10), + input_data_name="12345_2020-10-10T101010", + creation_time=datetime.datetime(2021, 10, 10, 10, 10, 10), + institution=Organization.AIND, + funding_source=[Funding(funder=Organization.NINDS, grant_number="grant001")], + modalities=[Modality.ECEPHYS], + subject_id="12345", + investigators=[Person(name="Jane Smith")], + ) + self.assertEqual("12345_2020-10-10T101010_2021-10-10T101010", dd.name) + + dd2 = DerivedDataDescription( + input_data_name="12345_2020-10-10T101010", + creation_time=datetime.datetime(2021, 10, 10, 10, 10, 10), institution=Organization.AIND, funding_source=[Funding(funder=Organization.NINDS, grant_number="grant001")], - modality=[Modality.ECEPHYS], - platform=Platform.ECEPHYS, + modalities=[Modality.ECEPHYS], subject_id="12345", investigators=[Person(name="Jane Smith")], + process_name="spikesorter", ) - self.assertEqual("input_2020-10-10_10-10-10", dd.name) + self.assertIn("12345_2020-10-10T101010_spikesorter", dd2.name) if __name__ == "__main__": diff --git a/tests/test_device.py b/tests/test_device.py index 684cf7f6c..d444b84a0 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -35,11 +35,9 @@ def test_other_validators(self): name="test_reward_spout", spout_diameter=0.5, solenoid_valve=Device( - device_type="solenoid", name="test_solenoid", ), lick_sensor=Device( - device_type="Lick sensor", name="Sensor_test", ), side=SpoutSide.OTHER, @@ -52,7 +50,6 @@ def test_other_validators(self): " [type=value_error, input_value={'name': 'test_reward_spo...outSide.OTHER: 'Other'>}, input_type=dict]\n" f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/value_error" ) - self.assertEqual(repr(e1.exception), expected_e1) with self.assertRaises(ValueError) as e2: @@ -128,3 +125,7 @@ def test_other_validators(self): ) self.assertEqual(repr(e5.exception), expected_e5) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_diagram_builder.py b/tests/test_diagram_builder.py index 17077896f..80289c0ea 100644 --- a/tests/test_diagram_builder.py +++ b/tests/test_diagram_builder.py @@ -45,7 +45,7 @@ def test_save_all_core_diagrams_default(self, mock_draw: MagicMock): call(Path("acquisition.svg")), call(Path("instrument.svg")), call(Path("procedures.svg")), - call(Path("rig.svg")), + call(Path("inst.svg")), call(Path("session.svg")), call(Path("metadata.nd.svg")), ], @@ -65,7 +65,7 @@ def test_save_all_core_diagrams_dir(self, mock_draw: MagicMock): call(Path("some_dir") / "acquisition.svg"), call(Path("some_dir") / "instrument.svg"), call(Path("some_dir") / "procedures.svg"), - call(Path("some_dir") / "rig.svg"), + call(Path("some_dir") / "inst.svg"), call(Path("some_dir") / "session.svg"), call(Path("some_dir") / "metadata.nd.svg"), ], diff --git a/tests/test_examples.py b/tests/test_examples.py index 8e8e5d4f2..dad9c36ec 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -18,7 +18,10 @@ class ExampleTests(unittest.TestCase): """tests for examples""" def test_examples(self): - """run through each example, compare to rendered json""" + """run through each example + + Check that the existing examples/*.json files match what gets generated from .write_standard_file() + """ for example_file in glob.glob(f"{EXAMPLES_DIR}/*.py"): logging.debug(f"testing {example_file}") diff --git a/tests/test_imaging.py b/tests/test_imaging.py index f43140a8e..7ead1de6e 100644 --- a/tests/test_imaging.py +++ b/tests/test_imaging.py @@ -2,10 +2,10 @@ import re import unittest -from datetime import date, datetime, timezone +from datetime import datetime, timezone from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.units import PowerUnit, FrequencyUnit +from aind_data_schema_models.units import PowerUnit from pydantic import ValidationError from pydantic import __version__ as pyd_version @@ -16,10 +16,11 @@ Scale3dTransform, Translation3dTransform, ) -from aind_data_schema.components.devices import Calibration, DAQChannel, DAQDevice +from aind_data_schema.components.devices import Calibration, Objective from aind_data_schema.core import acquisition as acq -from aind_data_schema.core import instrument as inst from aind_data_schema.core.processing import Registration +from aind_data_schema.core.instrument import Instrument +from aind_data_schema_models.modalities import Modality from aind_data_schema.components.identifiers import Person PYD_VERSION = re.match(r"(\d+.\d+).\d+", pyd_version).group(1) @@ -28,8 +29,8 @@ class ImagingTests(unittest.TestCase): """test imaging schemas""" - def test_constructors(self): - """testing constructors""" + def test_acquisition_constructor(self): + """testing Acquisition constructor""" with self.assertRaises(ValidationError): acq.Acquisition() @@ -75,62 +76,57 @@ def test_constructors(self): self.assertIsNotNone(a) + def test_instrument_constructor(self): + """testing Instrument constructor""" with self.assertRaises(ValidationError): - inst.Instrument() + Instrument() + + objective = Objective( + name="TLX Objective", + numerical_aperture=0.2, + magnification=3.6, + immersion="multi", + manufacturer=Organization.THORLABS, + model="TL4X-SAP", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", + ) - i = inst.Instrument( + i = Instrument( + instrument_id="room_exaSPIM1-1_20231004", + modalities=[Modality.SPIM], instrument_type="diSPIM", modification_date=datetime.now().date(), manufacturer=Organization.LIFECANVAS, - objectives=[], - detectors=[], - light_sources=[], + components=[objective], ) self.assertIsNotNone(i) + def test_instrument_type_other_requires_notes(self): + """testing Instrument type Other requires notes""" with self.assertRaises(ValidationError) as e1: - inst.Instrument( + Instrument( + instrument_id="room_exaSPIM1-1_20231004", + modalities=[Modality.SPIM], instrument_type="Other", modification_date=datetime(2020, 10, 10, 0, 0, 0).date(), manufacturer=Organization.OTHER, - objectives=[], - detectors=[], - light_sources=[], ) - expected_exception1 = ( - "1 validation error for Instrument\n" - "notes\n" - " Value error, Notes cannot be empty if instrument_type is Other." - " Describe the instrument_type in the notes field." - " [type=value_error, input_value=None, input_type=NoneType]\n" - f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/value_error" - ) - self.assertEqual(expected_exception1, repr(e1.exception)) + self.assertIn("instrument_id", repr(e1.exception)) + def test_modality_spim_requires_components(self): + """testing Modality SPIM requires components""" with self.assertRaises(ValidationError) as e2: - inst.Instrument( + Instrument( + instrument_id="room_exaSPIM1-1_20231004", + modalities=[Modality.SPIM], + modification_date=datetime(2020, 10, 10, 0, 0, 0).date(), instrument_type="diSPIM", manufacturer=Organization.OTHER, - objectives=[], - detectors=[], - light_sources=[], ) - expected_exception2 = ( - "2 validation errors for Instrument\n" - "modification_date\n" - " Field required [type=missing, input_value={'instrument_type': 'diSP...[]," - " 'light_sources': []}, input_type=dict]\n" - f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/missing\n" - "notes\n" - " Value error, Notes cannot be empty if manufacturer is Other." - " Describe the manufacturer in the notes field." - " [type=value_error, input_value=None, input_type=NoneType]\n" - f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/value_error" - ) - self.assertEqual(expected_exception2, repr(e2.exception)) + self.assertIn("modality 'SPIM' requires at least one device", repr(e2.exception)) def test_axis(self): """test the axis class""" @@ -209,85 +205,6 @@ def test_registration(self): self.assertIsNotNone(t) - def test_validators(self): - """test the validators""" - - with self.assertRaises(ValidationError) as e: - inst.Instrument( - instrument_id="exaSPIM1-1", - instrument_type="exaSPIM", - modification_date=date(2023, 10, 4), - manufacturer=Organization.CUSTOM, - daqs=[ - DAQDevice( - model="PCIe-6738", - data_interface="USB", - computer_name="Dev2", - manufacturer=Organization.NATIONAL_INSTRUMENTS, - name="Dev2", - serial_number="Unknown", - channels=[ - DAQChannel( - channel_name="3", - channel_type="Analog Output", - device_name="LAS-08308", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="5", - channel_type="Analog Output", - device_name="539251", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="4", - channel_type="Analog Output", - device_name="LAS-08309", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="2", - channel_type="Analog Output", - device_name="stage-x", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="0", - channel_type="Analog Output", - device_name="TL-1", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - DAQChannel( - channel_name="6", - channel_type="Analog Output", - device_name="LAS-08307", - sample_rate=10000, - sample_rate_unit=FrequencyUnit.HZ, - ), - ], - ) - ], - ) - expected_exception = ( - "2 validation errors for Instrument\n" - "objectives\n" - " Field required [type=missing," - " input_value={'instrument_id': 'exaSPI...hardware_version=None)]}, input_type=dict]\n" - f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/missing\n" - "daqs\n" - " Value error, Device name validation error: 'LAS-08308' is connected to '3' on 'Dev2'," - " but this device is not part of the rig. [type=value_error," - " input_value=[DAQDevice(device_type='D... hardware_version=None)], input_type=list]\n" - f" For further information visit https://errors.pydantic.dev/{PYD_VERSION}/v/value_error" - ) - print(repr(e.exception)) - self.assertEqual(expected_exception, repr(e.exception)) - if __name__ == "__main__": unittest.main() diff --git a/tests/test_rig_session_compatibility.py b/tests/test_inst_acq_compatibility.py similarity index 62% rename from tests/test_rig_session_compatibility.py rename to tests/test_inst_acq_compatibility.py index 2f189764b..f675baa88 100644 --- a/tests/test_rig_session_compatibility.py +++ b/tests/test_inst_acq_compatibility.py @@ -1,4 +1,4 @@ -"""Tests rig session compatibility check""" +"""Tests instrument session compatibility check""" import json import unittest @@ -12,7 +12,6 @@ import aind_data_schema.components.devices as d from aind_data_schema.components.identifiers import Person -import aind_data_schema.core.rig as r from aind_data_schema.components.devices import ( Calibration, Camera, @@ -34,7 +33,7 @@ ProbePort, Software, ) -from aind_data_schema.core.rig import Rig +from aind_data_schema.core.instrument import Instrument from aind_data_schema.core.session import ( CcfCoords, Coordinates3d, @@ -50,17 +49,17 @@ Stream, VisualStimulation, ) -from aind_data_schema.utils.compatibility_check import RigSessionCompatibility +from aind_data_schema.utils.compatibility_check import InstrumentSessionCompatibility from aind_data_schema_models.brain_atlas import CCFStructure EXAMPLES_DIR = Path(__file__).parents[1] / "examples" -EPHYS_RIG_JSON = EXAMPLES_DIR / "ephys_rig.json" +EPHYS_INST_JSON = EXAMPLES_DIR / "ephys_instrument.json" EPHYS_SESSION_JSON = EXAMPLES_DIR / "ephys_session.json" behavior_computer = "W10DT72941" ephys_computer = "W10DT72942" -running_wheel = Disc(name="Running Wheel", radius=15) +disc_mouse_platform = Disc(name="Running Wheel", radius=15) digital_out0 = DAQChannel(channel_name="DO0", device_name="Face Camera", channel_type="Digital Output") @@ -100,7 +99,7 @@ name="Manipulator A", serial_number="SN2937", manufacturer=Organization.NEW_SCALE_TECHNOLOGIES ), lasers=[red_laser, blue_laser], - collimator=Device(name="Collimator A", device_type="Collimator"), + collimator=Device(name="Collimator A"), fiber=Patch( name="Bundle Branching Fiber-optic Patch Cord", manufacturer=Organization.DORIC, @@ -234,16 +233,21 @@ output={"power mW": [1, 2, 7]}, ) -ephys_rig = Rig( - rig_id="323_EPHYS1_20231003", +ephys_inst = Instrument( + instrument_id="323_EPHYS1_20231003", modification_date=date(2023, 10, 3), modalities=[Modality.ECEPHYS], - ephys_assemblies=[ephys_assemblyA, ephys_assemblyB], - cameras=[camassm1, camassm2], - laser_assemblies=[laser_assembly], - daqs=[basestation, harp], - stick_microscopes=[microscope], - mouse_platform=running_wheel, + components=[ + ephys_assemblyA, + ephys_assemblyB, + camassm1, + camassm2, + laser_assembly, + basestation, + harp, + microscope, + ], + mouse_platform=disc_mouse_platform, calibrations=[red_laser_calibration, blue_laser_calibration], ) @@ -253,8 +257,8 @@ session_start_time=datetime(year=2023, month=4, day=25, hour=2, minute=35, second=0, tzinfo=timezone.utc), session_end_time=datetime(year=2023, month=4, day=25, hour=3, minute=16, second=0, tzinfo=timezone.utc), session_type="Receptive field mapping", + instrument_id="323_EPHYS2-RF_2023-04-24_01", ethics_review_id="2109", - rig_id="323_EPHYS2-RF_2023-04-24_01", active_mouse_platform=False, mouse_platform_name="mouse platform", stimulus_epochs=[ @@ -468,8 +472,8 @@ ) -class TestRigSessionCompatibility(unittest.TestCase): - """Tests RigSessionCompatibility class""" +class TestInstrumentSessionCompatibility(unittest.TestCase): + """Tests InstrumentSessionCompatibility class""" @classmethod def setUpClass(cls) -> None: @@ -481,292 +485,303 @@ def read_json(filepath: Path) -> dict: contents = json.load(f) return contents - cls.example_ephys_rig = Rig.model_validate_json(json.dumps(read_json(EPHYS_RIG_JSON))) - cls.example_ephys_session = Session.model_validate_json(json.dumps(read_json(EPHYS_SESSION_JSON))) - cls.ophys_rig = r.Rig( - rig_id="428_FIP1_20231003", - modification_date=date(2023, 10, 3), - modalities=[Modality.FIB], - cameras=[ - d.CameraAssembly( - name="BehaviorVideography_FaceSide", - camera_target=d.CameraTarget.FACE_SIDE_LEFT, - camera=d.Camera( - name="Side face camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 1", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - d.CameraAssembly( - name="BehaviorVideography_FaceBottom", - camera_target=d.CameraTarget.FACE_BOTTOM, - camera=d.Camera( - name="Bottom face Camera", - detector_type="Camera", - serial_number="TBD", - manufacturer=d.Organization.AILIPU, - model="ELP-USBFHD05MT-KL170IR", - notes="The light intensity sensor was removed; IR illumination is constantly on", - data_interface="USB", - computer_name="W10DTJK7N0M3", - frame_rate=120, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=640, - sensor_height=480, - chroma="Color", - cooling="Air", - bin_mode="Additive", - recording_software=d.Software(name="Bonsai", version="2.5"), - ), - lens=d.Lens( - name="Xenocam 2", - model="XC0922LENS", - serial_number="unknown", - manufacturer=d.Organization.OTHER, - max_aperture="f/1.4", - notes='Focal Length 9-22mm 1/3" IR F1.4', - ), - ), - ], - patch_cords=[ - d.Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=d.Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - light_sources=[ - d.LightEmittingDiode( - name="470nm LED", - manufacturer=d.Organization.THORLABS, - model="M470F3", - wavelength=470, - ), - d.LightEmittingDiode( - name="415nm LED", - manufacturer=d.Organization.THORLABS, - model="M415F3", - wavelength=415, - ), - d.LightEmittingDiode( - name="565nm LED", - manufacturer=d.Organization.THORLABS, - model="M565F3", - wavelength=565, - ), - ], - detectors=[ - d.Detector( - name="Green CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", + cameras = [ + d.CameraAssembly( + name="BehaviorVideography_FaceSide", + camera_target=d.CameraTarget.FACE_SIDE_LEFT, + camera=d.Camera( + name="Side face camera", detector_type="Camera", + serial_number="TBD", + manufacturer=d.Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, + recording_software=d.Software(name="Bonsai", version="2.5"), + ), + lens=d.Lens( + name="Xenocam 1", + model="XC0922LENS", + serial_number="unknown", + manufacturer=d.Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', ), - d.Detector( - name="Red CMOS", - serial_number="21396991", - manufacturer=d.Organization.FLIR, - model="BFS-U3-20S40M", + ), + d.CameraAssembly( + name="BehaviorVideography_FaceBottom", + camera_target=d.CameraTarget.FACE_BOTTOM, + camera=d.Camera( + name="Bottom face Camera", detector_type="Camera", + serial_number="TBD", + manufacturer=d.Organization.AILIPU, + model="ELP-USBFHD05MT-KL170IR", + notes="The light intensity sensor was removed; IR illumination is constantly on", data_interface="USB", + computer_name="W10DTJK7N0M3", + frame_rate=120, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=640, + sensor_height=480, + chroma="Color", cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ), - ], - objectives=[ - d.Objective( - name="Objective", - serial_number="128022336", - manufacturer=d.Organization.NIKON, - model="CFI Plan Apochromat Lambda D 10x", - numerical_aperture=0.45, - magnification=10, - immersion="air", - ) - ], - filters=[ - d.Filter( - name="Green emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-520/35-25", - filter_type="Band pass", - center_wavelength=520, - diameter=25, - ), - d.Filter( - name="Red emission filter", - manufacturer=d.Organization.SEMROCK, - model="FF01-600/37-25", - filter_type="Band pass", - center_wavelength=600, - diameter=25, - ), - d.Filter( - name="Emission Dichroic", - model="FF562-Di03-25x36", - manufacturer=d.Organization.SEMROCK, - filter_type="Dichroic", - height=25, - width=36, - cut_off_wavelength=562, - ), - d.Filter( - name="dual-edge standard epi-fluorescence dichroic beamsplitter", - model="FF493/574-Di01-25x36", - manufacturer=d.Organization.SEMROCK, - notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", - filter_type="Multiband", - width=36, - height=24, + recording_software=d.Software(name="Bonsai", version="2.5"), ), - d.Filter( - name="Excitation filter 410nm", - manufacturer=d.Organization.THORLABS, - model="FB410-10", - filter_type="Band pass", - diameter=25, - center_wavelength=410, + lens=d.Lens( + name="Xenocam 2", + model="XC0922LENS", + serial_number="unknown", + manufacturer=d.Organization.OTHER, + max_aperture="f/1.4", + notes='Focal Length 9-22mm 1/3" IR F1.4', ), - d.Filter( - name="Excitation filter 470nm", - manufacturer=d.Organization.THORLABS, - model="FB470-10", - filter_type="Band pass", - center_wavelength=470, - diameter=25, - ), - d.Filter( - name="Excitation filter 560nm", - manufacturer=d.Organization.THORLABS, - model="FB560-10", - filter_type="Band pass", - diameter=25, - center_wavelength=560, - ), - d.Filter( - name="450 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-898", - filter_type="Dichroic", - cut_off_wavelength=450, - width=35.6, - height=25.2, - ), - d.Filter( - name="500 Dichroic Longpass Filter", - manufacturer=d.Organization.EDMUND_OPTICS, - model="#69-899", - filter_type="Dichroic", - cut_off_wavelength=500, - width=35.6, - height=23.2, - ), - ], - lenses=[ - d.Lens( - manufacturer=d.Organization.THORLABS, - model="AC254-080-A-ML", - name="Image focusing lens", - focal_length=80, - focal_length_unit=SizeUnit.MM, - size=1, - ) - ], - daqs=[ - d.HarpDevice( - name="Harp Behavior", - harp_device_type=d.HarpDeviceType.BEHAVIOR, - core_version="2.1", - computer_name="behavior_computer", - is_clock_generator=False, - channels=[ - d.DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), - d.DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), - d.DAQChannel( - channel_name="DI0", device_name="Janelia_Lick_Detector Left", channel_type="Digital Input" + ), + ] + patch_cords = [ + d.Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=d.Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, + ) + ] + light_sources = [ + d.LightEmittingDiode( + name="470nm LED", + manufacturer=d.Organization.THORLABS, + model="M470F3", + wavelength=470, + ), + d.LightEmittingDiode( + name="415nm LED", + manufacturer=d.Organization.THORLABS, + model="M415F3", + wavelength=415, + ), + d.LightEmittingDiode( + name="565nm LED", + manufacturer=d.Organization.THORLABS, + model="M565F3", + wavelength=565, + ), + ] + detectors = [ + d.Detector( + name="Green CMOS", + serial_number="21396991", + manufacturer=d.Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, + ), + d.Detector( + name="Red CMOS", + serial_number="21396991", + manufacturer=d.Organization.FLIR, + model="BFS-U3-20S40M", + detector_type="Camera", + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, + ), + ] + objectives = [ + d.Objective( + name="Objective", + serial_number="128022336", + manufacturer=d.Organization.NIKON, + model="CFI Plan Apochromat Lambda D 10x", + numerical_aperture=0.45, + magnification=10, + immersion="air", + ) + ] + filters = [ + d.Filter( + name="Green emission filter", + manufacturer=d.Organization.SEMROCK, + model="FF01-520/35-25", + filter_type="Band pass", + center_wavelength=520, + diameter=25, + ), + d.Filter( + name="Red emission filter", + manufacturer=d.Organization.SEMROCK, + model="FF01-600/37-25", + filter_type="Band pass", + center_wavelength=600, + diameter=25, + ), + d.Filter( + name="Emission Dichroic", + model="FF562-Di03-25x36", + manufacturer=d.Organization.SEMROCK, + filter_type="Dichroic", + height=25, + width=36, + cut_off_wavelength=562, + ), + d.Filter( + name="dual-edge standard epi-fluorescence dichroic beamsplitter", + model="FF493/574-Di01-25x36", + manufacturer=d.Organization.SEMROCK, + notes="493/574 nm BrightLine dual-edge standard epi-fluorescence dichroic beamsplitter", + filter_type="Multiband", + width=36, + height=24, + ), + d.Filter( + name="Excitation filter 410nm", + manufacturer=d.Organization.THORLABS, + model="FB410-10", + filter_type="Band pass", + diameter=25, + center_wavelength=410, + ), + d.Filter( + name="Excitation filter 470nm", + manufacturer=d.Organization.THORLABS, + model="FB470-10", + filter_type="Band pass", + center_wavelength=470, + diameter=25, + ), + d.Filter( + name="Excitation filter 560nm", + manufacturer=d.Organization.THORLABS, + model="FB560-10", + filter_type="Band pass", + diameter=25, + center_wavelength=560, + ), + d.Filter( + name="450 Dichroic Longpass Filter", + manufacturer=d.Organization.EDMUND_OPTICS, + model="#69-898", + filter_type="Dichroic", + cut_off_wavelength=450, + width=35.6, + height=25.2, + ), + d.Filter( + name="500 Dichroic Longpass Filter", + manufacturer=d.Organization.EDMUND_OPTICS, + model="#69-899", + filter_type="Dichroic", + cut_off_wavelength=500, + width=35.6, + height=23.2, + ), + ] + lenses = [ + d.Lens( + manufacturer=d.Organization.THORLABS, + model="AC254-080-A-ML", + name="Image focusing lens", + focal_length=80, + focal_length_unit=SizeUnit.MM, + size=1, + ) + ] + daqs = [ + d.HarpDevice( + name="Harp Behavior", + harp_device_type=d.HarpDeviceType.BEHAVIOR, + core_version="2.1", + computer_name="behavior_computer", + is_clock_generator=False, + channels=[ + d.DAQChannel(channel_name="DO0", device_name="Solenoid Left", channel_type="Digital Output"), + d.DAQChannel(channel_name="DO1", device_name="Solenoid Right", channel_type="Digital Output"), + d.DAQChannel( + channel_name="DI0", device_name="Janelia_Lick_Detector Left", channel_type="Digital Input" + ), + d.DAQChannel( + channel_name="DI1", device_name="Janelia_Lick_Detector Right", channel_type="Digital Input" + ), + d.DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), + ], + ) + ] + stimulus_devices = [ + d.RewardDelivery( + reward_spouts=[ + d.RewardSpout( + name="Left spout", + side=d.SpoutSide.LEFT, + spout_diameter=1.2, + solenoid_valve=d.Device(name="Solenoid Left"), + lick_sensor=d.Device( + name="Janelia_Lick_Detector Left", + manufacturer=d.Organization.JANELIA, ), - d.DAQChannel( - channel_name="DI1", device_name="Janelia_Lick_Detector Right", channel_type="Digital Input" + lick_sensor_type=d.LickSensorType("Capacitive"), + ), + d.RewardSpout( + name="Right spout", + side=d.SpoutSide.RIGHT, + spout_diameter=1.2, + solenoid_valve=d.Device(name="Solenoid Right"), + lick_sensor=d.Device( + name="Janelia_Lick_Detector Right", + manufacturer=d.Organization.JANELIA, ), - d.DAQChannel(channel_name="DI3", device_name="Photometry Clock", channel_type="Digital Input"), - ], - ) - ], + lick_sensor_type=d.LickSensorType("Capacitive"), + ), + ], + ), + ] + additional_devices = [d.Device(name="Photometry Clock")] + + cls.example_ephys_inst = Instrument.model_validate_json(json.dumps(read_json(EPHYS_INST_JSON))) + cls.example_ephys_session = Session.model_validate_json(json.dumps(read_json(EPHYS_SESSION_JSON))) + cls.ophys_instrument = Instrument( + instrument_id="428_FIP1_20231003", + modification_date=date(2023, 10, 3), + modalities=[Modality.FIB], mouse_platform=d.Disc(name="mouse_disc", radius=8.5), - stimulus_devices=[ - d.RewardDelivery( - reward_spouts=[ - d.RewardSpout( - name="Left spout", - side=d.SpoutSide.LEFT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Left"), - lick_sensor=d.Device( - name="Janelia_Lick_Detector Left", - device_type="Lick detector", - manufacturer=d.Organization.JANELIA, - ), - lick_sensor_type=d.LickSensorType("Capacitive"), - ), - d.RewardSpout( - name="Right spout", - side=d.SpoutSide.RIGHT, - spout_diameter=1.2, - solenoid_valve=d.Device(device_type="Solenoid", name="Solenoid Right"), - lick_sensor=d.Device( - name="Janelia_Lick_Detector Right", - device_type="Lick detector", - manufacturer=d.Organization.JANELIA, - ), - lick_sensor_type=d.LickSensorType("Capacitive"), - ), - ], - ), + components=[ + *cameras, + *patch_cords, + *light_sources, + *detectors, + *objectives, + *filters, + *lenses, + *daqs, + *stimulus_devices, + *additional_devices, ], - additional_devices=[d.Device(device_type="Photometry Clock", name="Photometry Clock")], calibrations=[ d.Calibration( calibration_date=datetime(2023, 10, 2, 3, 15, 22, tzinfo=timezone.utc), @@ -783,8 +798,8 @@ def read_json(filepath: Path) -> dict: session_end_time=datetime(2022, 7, 12, 7, 00, 00, tzinfo=timezone.utc), subject_id="652567", session_type="Parameter Testing", + instrument_id="ophys_inst", ethics_review_id="2115", - rig_id="ophys_rig", mouse_platform_name="Disc", active_mouse_platform=False, data_streams=[ @@ -848,18 +863,25 @@ def read_json(filepath: Path) -> dict: def test_run_compatibility_check(self): """Tests compatibility check""" - expected_error = "Rig ID in session 323_EPHYS2-RF_2023-04-24_01 does not match the rig's 323_EPHYS1_20231003." - with self.assertRaises(ValueError) as context: - RigSessionCompatibility(rig=ephys_rig, session=ephys_session).run_compatibility_check() - self.assertIn(expected_error, str(context.exception)) + + self.assertRaises( + ValueError, + InstrumentSessionCompatibility( + instrument=self.ophys_instrument, session=self.ophys_session + ).run_compatibility_check, + ) with self.assertRaises(ValueError): - RigSessionCompatibility(rig=self.ophys_rig, session=self.ophys_session).run_compatibility_check() + InstrumentSessionCompatibility( + instrument=self.ophys_instrument, session=self.ophys_session + ).run_compatibility_check() def test_check_examples_compatibility(self): """Tests that examples are compatible""" # check that ephys session and rig are synced - example_ephys_check = RigSessionCompatibility(rig=self.example_ephys_rig, session=self.example_ephys_session) + example_ephys_check = InstrumentSessionCompatibility( + instrument=self.example_ephys_inst, session=self.example_ephys_session + ) self.assertIsNone(example_ephys_check.run_compatibility_check()) diff --git a/tests/test_instrument.py b/tests/test_instrument.py new file mode 100644 index 000000000..473f3aadf --- /dev/null +++ b/tests/test_instrument.py @@ -0,0 +1,450 @@ +""" test Instrument """ + +import json +import unittest +from datetime import date + +from aind_data_schema_models.modalities import Modality +from aind_data_schema_models.organizations import Organization +from aind_data_schema_models.units import FrequencyUnit +from pydantic import ValidationError +from pydantic_core import PydanticSerializationError + +from aind_data_schema.components.devices import ( + Calibration, + Camera, + CameraAssembly, + CameraTarget, + ChannelType, + DAQChannel, + Detector, + DetectorType, + Device, + Disc, + EphysAssembly, + EphysProbe, + ImagingInstrumentType, + Laser, + LaserAssembly, + Lens, + Manipulator, + NeuropixelsBasestation, + Objective, + Olfactometer, + OlfactometerChannel, + Patch, +) +from aind_data_schema.core.instrument import Instrument, DEVICES_REQUIRED + +daqs = [ + NeuropixelsBasestation( + name="Neuropixels basestation", + basestation_firmware_version="1", + bsc_firmware_version="2", + slot=0, + manufacturer=Organization.IMEC, + ports=[], + computer_name="foo", + channels=[ + DAQChannel( + channel_name="123", + device_name="Laser A", + channel_type="Analog Output", + ), + DAQChannel( + channel_name="321", + device_name="Probe A", + channel_type="Analog Output", + ), + DAQChannel( + channel_name="234", + device_name="Camera A", + channel_type="Digital Output", + ), + DAQChannel( + channel_name="2354", + device_name="Disc A", + channel_type="Digital Output", + ), + ], + ) +] + +ems = [ + EphysAssembly( + probes=[EphysProbe(probe_model="Neuropixels 1.0", name="Probe A")], + manipulator=Manipulator( + name="Probe manipulator", + manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, + serial_number="4321", + ), + name="Ephys_assemblyA", + ) +] + +lms = [ + LaserAssembly( + lasers=[ + Laser( + manufacturer=Organization.HAMAMATSU, + serial_number="1234", + name="Laser A", + wavelength=488, + ), + ], + manipulator=Manipulator( + name="Laser manipulator", + manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, + serial_number="1234", + ), + name="Laser_assembly", + collimator=Device(name="Collimator A"), + fiber=Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, + ), + ) +] +cameras = [ + CameraAssembly( + name="cam", + camera_target="Face bottom", + lens=Lens(name="Camera lens", manufacturer=Organization.OTHER), + camera=Camera( + name="Camera A", + detector_type=DetectorType.CAMERA, + manufacturer=Organization.OTHER, + data_interface="USB", + computer_name="ASDF", + frame_rate=144, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1, + sensor_height=1, + chroma="Color", + ), + ) +] +stick_microscopes = [ + CameraAssembly( + name="Assembly A", + camera=Camera( + name="Camera A", + detector_type=DetectorType.CAMERA, + manufacturer=Organization.OTHER, + data_interface="USB", + computer_name="ASDF", + frame_rate=144, + frame_rate_unit=FrequencyUnit.HZ, + sensor_width=1, + sensor_height=1, + chroma="Color", + ), + camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS A VALUE + lens=Lens(name="Lens A", manufacturer=Organization.OTHER), + ) +] +light_sources = [ + Laser( + manufacturer=Organization.HAMAMATSU, + serial_number="1234", + name="Laser A", + wavelength=488, + ) +] +detectors = [ + Detector( + name="FLIR CMOS for Green Channel", + serial_number="21396991", + manufacturer=Organization.FLIR, + model="BFS-U3-20S40M", + detector_type=DetectorType.CAMERA, + data_interface="USB", + cooling="Air", + immersion="air", + bin_width=4, + bin_height=4, + bin_mode="Additive", + crop_width=200, + crop_height=200, + gain=2, + chroma="Monochrome", + bit_depth=16, + ) +] +patch_cords = [ + Patch( + name="Bundle Branching Fiber-optic Patch Cord", + manufacturer=Organization.DORIC, + model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", + core_diameter=200, + numerical_aperture=0.37, + ) +] +objectives = [ + Objective( + name="TLX Objective 1", + numerical_aperture=0.2, + magnification=3.6, + manufacturer=Organization.LIFECANVAS, + immersion="multi", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", + serial_number="Unknown-1", + ) +] +stimulus_devices = [ + Olfactometer( + name="Olfactometer", + manufacturer=Organization.CHAMPALIMAUD, + model="1234", + serial_number="213456", + hardware_version="1", + is_clock_generator=False, + computer_name="W10XXX000", + channels=[ + OlfactometerChannel( + channel_index=0, + channel_type=ChannelType.CARRIER, + flow_capacity=100, + ), + OlfactometerChannel( + channel_index=1, + channel_type=ChannelType.ODOR, + flow_capacity=100, + ), + ], + ) +] +calibration = Calibration( + calibration_date=date(2020, 10, 10), + device_name="Laser A", + description="Laser power calibration", + input={"power percent": [10, 40, 80]}, + output={"power mW": [2, 6, 10]}, +) + + +class InstrumentTests(unittest.TestCase): + """test instrument schemas""" + + def test_constructors(self): + """always returns true""" + + with self.assertRaises(ValidationError): + Instrument() + + inst = Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS, Modality.FIB], + components=[ + *daqs, + *cameras, + *stick_microscopes, + *light_sources, + *lms, + *ems, + *detectors, + *patch_cords, + *stimulus_devices, + ], + mouse_platform=Disc(name="Disc A", radius=1), + calibrations=[ + Calibration( + calibration_date=date(2020, 10, 10), + device_name="Laser A", + description="Laser power calibration", + input={"power percent": [10, 40, 80]}, + output={"power mW": [2, 6, 10]}, + ) + ], + ) + self.assertIsNotNone(inst) + + def test_validator_modality_device_missing(self): + """Test that the modality -> device validator throws validation errors when devices are missing""" + + # Mapping is a dictionary of Modality -> List[Device groups] + for modality_abbreviation, _ in DEVICES_REQUIRED.items(): + with self.assertRaises(ValidationError): + Instrument( + modalities=[Modality.from_abbreviation(modality_abbreviation)], + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + components=[], + calibrations=[], + ) + + def test_validator_modality_device_present(self): + """Test that the modality -> device validator does not throw validation errors when devices are present""" + + # Mapping is a dictionary of Modality -> List[Device groups] + for modality_abbreviation, device_groups in DEVICES_REQUIRED.items(): + + inst = Instrument( + modalities=[Modality.from_abbreviation(modality_abbreviation)], + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + components=[ + *daqs, + *cameras, + *stick_microscopes, + *light_sources, + *lms, + *ems, + *detectors, + *patch_cords, + *stimulus_devices, + *objectives, + ], + calibrations=[], + ) + self.assertIsNotNone(inst) + + def test_validator_notes(self): + """Test the notes validator""" + + # Test when instrument_type is OTHER and notes are empty + with self.assertRaises(ValidationError): + Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.OTHER, + manufacturer=Organization.IMEC, + notes=None, + ) + + # Test when manufacturer is OTHER and notes are empty + with self.assertRaises(ValidationError): + Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.CONFOCAL, + manufacturer=Organization.OTHER, + notes=None, + ) + + # Test when both instrument_type and manufacturer are OTHER and notes are empty + with self.assertRaises(ValidationError): + Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.OTHER, + manufacturer=Organization.OTHER, + notes=None, + ) + + # Test when notes are provided for instrument_type OTHER + inst = Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.OTHER, + manufacturer=Organization.IMEC, + notes="This is a custom instrument type.", + ) + self.assertIsNotNone(inst) + + # Test when notes are provided for manufacturer OTHER + inst = Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.CONFOCAL, + manufacturer=Organization.OTHER, + notes="This is a custom manufacturer.", + ) + self.assertIsNotNone(inst) + + # Test when notes are provided for both instrument_type and manufacturer OTHER + inst = Instrument( + instrument_id="123_EPHYS1-OPTO_20220101", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS], + components=[*daqs, *ems], + instrument_type=ImagingInstrumentType.OTHER, + manufacturer=Organization.OTHER, + notes="This is a custom instrument type and manufacturer.", + ) + self.assertIsNotNone(inst) + + def test_instrument_id_validator(self): + """Tests that instrument_id validator works as expected""" + + with self.assertRaises(ValidationError): + Instrument( + instrument_id="123", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS, Modality.FIB], + components=[ + *daqs, + *cameras, + *stick_microscopes, + *light_sources, + *lms, + *ems, + *detectors, + *patch_cords, + *stimulus_devices, + ], + mouse_platform=Disc(name="Disc A", radius=1), + calibrations=[calibration], + ) + with self.assertRaises(ValidationError): + Instrument( + instrument_id="123_EPHYS-OPTO_2020-01-01", + modification_date=date(2020, 10, 10), + modalities=[Modality.ECEPHYS, Modality.FIB], + components=[ + *daqs, + *cameras, + *stick_microscopes, + *light_sources, + *lms, + *ems, + *detectors, + *patch_cords, + *stimulus_devices, + ], + mouse_platform=Disc(name="Disc A", radius=1), + calibrations=[calibration], + ) + + def test_serialize_modalities(self): + """Tests that modalities serializer can handle different types""" + expected_modalities = [{"name": "Extracellular electrophysiology", "abbreviation": "ecephys"}] + # Case 1: Modality is a class instance + instrument_instance_modality = Instrument.model_construct( + instrument_id="123_EPHYS1-OPTO_20220101", + modalities={Modality.ECEPHYS}, # Example with a valid Modality instance + ) + instrument_json = instrument_instance_modality.model_dump_json() + instrument_data = json.loads(instrument_json) + self.assertEqual(instrument_data["modalities"], expected_modalities) + + # Case 2: Modality is a dictionary when Rig is constructed from JSON + instrument_dict_modality = Instrument.model_construct(**instrument_data) + instrument_dict_json = instrument_dict_modality.model_dump_json() + instrument_dict_data = json.loads(instrument_dict_json) + self.assertEqual(instrument_dict_data["modalities"], expected_modalities) + + # Case 3: Modality is an unknown type + with self.assertRaises(PydanticSerializationError) as context: + instrument_unknown_modality = Instrument.model_construct(modalities={"UnknownModality"}) + + instrument_unknown_modality.model_dump_json() + self.assertIn("Error calling function `serialize_modalities`", str(context.exception)) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 28f87a2a5..6945a12bb 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -9,15 +9,25 @@ from aind_data_schema_models.modalities import Modality from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.platforms import Platform from pydantic import ValidationError from pydantic import __version__ as pyd_version -from aind_data_schema.components.devices import MousePlatform +from aind_data_schema.components.devices import ( + Device, + EphysAssembly, + EphysProbe, + LickSensorType, + Manipulator, + MotorizedStage, + MousePlatform, + Objective, + RewardDelivery, + RewardSpout, + SpoutSide, +) from aind_data_schema.components.identifiers import Person from aind_data_schema.core.acquisition import Acquisition from aind_data_schema.core.data_description import DataDescription, Funding -from aind_data_schema.core.instrument import Instrument from aind_data_schema.core.metadata import ExternalPlatforms, Metadata, MetadataStatus, create_metadata_json from aind_data_schema.core.procedures import ( IontophoresisInjection, @@ -27,12 +37,24 @@ ViralMaterial, ) from aind_data_schema.core.processing import PipelineProcess, Processing -from aind_data_schema.core.rig import Rig +from aind_data_schema.core.instrument import Instrument from aind_data_schema.core.session import Session from aind_data_schema.core.subject import BreedingInfo, Housing, Sex, Species, Subject +from tests.resources.spim_instrument import inst +from tests.resources.ephys_instrument import inst as ephys_inst PYD_VERSION = re.match(r"(\d+.\d+).\d+", pyd_version).group(1) +ephys_assembly = EphysAssembly( + probes=[EphysProbe(probe_model="Neuropixels 1.0", name="Probe A")], + manipulator=Manipulator( + name="Probe manipulator", + manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, + serial_number="4321", + ), + name="Ephys_assemblyA", +) + class TestMetadata(unittest.TestCase): """Class to test Metadata model""" @@ -40,6 +62,8 @@ class TestMetadata(unittest.TestCase): @classmethod def setUpClass(cls) -> None: """Set up the test class.""" + cls.spim_instrument = inst + subject = Subject( species=Species.MUS_MUSCULUS, subject_id="12345", @@ -58,9 +82,7 @@ def setUpClass(cls) -> None: background_strain="C57BL/6J", ) dd = DataDescription( - label="test_data", - modality=[Modality.ECEPHYS], - platform=Platform.ECEPHYS, + modalities=[Modality.ECEPHYS], subject_id="123456", data_level="raw", creation_time=datetime(2022, 11, 22, 8, 43, 00, tzinfo=timezone.utc), @@ -75,8 +97,8 @@ def setUpClass(cls) -> None: processing_pipeline=PipelineProcess(experimenters=[Person(name="Dan Processor")], data_processes=[]), ) - cls.sample_name = "ecephys_655019_2023-04-03_18-17-09" - cls.sample_location = "s3://bucket/ecephys_655019_2023-04-03_18-17-09" + cls.sample_name = "655019_2023-04-03T181709" + cls.sample_location = "s3://bucket/655019_2023-04-03T181709" cls.subject = subject cls.dd = dd cls.procedures = procedures @@ -105,8 +127,8 @@ def test_valid_subject_info(self): ), genotype="Emx1-IRES-Cre;Camk2a-tTA;Ai93(TITL-GCaMP6f)/wt", ) - d1 = Metadata(name="ecephys_655019_2023-04-03_18-17-09", location="bucket", subject=s1) - self.assertEqual("ecephys_655019_2023-04-03_18-17-09", d1.name) + d1 = Metadata(name="655019_2023-04-03T181709", location="bucket", subject=s1) + self.assertEqual("655019_2023-04-03T181709", d1.name) self.assertEqual("bucket", d1.location) self.assertEqual(MetadataStatus.VALID, d1.metadata_status) self.assertEqual(s1, d1.subject) @@ -116,11 +138,11 @@ def test_missing_subject_info(self): present""" d1 = Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", ) self.assertEqual(MetadataStatus.MISSING, d1.metadata_status) - self.assertEqual("ecephys_655019_2023-04-03_18-17-09", d1.name) + self.assertEqual("655019_2023-04-03T181709", d1.name) self.assertEqual("bucket", d1.location) # Assert at least a name and location are required @@ -142,7 +164,7 @@ def test_invalid_core_models(self): metadata_status as INVALID""" # Invalid subject model - d1 = Metadata(name="ecephys_655019_2023-04-03_18-17-09", location="bucket", subject=Subject.model_construct()) + d1 = Metadata(name="655019_2023-04-03T181709", location="bucket", subject=Subject.model_construct()) self.assertEqual(MetadataStatus.INVALID, d1.metadata_status) # Valid subject model, but invalid procedures model @@ -162,7 +184,7 @@ def test_invalid_core_models(self): genotype="Emx1-IRES-Cre;Camk2a-tTA;Ai93(TITL-GCaMP6f)/wt", ) d2 = Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", subject=s2, procedures=Procedures.model_construct(injection_materials=["some materials"]), @@ -171,7 +193,7 @@ def test_invalid_core_models(self): # Tests constructed via dictionary d3 = Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", subject=json.loads(Subject.model_construct().model_dump_json()), ) @@ -191,13 +213,12 @@ def test_validate_smartspim_metadata(self): surgery1 = Surgery.model_construct(procedures=[nano_inj, ionto_inj]) with self.assertRaises(ValidationError) as context: Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.SMARTSPIM, creation_time=time(12, 12, 12), - modality=[Modality.SPIM], + modalities=[Modality.SPIM], + subject_id="655019", ), procedures=Procedures.model_construct(subject_procedures=[surgery1]), acquisition=Acquisition.model_construct(), @@ -207,44 +228,21 @@ def test_validate_smartspim_metadata(self): str(context.exception), ) - # Tests excluded metadata getting included - surgery1 = Surgery.model_construct(procedures=[nano_inj, ionto_inj]) - with self.assertRaises(ValidationError) as context: - Metadata( - name="ecephys_655019_2023-04-03_18-17-09", - location="bucket", - data_description=DataDescription.model_construct( - label="some label", - platform=Platform.SMARTSPIM, - creation_time=time(12, 12, 12), - modality=[Modality.SPIM], - ), - subject=Subject.model_construct(), - session=Session.model_construct(), - procedures=Procedures.model_construct(subject_procedures=[surgery1]), - acquisition=Acquisition.model_construct(), - ) - self.assertIn( - "SPIM metadata includes excluded file: session", - str(context.exception), - ) - # Tests missing injection materials surgery2 = Surgery.model_construct(procedures=[nano_inj]) with self.assertRaises(ValidationError) as context: Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.SMARTSPIM, creation_time=time(12, 12, 12), - modality=[Modality.SPIM], + modalities=[Modality.SPIM], + subject_id="655019", ), subject=Subject.model_construct(), procedures=Procedures.model_construct(subject_procedures=[surgery2]), acquisition=Acquisition.model_construct(), - instrument=Instrument.model_construct(), + instrument=inst, processing=Processing.model_construct(), ) self.assertIn("Injection is missing injection_materials.", str(context.exception)) @@ -258,25 +256,75 @@ def test_multi_modal_metadata(self): surgery1 = Surgery.model_construct(procedures=[nano_inj, ionto_inj]) mouse_platform = MousePlatform.model_construct(name="platform1") - rig = Rig.model_construct(rig_id="123_EPHYS1_20220101", mouse_platform=mouse_platform) - session = Session.model_construct(rig_id="123_EPHYS1_20220101", mouse_platform_name="platform1") + + objective = Objective( + name="TLX Objective", + numerical_aperture=0.2, + magnification=3.6, + immersion="multi", + manufacturer=Organization.THORLABS, + model="TL4X-SAP", + notes="Thorlabs TL4X-SAP with LifeCanvas dipping cap and correction optics.", + ) + + reward_delivery = RewardDelivery( + reward_spouts=[ + RewardSpout( + name="Left spout", + side=SpoutSide.LEFT, + spout_diameter=1.2, + solenoid_valve=Device(name="Solenoid Left"), + lick_sensor=Device( + name="Janelia_Lick_Detector Left", + manufacturer=Organization.JANELIA, + ), + lick_sensor_type=LickSensorType("Capacitive"), + ), + RewardSpout( + name="Right spout", + side=SpoutSide.RIGHT, + spout_diameter=1.2, + solenoid_valve=Device(name="Solenoid Right"), + lick_sensor=Device( + name="Janelia_Lick_Detector Right", + manufacturer=Organization.JANELIA, + ), + lick_sensor_type=LickSensorType("Capacitive"), + ), + ], + stage_type=MotorizedStage( + name="NewScaleMotor for LickSpouts", + serial_number="xxxx", # grabbing from GUI/SettingFiles + manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, + travel=15.0, # unit is mm + firmware=( + "https://github.com/AllenNeuralDynamics/python-newscale,branch: axes-on-target,commit #7c17497" + ), + ), + ) + + inst = Instrument.model_construct( + instrument_id="123_EPHYS1_20220101", + mouse_platform=mouse_platform, + modalities=[Modality.BEHAVIOR, Modality.SPIM], + components=[objective, reward_delivery], + ) + session = Session.model_construct(instrument_id="123_EPHYS1_20220101", mouse_platform_name="platform1") m = Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.SMARTSPIM, + subject_id="655019", creation_time=time(12, 12, 12), - modality=[Modality.BEHAVIOR, Modality.SPIM], # technically this is impossible, but we need to test it + modalities=[Modality.BEHAVIOR, Modality.SPIM], # technically this is impossible, but we need to test it ), subject=Subject.model_construct(), session=session, # SPIM excludes session, but BEHAVIOR requires it procedures=Procedures.model_construct(subject_procedures=[surgery1]), acquisition=Acquisition.model_construct(), - rig=rig, + instrument=inst, processing=Processing.model_construct(), - instrument=Instrument.model_construct(), ) self.assertIsNotNone(m) @@ -288,18 +336,23 @@ def test_validate_ecephys_metadata(self): # Tests missing metadata surgery1 = Surgery.model_construct(procedures=[nano_inj, ionto_inj]) + modalities = [Modality.ECEPHYS] with self.assertRaises(ValidationError) as context: Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.ECEPHYS, creation_time=time(12, 12, 12), - modality=[Modality.ECEPHYS], + modalities=modalities, + subject_id="655019", ), procedures=Procedures.model_construct(subject_procedures=[surgery1]), - rig=Rig.model_construct(), + instrument=Instrument.model_construct( + modalities=modalities, + components=[ + ephys_assembly, + ], + ), ) self.assertIn( "ECEPHYS metadata missing required file: subject", @@ -308,74 +361,56 @@ def test_validate_ecephys_metadata(self): # Tests missing injection materials surgery2 = Surgery.model_construct(procedures=[nano_inj]) + modalities = [Modality.ECEPHYS] with self.assertRaises(ValidationError) as context: Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.ECEPHYS, creation_time=time(12, 12, 12), - modality=[Modality.ECEPHYS], + modalities=modalities, + subject_id="655019", ), subject=Subject.model_construct(), procedures=Procedures.model_construct(subject_procedures=[surgery2]), - rig=Rig.model_construct(), + instrument=ephys_inst, processing=Processing.model_construct(), session=Session.model_construct(), + acquisition=Acquisition.model_construct(instrument_id="323_EPHYS1_20231003"), ) self.assertIn("Injection is missing injection_materials.", str(context.exception)) - def test_validate_underscore_modality(self): - """Tests that ecephys validator works as expected""" - viral_material = ViralMaterial.model_construct() - nano_inj = NanojectInjection.model_construct(injection_materials=[viral_material]) - ionto_inj = IontophoresisInjection.model_construct(injection_materials=[viral_material]) - mouse_platform = MousePlatform.model_construct(name="platform1") - rig = Rig.model_construct(rig_id="123_EPHYS2_20230101", mouse_platform=mouse_platform) - session = Session.model_construct(rig_id="123_EPHYS2_20230101", mouse_platform_name="platform1") - - # Tests missing metadata - surgery1 = Surgery.model_construct(procedures=[nano_inj, ionto_inj]) - m = Metadata( - name="ecephys_655019_2023-04-03_18-17-09", - location="bucket", - data_description=DataDescription.model_construct( - label="some label", - platform=Platform.ECEPHYS, - creation_time=time(12, 12, 12), - modality=[Modality.BEHAVIOR_VIDEOS], - ), - subject=Subject.model_construct(), - procedures=Procedures.model_construct(subject_procedures=[surgery1]), - rig=rig, - session=session, - ) - self.assertIsNotNone(m) + def test_validate_instrument_session_compatibility(self): + """Tests that instrument/session compatibility validator works as expected""" - def test_validate_rig_session_compatibility(self): - """Tests that rig/session compatibility validator works as expected""" + modalities = [Modality.ECEPHYS] mouse_platform = MousePlatform.model_construct(name="platform1") - rig = Rig.model_construct(rig_id="123_EPHYS1_20220101", mouse_platform=mouse_platform) - session = Session.model_construct(rig_id="123_EPHYS2_20230101", mouse_platform_name="platform2") + inst = Instrument.model_construct( + instrument_id="123_EPHYS1_20220101", + mouse_platform=mouse_platform, + modalities=modalities, + components=[ephys_assembly], + ) with self.assertRaises(ValidationError) as context: Metadata( - name="ecephys_655019_2023-04-03_18-17-09", + name="655019_2023-04-03T181709", location="bucket", data_description=DataDescription.model_construct( - label="some label", - platform=Platform.ECEPHYS, creation_time=time(12, 12, 12), - modality=[Modality.ECEPHYS], + modalities=modalities, + subject_id="655019", ), subject=Subject.model_construct(), procedures=Procedures.model_construct(), - rig=rig, + instrument=inst, processing=Processing.model_construct(), - session=session, + acquisition=Acquisition.model_construct( + instrument_id="123_EPHYS2_20230101", + ), + session=Session.model_construct(instrument_id="123_EPHYS2_20230101", mouse_platform_name="platform1"), ) self.assertIn( - "Rig ID in session 123_EPHYS2_20230101 does not match the rig's 123_EPHYS1_20220101.", + "Instrument ID in session 123_EPHYS2_20230101 does not match the instrument's 123_EPHYS1_20220101.", str(context.exception), ) @@ -403,10 +438,9 @@ def test_create_from_core_jsons(self): "data_description": None, "procedures": self.procedures_json, "session": None, - "rig": None, + "instrument": None, "processing": self.processing_json, "acquisition": None, - "instrument": None, "quality_control": None, } expected_md = Metadata( @@ -470,10 +504,9 @@ def test_create_from_core_jsons_invalid(self, mock_warning: MagicMock): "data_description": self.dd_json, "procedures": self.procedures_json, "session": None, - "rig": None, + "instrument": None, "processing": self.processing_json, "acquisition": None, - "instrument": None, "quality_control": None, } # there are some userwarnings when creating Subject from json @@ -507,10 +540,9 @@ def test_create_from_core_jsons_corrupt(self, mock_is_dict_corrupt: MagicMock, m "data_description": None, "procedures": self.procedures_json, "session": None, - "rig": None, + "instrument": None, "processing": self.processing_json, "acquisition": None, - "instrument": None, "quality_control": None, } # there are some userwarnings when creating Subject from json diff --git a/tests/test_model.py b/tests/test_model.py index 660840470..9a81adc5c 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -29,7 +29,7 @@ def test_constructors(self): license="CC-BY-4.0", developers=[Person(name="Dr. Dan")], developer_institution=Organization.AIND, - modality=[Modality.SPIM], + modalities=[Modality.SPIM], pretrained_source_url="url pretrained weights are from", architecture=ModelArchitecture( backbone=ModelBackbone.RESNET, diff --git a/tests/test_procedures.py b/tests/test_procedures.py index 5db9b2c19..3e633bd58 100644 --- a/tests/test_procedures.py +++ b/tests/test_procedures.py @@ -215,7 +215,6 @@ def test_injection_material_check(self): probes=[ OphysProbe( ophys_probe=FiberProbe( - device_type="Fiber optic probe", name="Probe A", manufacturer=Organization.DORIC, model="8", diff --git a/tests/test_rig.py b/tests/test_rig.py deleted file mode 100644 index faa5cccb0..000000000 --- a/tests/test_rig.py +++ /dev/null @@ -1,861 +0,0 @@ -""" test Rig """ - -import json -import unittest -from datetime import date, datetime - -from aind_data_schema_models.modalities import Modality -from aind_data_schema_models.organizations import Organization -from aind_data_schema_models.units import FrequencyUnit -from pydantic import ValidationError -from pydantic_core import PydanticSerializationError - -from aind_data_schema.components.devices import ( - Calibration, - Camera, - CameraAssembly, - CameraTarget, - ChannelType, - DAQChannel, - Detector, - DetectorType, - Device, - Disc, - EphysAssembly, - EphysProbe, - Laser, - LaserAssembly, - Lens, - Manipulator, - NeuropixelsBasestation, - Olfactometer, - OlfactometerChannel, - Patch, -) -from aind_data_schema.core.rig import Rig - - -class RigTests(unittest.TestCase): - """test rig schemas""" - - def test_constructors(self): - """always returns true""" - - with self.assertRaises(ValidationError): - Rig() - - daqs = [ - NeuropixelsBasestation( - name="Neuropixels basestation", - basestation_firmware_version="1", - bsc_firmware_version="2", - slot=0, - manufacturer=Organization.IMEC, - ports=[], - computer_name="foo", - channels=[ - DAQChannel( - channel_name="123", - device_name="Laser A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="321", - device_name="Probe A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="234", - device_name="Camera A", - channel_type="Digital Output", - ), - DAQChannel( - channel_name="2354", - device_name="Disc A", - channel_type="Digital Output", - ), - ], - ) - ] - - ems = [ - EphysAssembly( - probes=[EphysProbe(probe_model="Neuropixels 1.0", name="Probe A")], - manipulator=Manipulator( - name="Probe manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="4321", - ), - name="Ephys_assemblyA", - ) - ] - - lms = [ - LaserAssembly( - lasers=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ), - ], - manipulator=Manipulator( - name="Laser manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="1234", - ), - name="Laser_assembly", - collimator=Device(name="Collimator A", device_type="Collimator"), - fiber=Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ), - ) - ] - - rig = Rig( - rig_id="123_EPHYS1-OPTO_20220101", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[ - CameraAssembly( - name="cam", - camera_target="Face bottom", - lens=Lens(name="Camera lens", manufacturer=Organization.OTHER), - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - ) - ], - stick_microscopes=[ - CameraAssembly( - name="Assembly A", - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS A VALUE - lens=Lens(name="Lens A", manufacturer=Organization.OTHER), - ) - ], - light_sources=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ) - ], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[ - Detector( - name="FLIR CMOS for Green Channel", - serial_number="21396991", - manufacturer=Organization.FLIR, - model="BFS-U3-20S40M", - detector_type=DetectorType.CAMERA, - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ) - ], - patch_cords=[ - Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - stimulus_devices=[ - Olfactometer( - name="Olfactometer", - manufacturer=Organization.CHAMPALIMAUD, - model="1234", - serial_number="213456", - hardware_version="1", - is_clock_generator=False, - computer_name="W10XXX000", - channels=[ - OlfactometerChannel( - channel_index=0, - channel_type=ChannelType.CARRIER, - flow_capacity=100, - ), - OlfactometerChannel( - channel_index=1, - channel_type=ChannelType.ODOR, - flow_capacity=100, - ), - ], - ) - ], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[ - Calibration( - calibration_date=date(2020, 10, 10), - device_name="Laser A", - description="Laser power calibration", - input={"power percent": [10, 40, 80]}, - output={"power mW": [2, 6, 10]}, - ) - ], - ) - - assert rig is not None - - def test_validator(self): - """Test the rig file validators""" - - # A Rig model with ECEPHYS in the modality list requires - # ephys_assemblies and stick microscopes - with self.assertRaises(ValidationError): - Rig( - modalities=[ - Modality.ECEPHYS, - Modality.SLAP, - Modality.FIB, - Modality.BEHAVIOR_VIDEOS, - Modality.POPHYS, - Modality.BEHAVIOR, - ], - rig_id="123_EPHYS1-OPTO_20220101", - modification_date=date(2020, 10, 10), - daqs=[ - NeuropixelsBasestation( - name="Basestation", - basestation_firmware_version="1", - bsc_firmware_version="2", - slot=0, - manufacturer=Organization.IMEC, - ports=[], - computer_name="foo", - channels=[ - DAQChannel( - channel_name="123", - device_name="Laser A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="321", - device_name="Probe A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="234", - device_name="Camera A", - channel_type="Digital Output", - ), - DAQChannel( - channel_name="2354", - device_name="Disc A", - channel_type="Digital Output", - ), - ], - ) - ], - calibrations=[ - Calibration( - calibration_date=date(2020, 10, 10), - device_name="Laser A", - description="Laser power calibration", - input={"power percent": [10, 40, 80]}, - output={"power mW": [2, 6, 10]}, - ) - ], - mouse_platform=Disc(name="Disc A", radius=1), - ) - - with self.assertRaises(ValidationError): - daqs = [ - NeuropixelsBasestation( - name="Basestation", - basestation_firmware_version="1", - bsc_firmware_version="2", - slot=0, - manufacturer=Organization.IMEC, - ports=[], - computer_name="foo", - channels=[ - DAQChannel( - channel_name="123", - device_name="Laser A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="321", - device_name="Probe A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="234", - device_name="Camera A", - channel_type="Digital Output", - ), - DAQChannel( - channel_name="2354", - device_name="Disc A", - channel_type="Digital Output", - ), - ], - ) - ] - - Rig( - rig_id="123_EPHYS1-OPTO_20220101", - modification_date=datetime.now(), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - ) - - with self.assertRaises(ValueError): - ems = [ - EphysAssembly( - probes=[EphysProbe(probe_model="Neuropixels 1.0", name="Probe A")], - manipulator=Manipulator( - name="Probe manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="4321", - ), - name="Ephys_assemblyA", - ) - ] - - lms = [ - LaserAssembly( - lasers=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ), - ], - manipulator=Manipulator( - name="Laser manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="1234", - ), - name="Laser_assembly", - collimator=Device(name="Collimator B", device_type="Collimator"), - fiber=Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ), - ) - ] - - Rig( - rig_id="1234", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[ - CameraAssembly( - name="cam", - camera_target=CameraTarget.OTHER, - lens=Lens(name="Camera lens", manufacturer=Organization.OTHER), - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - ) - ], - stick_microscopes=[ - CameraAssembly( - name="Assembly A", - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - camera_target=CameraTarget.OTHER, - lens=Lens(name="Lens A", manufacturer=Organization.OTHER), - ) - ], - light_sources=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ) - ], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[ - Detector( - name="FLIR CMOS for Green Channel", - serial_number="21396991", - manufacturer=Organization.FLIR, - model="BFS-U3-20S40M", - detector_type=DetectorType.CAMERA, - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ) - ], - patch_cords=[ - Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - stimulus_devices=[ - Olfactometer( - name="Olfactometer", - manufacturer=Organization.CHAMPALIMAUD, - model="1234", - serial_number="213456", - hardware_version="1", - is_clock_generator=False, - computer_name="W10XXX000", - channels=[ - OlfactometerChannel( - channel_index=0, - channel_type=ChannelType.CARRIER, - flow_capacity=100, - ), - OlfactometerChannel( - channel_index=1, - channel_type=ChannelType.ODOR, - flow_capacity=100, - ), - ], - ) - ], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[ - Calibration( - calibration_date=date(2020, 10, 10), - device_name="Laser A", - description="Laser power calibration", - input={"power percent": [10, 40, 80]}, - output={"power mW": [2, 6, 10]}, - ) - ], - ) - - # Tests that validators catch empty lists without KeyErrors - with self.assertRaises(ValidationError): - Rig( - rig_id="123_EPHYS1-OPTO_20220101", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[], - stick_microscopes=[], - light_sources=[], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[], - patch_cords=[], - stimulus_devices=[], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[], - ) - - with self.assertRaises(ValidationError): - camera = CameraAssembly( - name="cam", - camera_target=CameraTarget.OTHER, - lens=Lens(name="Camera lens", manufacturer=Organization.OTHER), - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - ) - Rig( - rig_id="123_EPHYS-OPTO_20200101", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[camera], - stick_microscopes=[ - CameraAssembly( - name="Assembly A", - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS A VALUE - lens=Lens(name="Lens A", manufacturer=Organization.OTHER), - ) - ], - light_sources=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ) - ], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[ - Detector( - name="FLIR CMOS for Green Channel", - serial_number="21396991", - manufacturer=Organization.FLIR, - model="BFS-U3-20S40M", - detector_type=DetectorType.CAMERA, - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ) - ], - patch_cords=[ - Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - ], - stimulus_devices=[ - Olfactometer( - name="Olfactometer", - manufacturer=Organization.CHAMPALIMAUD, - model="1234", - serial_number="213456", - hardware_version="1", - is_clock_generator=False, - computer_name="W10XXX000", - channels=[ - OlfactometerChannel( - channel_index=0, - channel_type=ChannelType.CARRIER, - flow_capacity=100, - ), - ], - ) - ], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[ - Calibration( - calibration_date=date(2020, 10, 10), - device_name="Laser A", - description="Laser power calibration", - input={"power percent": [10, 40, 80]}, - output={"power mW": [2, 6, 10]}, - ) - ], - ) - - def test_rig_id_validator(self): - """Tests that rig_id validator works as expected""" - daqs = [ - NeuropixelsBasestation( - name="Neuropixels basestation", - basestation_firmware_version="1", - bsc_firmware_version="2", - slot=0, - manufacturer=Organization.IMEC, - ports=[], - computer_name="foo", - channels=[ - DAQChannel( - channel_name="123", - device_name="Laser A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="321", - device_name="Probe A", - channel_type="Analog Output", - ), - DAQChannel( - channel_name="234", - device_name="Camera A", - channel_type="Digital Output", - ), - DAQChannel( - channel_name="2354", - device_name="Disc A", - channel_type="Digital Output", - ), - ], - ) - ] - - ems = [ - EphysAssembly( - probes=[EphysProbe(probe_model="Neuropixels 1.0", name="Probe A")], - manipulator=Manipulator( - name="Probe manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="4321", - ), - name="Ephys_assemblyA", - ) - ] - - lms = [ - LaserAssembly( - lasers=[ - Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ), - ], - manipulator=Manipulator( - name="Laser manipulator", - manufacturer=Organization.NEW_SCALE_TECHNOLOGIES, - serial_number="1234", - ), - name="Laser_assembly", - collimator=Device(name="Collimator A", device_type="Collimator"), - fiber=Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ), - ) - ] - camera = CameraAssembly( - name="cam", - camera_target="Face bottom", - lens=Lens(name="Camera lens", manufacturer=Organization.OTHER), - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - ) - - stick_microscope = CameraAssembly( - name="Assembly A", - camera=Camera( - name="Camera A", - detector_type=DetectorType.CAMERA, - manufacturer=Organization.OTHER, - data_interface="USB", - computer_name="ASDF", - frame_rate=144, - frame_rate_unit=FrequencyUnit.HZ, - sensor_width=1, - sensor_height=1, - chroma="Color", - ), - camera_target=CameraTarget.BRAIN_SURFACE, # NEEDS A VALUE - lens=Lens(name="Lens A", manufacturer=Organization.OTHER), - ) - light_source = Laser( - manufacturer=Organization.HAMAMATSU, - serial_number="1234", - name="Laser A", - wavelength=488, - ) - detector = Detector( - name="FLIR CMOS for Green Channel", - serial_number="21396991", - manufacturer=Organization.FLIR, - model="BFS-U3-20S40M", - detector_type=DetectorType.CAMERA, - data_interface="USB", - cooling="Air", - immersion="air", - bin_width=4, - bin_height=4, - bin_mode="Additive", - crop_width=200, - crop_height=200, - gain=2, - chroma="Monochrome", - bit_depth=16, - ) - patch_cord = Patch( - name="Bundle Branching Fiber-optic Patch Cord", - manufacturer=Organization.DORIC, - model="BBP(4)_200/220/900-0.37_Custom_FCM-4xMF1.25", - core_diameter=200, - numerical_aperture=0.37, - ) - stimulus_device = Olfactometer( - name="Olfactometer", - manufacturer=Organization.CHAMPALIMAUD, - model="1234", - serial_number="213456", - hardware_version="1", - is_clock_generator=False, - computer_name="W10XXX000", - channels=[ - OlfactometerChannel( - channel_index=0, - channel_type=ChannelType.CARRIER, - flow_capacity=100, - ), - OlfactometerChannel( - channel_index=1, - channel_type=ChannelType.ODOR, - flow_capacity=100, - ), - ], - ) - calibration = Calibration( - calibration_date=date(2020, 10, 10), - device_name="Laser A", - description="Laser power calibration", - input={"power percent": [10, 40, 80]}, - output={"power mW": [2, 6, 10]}, - ) - - with self.assertRaises(ValidationError): - Rig( - rig_id="123", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[camera], - stick_microscopes=[stick_microscope], - light_sources=[light_source], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[detector], - patch_cords=[patch_cord], - stimulus_devices=[stimulus_device], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[calibration], - ) - with self.assertRaises(ValidationError): - Rig( - rig_id="123_EPHYS-OPTO_2020-01-01", - modification_date=date(2020, 10, 10), - modalities=[Modality.ECEPHYS, Modality.FIB], - daqs=daqs, - cameras=[camera], - stick_microscopes=[stick_microscope], - light_sources=[light_source], - laser_assemblies=lms, - ephys_assemblies=ems, - detectors=[detector], - patch_cords=[patch_cord], - stimulus_devices=[stimulus_device], - mouse_platform=Disc(name="Disc A", radius=1), - calibrations=[calibration], - ) - - def test_serialize_modalities(self): - """Tests that modalities serializer can handle different types""" - expected_modalities = [{"name": "Extracellular electrophysiology", "abbreviation": "ecephys"}] - # Case 1: Modality is a class instance - rig_instance_modality = Rig.model_construct( - rig_id="123_EPHYS1-OPTO_20220101", modalities={Modality.ECEPHYS} # Example with a valid Modality instance - ) - rig_json = rig_instance_modality.model_dump_json() - rig_data = json.loads(rig_json) - self.assertEqual(rig_data["modalities"], expected_modalities) - - # Case 2: Modality is a dictionary when Rig is constructed from JSON - rig_dict_modality = Rig.model_construct(**rig_data) - rig_dict_json = rig_dict_modality.model_dump_json() - rig_dict_data = json.loads(rig_dict_json) - self.assertEqual(rig_dict_data["modalities"], expected_modalities) - - # Case 3: Modality is an unknown type - with self.assertRaises(PydanticSerializationError) as context: - rig_unknown_modality = Rig.model_construct(modalities={"UnknownModality"}) - - rig_unknown_modality.model_dump_json() - self.assertIn("Error calling function `serialize_modalities`", str(context.exception)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_session.py b/tests/test_session.py index b8eaa9c91..e2e260fe7 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -46,7 +46,7 @@ def test_constructors(self): session_end_time=datetime.now(), subject_id="1234", session_type="Test", - rig_id="1234", + instrument_id="1234", mouse_platform_name="Running wheel", active_mouse_platform=False, data_streams=[ @@ -127,7 +127,7 @@ def test_constructors(self): protocol_id=["doi_path"], ethics_review_id="1234", session_type="3D MRI Volume", - rig_id="NA", + instrument_id="NA", animal_weight_prior=22.1, animal_weight_post=21.9, data_streams=[stream],