diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a57d7..9977a8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,23 @@ -# v0.4.0 (upcoming) +# v0.4.0 (Upcoming) + +## Deprecations and Changes +- **Breaking Change**: `OpticalLens`changed in `ObjectiveLens` +- **Breaking Change**: `MicroscopyChannel.indicator` changed from a nested group to a link reference +- **Breaking Change**: `MicroscopySeries.microscopy_rig` changed from a nested group to a link reference +- Updated `ndx-ophys-devices` dependency from v0.2.0 to v0.4.0 + +## Features +- Added `MicroscopyExperimentMetadata` (extends `LabMetaData`) as a centralized container for experiment metadata, including: + - `MicroscopyRig` objects + - `ViralVector` objects (from ndx-ophys-devices) + - `ViralVectorInjection` objects (from ndx-ophys-devices) + - `Indicator` objects (from ndx-ophys-devices) +- Added support for `ViralVector` and `ViralVectorInjection` imports from ndx-ophys-devices + +## Notes +- These changes improve metadata organization by centralizing all experiment-related objects in `MicroscopyExperimentMetadata` +- The use of links instead of nested groups provides better data reusability and reduces duplication +- Users should add `MicroscopyExperimentMetadata` to NWBFile using `nwbfile.add_lab_meta_data()` # v0.3.0 (Jun 3, 2025) ## Bug Fixes diff --git a/README.md b/README.md index 00ac077..c9f9785 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ A Neurodata Without Borders (NWB) extension for storing microscopy data and asso ## Features **Comprehensive Neurodata Types** +- Experiment metadata container: + - `MicroscopyExperimentMetadata` - Microscope and optical component metadata (integration with [ndx-ophys-devices](https://github.com/catalystneuro/ndx-ophys-devices)): - `MicroscopeModel` - `Microscope` @@ -14,6 +16,8 @@ A Neurodata Without Borders (NWB) extension for storing microscopy data and asso - `DichroicMirror` - `Photodetector` - `Indicator` + - `ViralVector` + - `ViralVectorInjection` - Microscopy channel configurations: - `MicroscopyChannel` - Imaging space definitions: @@ -56,7 +60,7 @@ classDiagram model_number : text, optional } - class DeviceInstance{ + class Device{ <> -------------------------------------- attributes @@ -73,7 +77,7 @@ classDiagram } class Microscope { - <> + <> -------------------------------------- attributes -------------------------------------- @@ -98,7 +102,7 @@ classDiagram } class ExcitationSource { - <> + <> -------------------------------------- attributes -------------------------------------- @@ -121,7 +125,7 @@ classDiagram } class OpticalFilter { - <> + <> -------------------------------------- attributes -------------------------------------- @@ -149,7 +153,7 @@ classDiagram } class DichroicMirror { - <> + <> -------------------------------------- attributes -------------------------------------- @@ -161,7 +165,7 @@ classDiagram } class Photodetector { - <> + <> -------------------------------------- attributes -------------------------------------- @@ -184,7 +188,7 @@ classDiagram } DeviceModel <|-- MicroscopeModel : extends - DeviceInstance <|-- Microscope : extends + Device <|-- Microscope : extends Microscope o--> MicroscopeModel : links MicroscopyRig o--> Microscope : links @@ -266,6 +270,90 @@ classDiagram ImagingSpace *-- IlluminationPattern : contains ``` +#### Experiment Metadata Components + +```mermaid +%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#ffffff', 'primaryBorderColor': '#144E73', 'lineColor': '#D96F32'}}}%% + +classDiagram + direction TB + + class MicroscopyExperimentMetadata { + <> + -------------------------------------- + groups + -------------------------------------- + **microscopy_rigs** : MicroscopyRig[0..*] + **viral_vectors** : ViralVector[0..*] + **viral_vector_injections** : ViralVectorInjection[0..*] + **indicators** : Indicator[0..*] + } + + class MicroscopyRig { + <> + -------------------------------------- + attributes + -------------------------------------- + description : text + -------------------------------------- + links + -------------------------------------- + microscope : Microscope + excitation_source : ExcitationSource, optional + excitation_filter : OpticalFilter, optional + dichroic_mirror : DichroicMirror, optional + photodetector : Photodetector, optional + emission_filter : OpticalFilter, optional + } + + class ViralVector { + <> + -------------------------------------- + attributes + -------------------------------------- + **construct_name** : text + titer_in_vg_per_ml : numeric, optional + manufacturer : text, optional + description : text, optional + } + class ViralVectorInjection { + <> + -------------------------------------- + attributes + -------------------------------------- + location : text, optional + hemisphere : text, optional + ap_in_mm : numeric, optional + ml_in_mm : numeric, optional + dv_in_mm : numeric, optional + pitch_in_deg : numeric, optional + yaw_in_deg : numeric, optional + roll_in_deg : numeric, optional + stereotactic_rotation_in_deg : numeric, optional + stereotactic_tilt_in_deg : numeric, optional + volume_in_uL : numeric, optional + injection_date : text, optional + **viral_vector** : ViralVector + } + class Indicator { + <> + -------------------------------------- + attributes + -------------------------------------- + **label** : text + description : text, optional + manufacturer : text, optional + **viral_vector_injection** : ViralVectorInjection, optional + } + + MicroscopyExperimentMetadata *-- MicroscopyRig : contains + MicroscopyExperimentMetadata *-- ViralVector : contains + MicroscopyExperimentMetadata *-- ViralVectorInjection : contains + MicroscopyExperimentMetadata *-- Indicator : contains + ViralVectorInjection o--> ViralVector : links + Indicator o--> ViralVectorInjection : links +``` + #### Microscopy Series and Imaging Space Components ```mermaid @@ -284,17 +372,20 @@ classDiagram **excitation_wavelength_in_nm** : float **emission_wavelength_in_nm** : float -------------------------------------- - groups + links -------------------------------------- - indicator + **indicator** : Indicator } class MicroscopySeries { <> -------------------------------------- - groups + links -------------------------------------- **microscopy_rig** : MicroscopyRig + -------------------------------------- + groups + -------------------------------------- **microscopy_channel** : MicroscopyChannel } @@ -412,9 +503,9 @@ classDiagram VolumetricMicroscopySeries *-- VolumetricImagingSpace : contains MultiPlaneMicroscopyContainer *-- PlanarMicroscopySeries : contains MultiChannelMicroscopyContainer *-- MicroscopySeries : contains - MicroscopySeries *-- MicroscopyRig : contains - MicroscopyChannel *-- MicroscopySeries : contains - MicroscopyChannel --* Indicator : contains + MicroscopySeries o--> MicroscopyRig : links + MicroscopySeries *-- MicroscopyChannel : contains + MicroscopyChannel o--> Indicator : links ``` #### Segmentation Components diff --git a/docs/source/format.rst b/docs/source/format.rst index 8192813..a36c830 100644 --- a/docs/source/format.rst +++ b/docs/source/format.rst @@ -26,7 +26,7 @@ A device instance for acquiring imaging data. groups: - neurodata_type_def: Microscope - neurodata_type_inc: DeviceInstance + neurodata_type_inc: Device doc: Instance of a microscope used to acquire imaging data. attributes: - name: technique @@ -100,10 +100,36 @@ Represents a channel in a microscope with metadata about the indicator and wavel - name: emission_wavelength_in_nm dtype: float64 doc: Wavelength of the emission light in nanometers. + links: + - name: indicator + target_type: Indicator + doc: Link to Indicator object which contains metadata about the indicator used in this light path. + quantity: 1 + +MicroscopyExperimentMetadata +^^^^^^^^^^^^^^^^^^^^^^^^^^ +Container for centralizing all microscopy experiment metadata. + +.. code-block:: yaml + + groups: + - neurodata_type_def: MicroscopyExperimentMetadata + neurodata_type_inc: LabMetaData + doc: Metadata about the microscopy experiment. + name: microscopy_experiment_metadata groups: + - neurodata_type_inc: MicroscopyRig + doc: Group containing of one or more MicroscopyRig objects. + quantity: "*" + - neurodata_type_inc: ViralVector + doc: Group containing of one or more ViralVector objects. + quantity: "*" + - neurodata_type_inc: ViralVectorInjection + doc: Group containing one or more ViralVectorInjection objects. + quantity: "*" - neurodata_type_inc: Indicator - doc: Indicator object which contains metadata about the indicator used in this light path. - quantity: 1 + doc: Group containing one or more Indicator objects. + quantity: "*" Microscopy Series Components ------------------------ @@ -119,10 +145,11 @@ Base type for microscopy time series data. neurodata_type_inc: TimeSeries doc: Imaging data acquired over time from an optical channel in a microscope while a light source illuminates the imaging space. + links: + - name: microscopy_rig + doc: Link to a MicroscopyRig object containing metadata about the microscopy rig used to acquire this imaging data. + target_type: MicroscopyRig groups: - - neurodata_type_inc: MicroscopyRig - doc: MicroscopyRig object containing metadata about the microscopy rig used to acquire this imaging data. - quantity: 1 - neurodata_type_inc: MicroscopyChannel doc: MicroscopyChannel object containing metadata about the channel used to acquire this imaging data. quantity: 1 @@ -440,7 +467,7 @@ Base type for segmentation data. groups: - neurodata_type_def: Segmentation neurodata_type_inc: DynamicTable - doc: Abstract class to contain the results from image segmentation of a specific imaging space. + doc: Abstract class to contain the spatial components resulting from image segmentation of a specific imaging space. attributes: - name: description dtype: text @@ -459,7 +486,7 @@ For 2D segmentation data. groups: - neurodata_type_def: PlanarSegmentation neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific planar imaging space. + doc: ROI spatial components resulting from image segmentation of a specific planar imaging space. datasets: - name: image_mask neurodata_type_inc: VectorData @@ -505,7 +532,7 @@ For 3D segmentation data. groups: - neurodata_type_def: VolumetricSegmentation neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific volumetric imaging space. + doc: ROI spatial components resulting from image segmentation of a specific volumetric imaging space. datasets: - name: volume_mask neurodata_type_inc: VectorData @@ -560,7 +587,7 @@ Container for multiple segmentations. doc: A container of many Segmentation objects. groups: - neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific imaging space. + doc: Results from image segmentation. quantity: "+" SummaryImage @@ -603,7 +630,10 @@ For extracted ROI responses. groups: - neurodata_type_def: MicroscopyResponseSeries neurodata_type_inc: TimeSeries - doc: ROI responses extracted from optical imaging. + doc: + ROI responses extracted from imaging data, linked in the microscopy_series field. + This object contains the temporal components from multiple ROIs, + that can result from different processing steps, e.g., raw, deconvolved, or denoised fluorescence traces. datasets: - name: data dtype: numeric @@ -616,8 +646,9 @@ For extracted ROI responses. doc: Signals from ROIs. - name: rois neurodata_type_inc: DynamicTableRegion - doc: DynamicTableRegion referencing segmentation containing more information about the ROIs - stored in this series. + doc: + DynamicTableRegion referencing Segmentation table containing information about the ROIs + spatial components. links: - name: microscopy_series doc: Link to a MicroscopySeries object containing the imaging data this response series is derived from. diff --git a/docs/source/user_guide.rst b/docs/source/user_guide.rst index d891ead..24687dc 100644 --- a/docs/source/user_guide.rst +++ b/docs/source/user_guide.rst @@ -150,7 +150,59 @@ The device components include MicroscopeModel, Microscope, and MicroscopyRig: Other optical components (filters, sources, detectors) are provided by the ndx-ophys-devices extension. -4. **MicroscopyChannel**: Defines a channel with indicator and wavelength information +4. **MicroscopyExperimentMetadata**: Container for centralizing all experiment metadata + + .. code-block:: python + + from ndx_ophys_devices import Indicator, ViralVector, ViralVectorInjection + + # Create viral vector and injection metadata + viral_vector = ViralVector( + name="viral_vector", + description="AAV viral vector for optogenetic stimulation", + construct_name="AAV-EF1a-DIO-hChR2(H134R)-EYFP", + manufacturer="Vector Manufacturer", + titer_in_vg_per_ml=1.0e12, + ) + + viral_vector_injection = ViralVectorInjection( + name="viral_vector_injection", + description="Viral vector injection for optogenetic stimulation", + location="Hippocampus", + hemisphere="right", + reference="Bregma at the cortical surface", + ap_in_mm=2.0, + ml_in_mm=1.5, + dv_in_mm=-3.0, + pitch_in_deg=0.0, + yaw_in_deg=0.0, + roll_in_deg=0.0, + stereotactic_rotation_in_deg=0.0, + stereotactic_tilt_in_deg=0.0, + volume_in_uL=0.45, + injection_date="1970-01-01T00:00:00+00:00", + viral_vector=viral_vector, + ) + + indicator = Indicator( + name="indicator", + description="Green indicator", + label="GCamp6f", + viral_vector_injection=viral_vector_injection, + ) + + # Create the experiment metadata container + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig] + ) + + # Add to NWB file + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + +5. **MicroscopyChannel**: Defines a channel with indicator and wavelength information .. code-block:: python @@ -159,7 +211,7 @@ Other optical components (filters, sources, detectors) are provided by the ndx-o description='GCaMP6f channel', excitation_wavelength_in_nm=488.0, emission_wavelength_in_nm=520.0, - indicator=indicator # from ndx-ophys-devices + indicator=indicator # Link to indicator from MicroscopyExperimentMetadata ) Illumination Pattern Configuration @@ -332,7 +384,52 @@ Basic workflow for 2D imaging: # ... ) - # 4. Define illumination pattern + # 4. Create experiment metadata with indicators and rig + from ndx_ophys_devices import Indicator, ViralVector, ViralVectorInjection + + viral_vector = ViralVector( + name="viral_vector", + description="AAV viral vector for optogenetic stimulation", + construct_name="AAV-EF1a-DIO-hChR2(H134R)-EYFP", + manufacturer="Vector Manufacturer", + titer_in_vg_per_ml=1.0e12, + ) + + viral_vector_injection = ViralVectorInjection( + name="viral_vector_injection", + description="Viral vector injection for optogenetic stimulation", + location="Hippocampus", + hemisphere="right", + reference="Bregma at the cortical surface", + ap_in_mm=2.0, + ml_in_mm=1.5, + dv_in_mm=-3.0, + pitch_in_deg=0.0, + yaw_in_deg=0.0, + roll_in_deg=0.0, + stereotactic_rotation_in_deg=0.0, + stereotactic_tilt_in_deg=0.0, + volume_in_uL=0.45, + injection_date="1970-01-01T00:00:00+00:00", + viral_vector=viral_vector, + ) + + indicator = Indicator( + name="indicator", + description="Green indicator", + label="GCamp6f", + viral_vector_injection=viral_vector_injection, + ) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig] + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + + # 5. Define illumination pattern line_scan = LineScan( name='line_scanning', description='Line scanning two-photon microscopy', @@ -341,7 +438,7 @@ Basic workflow for 2D imaging: dwell_time_in_s=1.0e-6 ) - # 5. Set up imaging space with illumination pattern + # 6. Set up imaging space with illumination pattern planar_imaging_space = PlanarImagingSpace( name='cortex_plane', description='Layer 2/3 of visual cortex', @@ -354,16 +451,16 @@ Basic workflow for 2D imaging: illumination_pattern=line_scan # Include the illumination pattern ) - # 4. Create microscopy channel + # 7. Create microscopy channel microscopy_channel = MicroscopyChannel( name='gcamp_channel', description='GCaMP6f channel', excitation_wavelength_in_nm=488.0, emission_wavelength_in_nm=520.0, - indicator=indicator # from ndx-ophys-devices + indicator=indicator # Link to indicator from MicroscopyExperimentMetadata ) - # 5. Create imaging series + # 8. Create imaging series microscopy_series = PlanarMicroscopySeries( name='microscopy_series', description='Two-photon calcium imaging', @@ -414,7 +511,52 @@ Workflow for one-photon widefield imaging: # ... ) - # 4. Define illumination pattern + # 4. Create experiment metadata with indicators and rig + from ndx_ophys_devices import Indicator, ViralVector, ViralVectorInjection + + viral_vector = ViralVector( + name="viral_vector", + description="AAV viral vector for optogenetic stimulation", + construct_name="AAV-EF1a-DIO-hChR2(H134R)-EYFP", + manufacturer="Vector Manufacturer", + titer_in_vg_per_ml=1.0e12, + ) + + viral_vector_injection = ViralVectorInjection( + name="viral_vector_injection", + description="Viral vector injection for optogenetic stimulation", + location="Hippocampus", + hemisphere="right", + reference="Bregma at the cortical surface", + ap_in_mm=2.0, + ml_in_mm=1.5, + dv_in_mm=-3.0, + pitch_in_deg=0.0, + yaw_in_deg=0.0, + roll_in_deg=0.0, + stereotactic_rotation_in_deg=0.0, + stereotactic_tilt_in_deg=0.0, + volume_in_uL=0.45, + injection_date="1970-01-01T00:00:00+00:00", + viral_vector=viral_vector, + ) + + indicator = Indicator( + name="indicator", + description="Green indicator", + label="GCamp6f", + viral_vector_injection=viral_vector_injection, + ) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig] + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + + # 5. Define illumination pattern plane_acquisition = PlaneAcquisition( name='plane_acquisition', description='Widefield fluorescence imaging', @@ -422,7 +564,7 @@ Workflow for one-photon widefield imaging: plane_rate_in_Hz=30.0 ) - # 5. Set up imaging space with illumination pattern + # 6. Set up imaging space with illumination pattern planar_imaging_space = PlanarImagingSpace( name='hippo_plane', description='CA1 region of hippocampus', @@ -435,16 +577,16 @@ Workflow for one-photon widefield imaging: illumination_pattern=plane_acquisition ) - # 6. Create microscopy channel + # 7. Create microscopy channel microscopy_channel = MicroscopyChannel( name='gcamp_channel', description='GCaMP6f channel', excitation_wavelength_in_nm=470.0, emission_wavelength_in_nm=520.0, - indicator=indicator # from ndx-ophys-devices + indicator=indicator # Link to indicator from MicroscopyExperimentMetadata ) - # 5. Create imaging series + # 8. Create imaging series microscopy_series = PlanarMicroscopySeries( name='imaging_data', description='One-photon calcium imaging', @@ -495,7 +637,52 @@ Workflow for volumetric imaging with targeted scanning: # ... ) - # 4. Define illumination pattern + # 4. Create experiment metadata with indicators and rig + from ndx_ophys_devices import Indicator, ViralVector, ViralVectorInjection + + viral_vector = ViralVector( + name="viral_vector", + description="AAV viral vector for optogenetic stimulation", + construct_name="AAV-EF1a-DIO-hChR2(H134R)-EYFP", + manufacturer="Vector Manufacturer", + titer_in_vg_per_ml=1.0e12, + ) + + viral_vector_injection = ViralVectorInjection( + name="viral_vector_injection", + description="Viral vector injection for optogenetic stimulation", + location="Hippocampus", + hemisphere="right", + reference="Bregma at the cortical surface", + ap_in_mm=2.0, + ml_in_mm=1.5, + dv_in_mm=-3.0, + pitch_in_deg=0.0, + yaw_in_deg=0.0, + roll_in_deg=0.0, + stereotactic_rotation_in_deg=0.0, + stereotactic_tilt_in_deg=0.0, + volume_in_uL=0.45, + injection_date="1970-01-01T00:00:00+00:00", + viral_vector=viral_vector, + ) + + indicator = Indicator( + name="indicator", + description="Green indicator", + label="GCamp6f", + viral_vector_injection=viral_vector_injection, + ) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig] + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + + # 5. Define illumination pattern random_access_scan = RandomAccessScan( name='random_access', description='Targeted imaging of specific neurons', @@ -504,7 +691,7 @@ Workflow for volumetric imaging with targeted scanning: scanning_pattern='spiral' ) - # 5. Set up volumetric space with illumination pattern + # 6. Set up volumetric space with illumination pattern volumetric_imaging_space = VolumetricImagingSpace( name='cortex_volume', description='Visual cortex volume', @@ -517,16 +704,16 @@ Workflow for volumetric imaging with targeted scanning: illumination_pattern=random_access_scan ) - # 6. Create microscopy channel + # 7. Create microscopy channel microscopy_channel = MicroscopyChannel( name='gcamp_channel', description='GCaMP6f channel', excitation_wavelength_in_nm=920.0, emission_wavelength_in_nm=520.0, - indicator=indicator # from ndx-ophys-devices + indicator=indicator # Link to indicator from MicroscopyExperimentMetadata ) - # 5. Create volumetric series + # 8. Create volumetric series volume_series = VolumetricMicroscopySeries( name='volume_data', microscopy_rig=microscopy_rig, diff --git a/pyproject.toml b/pyproject.toml index fc65850..15ab4d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ keywords = [ dependencies = [ "pynwb>=2.8.0", "hdmf>=3.14.1", - "ndx-ophys-devices==0.3.1" + "ndx-ophys-devices<=0.5.0" ] [project.urls] diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index 57facb8..fc12295 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -4,7 +4,7 @@ groups: doc: A microscope model used to acquire imaging data. - neurodata_type_def: Microscope - neurodata_type_inc: DeviceInstance + neurodata_type_inc: Device doc: Instance of a microscope used to acquire imaging data. attributes: - name: technique @@ -44,9 +44,9 @@ groups: target_type: OpticalFilter doc: Link to OpticalFilter object which contains metadata about the emission filter. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). quantity: "?" - - name: optical_lens - target_type: OpticalLens - doc: Link to OpticalLens object which contains metadata about the optical lens used in the microscopy rig. + - name: objective_lens + target_type: ObjectiveLens + doc: Link to ObjectiveLens object which contains metadata about the objective lens used in the microscopy rig. quantity: "?" - neurodata_type_def: MicroscopyChannel @@ -66,10 +66,29 @@ groups: - name: emission_wavelength_in_nm dtype: float64 doc: Wavelength of the emission light in nanometers. + links: + - name: indicator + target_type: Indicator + doc: Link to Indicator object which contains metadata about the indicator used in this light path. + quantity: 1 + + - neurodata_type_def: MicroscopyExperimentMetadata + neurodata_type_inc: LabMetaData + doc: Metadata about the microscopy experiment. + name: microscopy_experiment_metadata groups: + - neurodata_type_inc: MicroscopyRig + doc: Group containing of one or more MicroscopyRig objects. + quantity: "*" + - neurodata_type_inc: ViralVector + doc: Group containing of one or more ViralVector objects. + quantity: "*" + - neurodata_type_inc: ViralVectorInjection + doc: Group containing one or more ViralVectorInjection objects. + quantity: "*" - neurodata_type_inc: Indicator - doc: Indicator object which contains metadata about the indicator used in this light path. - quantity: 1 + doc: Group containing one or more Indicator objects. + quantity: "*" - neurodata_type_def: IlluminationPattern neurodata_type_inc: NWBContainer @@ -230,10 +249,11 @@ groups: doc: Abstract class to contain imaging data acquired over time from an optical channel in a microscope while a light source illuminates the imaging space. + links: + - name: microscopy_rig + doc: Link to a MicroscopyRig object containing metadata about the microscopy rig used to acquire this imaging data. + target_type: MicroscopyRig groups: - - neurodata_type_inc: MicroscopyRig - doc: MicroscopyRig object containing metadata about the microscopy rig used to acquire this imaging data. - quantity: 1 - neurodata_type_inc: MicroscopyChannel doc: MicroscopyChannel object containing metadata about the channel used to acquire this imaging data. quantity: 1 @@ -312,17 +332,10 @@ groups: - neurodata_type_def: SegmentationContainer neurodata_type_inc: NWBDataInterface default_name: SegmentationContainer - doc: - Stores pixels in an image that represent different regions of interest (ROIs) - or masks. All segmentation for a given imaging space is stored together, with - storage for multiple imaging space (masks) supported. Each ROI is stored in its - own subgroup, with the ROI group containing a 2D mask or a list of pixels - that make up this mask. Segments can also be used for masking neuropil. If segmentation - is allowed to change with time, a new imaging plane (or module) is required and - ROI names should remain consistent between them. + doc: A container for many Segmentation objects. groups: - neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific imaging space. + doc: Results from image segmentation. quantity: "+" - neurodata_type_def: SummaryImage @@ -351,7 +364,7 @@ groups: - neurodata_type_def: Segmentation neurodata_type_inc: DynamicTable - doc: Abstract class to contain the results from image segmentation of a specific imaging space. + doc: Abstract class to contain the spatial components resulting from image segmentation of a specific imaging space. attributes: - name: description dtype: text @@ -363,7 +376,7 @@ groups: - neurodata_type_def: PlanarSegmentation neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific planar imaging space. + doc: ROI spatial components resulting from image segmentation of a specific planar imaging space. datasets: - name: image_mask neurodata_type_inc: VectorData @@ -405,7 +418,7 @@ groups: - neurodata_type_def: VolumetricSegmentation neurodata_type_inc: Segmentation - doc: Results from image segmentation of a specific volumetric imaging space. + doc: ROI spatial components resulting from image segmentation of a specific volumetric imaging space. datasets: - name: volume_mask neurodata_type_inc: VectorData @@ -452,7 +465,10 @@ groups: - neurodata_type_def: MicroscopyResponseSeries neurodata_type_inc: TimeSeries - doc: ROI responses extracted from optical imaging. + doc: + ROI responses extracted from imaging data, linked in the microscopy_series field. + This object contains the temporal components from multiple ROIs, + that can result from different processing steps, e.g., raw, deconvolved, or denoised fluorescence traces. datasets: - name: data dtype: numeric @@ -466,8 +482,8 @@ groups: - name: rois neurodata_type_inc: DynamicTableRegion doc: - DynamicTableRegion referencing segmentation containing more information about the ROIs - stored in this series. + DynamicTableRegion referencing Segmentation table containing information about the ROIs + spatial components. links: - name: microscopy_series doc: Link to a MicroscopySeries object containing the imaging data this response series is derived from. diff --git a/spec/ndx-microscopy.namespace.yaml b/spec/ndx-microscopy.namespace.yaml index 426eb6e..04f299b 100644 --- a/spec/ndx-microscopy.namespace.yaml +++ b/spec/ndx-microscopy.namespace.yaml @@ -11,6 +11,7 @@ namespaces: - namespace: core neurodata_types: - Device + - DeviceModel - LabMetaData - NWBContainer - TimeSeries @@ -21,13 +22,11 @@ namespaces: - DynamicTableRegion - namespace: ndx-ophys-devices neurodata_types: - - DeviceModel - - DeviceInstance - ExcitationSource - Indicator - OpticalFilter - Photodetector - DichroicMirror - - OpticalLens + - ObjectiveLens - source: ndx-microscopy.extensions.yaml version: 0.4.0 diff --git a/src/pynwb/ndx_microscopy/__init__.py b/src/pynwb/ndx_microscopy/__init__.py index 70a85f4..2be3337 100644 --- a/src/pynwb/ndx_microscopy/__init__.py +++ b/src/pynwb/ndx_microscopy/__init__.py @@ -11,14 +11,14 @@ # NOTE: ndx-ophys-devices needs to be imported first because loading the ndx-microscopy namespace depends on # having the ndx-ophys-devices namespace loaded into the global type map. from ndx_ophys_devices import ( - DeviceModel, - DeviceInstance, ExcitationSource, Indicator, + ViralVector, + ViralVectorInjection, OpticalFilter, Photodetector, DichroicMirror, - OpticalLens, + ObjectiveLens, ) extension_name = "ndx-microscopy" @@ -45,6 +45,7 @@ Microscope = get_class("Microscope", extension_name) MicroscopyRig = get_class("MicroscopyRig", extension_name) MicroscopyChannel = get_class("MicroscopyChannel", extension_name) +MicroscopyExperimentMetadata = get_class("MicroscopyExperimentMetadata", extension_name) IlluminationPattern = get_class("IlluminationPattern", extension_name) LineScan = get_class("LineScan", extension_name) PlaneAcquisition = get_class("PlaneAcquisition", extension_name) @@ -64,12 +65,12 @@ MicroscopyResponseSeriesContainer = get_class("MicroscopyResponseSeriesContainer", extension_name) __all__ = [ - "DeviceModel", - "DeviceInstance", - "OpticalLens", + "ObjectiveLens", "OpticalFilter", "ExcitationSource", "Indicator", + "ViralVector", + "ViralVectorInjection", "Photodetector", "DichroicMirror", "MicroscopeModel", @@ -79,6 +80,7 @@ "PlaneAcquisition", "RandomAccessScan", "MicroscopyRig", + "MicroscopyExperimentMetadata", "ImagingSpace", "PlanarImagingSpace", "VolumetricImagingSpace", diff --git a/src/pynwb/ndx_microscopy/testing/_mock.py b/src/pynwb/ndx_microscopy/testing/_mock.py index 13bb20b..538b09f 100644 --- a/src/pynwb/ndx_microscopy/testing/_mock.py +++ b/src/pynwb/ndx_microscopy/testing/_mock.py @@ -1,20 +1,19 @@ import warnings from typing import List, Optional, Tuple +import ndx_ophys_devices import numpy as np -import pynwb.base -from ndx_ophys_devices import ExcitationSource, OpticalFilter, Photodetector, DichroicMirror, Indicator +from ndx_ophys_devices import ExcitationSource, OpticalFilter, Photodetector, DichroicMirror from ndx_ophys_devices.testing import ( mock_ExcitationSource, mock_OpticalFilter, mock_Photodetector, mock_DichroicMirror, - mock_Indicator, ) from pynwb.testing.mock.utils import name_generator - +from pynwb.core import DynamicTableRegion import ndx_microscopy @@ -58,14 +57,14 @@ def mock_MicroscopyChannel( description: str = "A mock instance of a MicroscopyChannel type to be used for rapid testing.", excitation_wavelength_in_nm: Optional[float] = 488.0, emission_wavelength_in_nm: Optional[float] = 520.0, - indicator: Indicator = None, + indicator: ndx_ophys_devices.Indicator, ) -> ndx_microscopy.MicroscopyChannel: microscopy_channel = ndx_microscopy.MicroscopyChannel( name=name or name_generator("MicroscopyChannel"), description=description, excitation_wavelength_in_nm=excitation_wavelength_in_nm, emission_wavelength_in_nm=emission_wavelength_in_nm, - indicator=indicator or mock_Indicator(), + indicator=indicator, ) return microscopy_channel @@ -479,7 +478,7 @@ def mock_VolumetricMicroscopySeries( def mock_MicroscopyResponseSeries( *, - rois: pynwb.core.DynamicTableRegion, + rois: DynamicTableRegion, name: Optional[str] = None, description: str = "A mock instance of a MicroscopyResponseSeries type to be used for rapid testing.", data: Optional[np.ndarray] = None, diff --git a/src/pynwb/tests/test_constructors.py b/src/pynwb/tests/test_constructors.py index 2df4f08..9db9ebc 100644 --- a/src/pynwb/tests/test_constructors.py +++ b/src/pynwb/tests/test_constructors.py @@ -26,6 +26,7 @@ ) from ndx_microscopy import ( + MicroscopyExperimentMetadata, Segmentation, PlanarSegmentation, VolumetricSegmentation, @@ -49,7 +50,9 @@ def test_constructor_microscope_model(): def test_constructor_microscopy_channel(): - microscopy_channel = mock_MicroscopyChannel() + from ndx_ophys_devices.testing import mock_Indicator + + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) assert microscopy_channel.description == "A mock instance of a MicroscopyChannel type to be used for rapid testing." @@ -58,6 +61,25 @@ def test_constructor_microscopy_rig(): assert microscopy_rig.description == "A mock instance of a MicroscopyRig type to be used for rapid testing." +def test_constructor_microscopy_experiment_metadata(): + from ndx_ophys_devices.testing import mock_Indicator, mock_ViralVector, mock_ViralVectorInjection + + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + microscopy_rig = mock_MicroscopyRig() + + _ = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig], + ) + + def test_constructor_illumination_pattern(): """Test constructor for base IlluminationPattern class.""" illumination_pattern = mock_IlluminationPattern() @@ -162,8 +184,10 @@ def test_constructor_segmentation_container(): def test_constructor_planar_microscopy_series(): + from ndx_ophys_devices.testing import mock_Indicator + + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) microscopy_rig = mock_MicroscopyRig() - microscopy_channel = mock_MicroscopyChannel() planar_imaging_space = mock_PlanarImagingSpace() planar_microscopy_series = mock_PlanarMicroscopySeries( @@ -178,9 +202,10 @@ def test_constructor_planar_microscopy_series(): def test_constructor_multi_plane_microscopy_container(): + from ndx_ophys_devices.testing import mock_Indicator + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) microscopy_rig = mock_MicroscopyRig() - microscopy_channel = mock_MicroscopyChannel() planar_imaging_space = mock_PlanarImagingSpace() planar_microscopy_series = mock_PlanarMicroscopySeries( @@ -196,9 +221,10 @@ def test_constructor_multi_plane_microscopy_container(): def test_constructor_multi_channel_microscopy_container(): + from ndx_ophys_devices.testing import mock_Indicator + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) microscopy_rig = mock_MicroscopyRig() - microscopy_channel = mock_MicroscopyChannel() planar_imaging_space = mock_PlanarImagingSpace() planar_microscopy_series = mock_PlanarMicroscopySeries( microscopy_rig=microscopy_rig, @@ -213,8 +239,10 @@ def test_constructor_multi_channel_microscopy_container(): def test_constructor_volumetric_microscopy_series(): + from ndx_ophys_devices.testing import mock_Indicator + + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) microscopy_rig = mock_MicroscopyRig() - microscopy_channel = mock_MicroscopyChannel() volumetric_imaging_space = mock_VolumetricImagingSpace() volumetric_microscopy_series = mock_VolumetricMicroscopySeries( @@ -246,9 +274,10 @@ def test_constructor_microscopy_response_series(): def test_constructor_microscopy_response_series_with_microscopy_series(): + from ndx_ophys_devices.testing import mock_Indicator + microscopy_channel = mock_MicroscopyChannel(indicator=mock_Indicator(name="Indicator1")) microscopy_rig = mock_MicroscopyRig() - microscopy_channel = mock_MicroscopyChannel() number_of_rois = 10 planar_imaging_space = mock_PlanarImagingSpace() microscopy_series = mock_PlanarMicroscopySeries( diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index befd88f..2a122fe 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -17,6 +17,9 @@ mock_PhotodetectorModel, mock_OpticalFilterModel, mock_DichroicMirrorModel, + mock_ViralVector, + mock_ViralVectorInjection, + mock_Indicator, ) from ndx_microscopy.testing import ( @@ -35,7 +38,7 @@ mock_MicroscopyResponseSeries, ) -from ndx_microscopy import MicroscopyResponseSeriesContainer +from ndx_microscopy import MicroscopyExperimentMetadata, MicroscopyResponseSeriesContainer class TestPlanarMicroscopySeriesSimpleRoundtrip(pynwb_TestCase): @@ -51,32 +54,32 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) nwbfile.add_device(devices=microscope) excitation_source_model = mock_ExcitationSourceModel(name="ExcitationSourceModel") - nwbfile.add_device(devices=excitation_source_model) + nwbfile.add_device_model(device_models=excitation_source_model) excitation_source = mock_ExcitationSource(model=excitation_source_model) nwbfile.add_device(devices=excitation_source) excitation_filter_model = mock_OpticalFilterModel(name="OpticalFilterModel") - nwbfile.add_device(devices=excitation_filter_model) + nwbfile.add_device_model(device_models=excitation_filter_model) excitation_filter = mock_OpticalFilter(model=excitation_filter_model) nwbfile.add_device(devices=excitation_filter) dichroic_mirror_model = mock_DichroicMirrorModel(name="DichroicMirrorModel") - nwbfile.add_device(devices=dichroic_mirror_model) + nwbfile.add_device_model(device_models=dichroic_mirror_model) dichroic_mirror = mock_DichroicMirror(model=dichroic_mirror_model) nwbfile.add_device(devices=dichroic_mirror) photodetector_model = mock_PhotodetectorModel(name="PhotodetectorModel") - nwbfile.add_device(devices=photodetector_model) + nwbfile.add_device_model(device_models=photodetector_model) photodetector = mock_Photodetector(model=photodetector_model) nwbfile.add_device(devices=photodetector) emission_filter_model = mock_OpticalFilterModel(name="EmissionFilterModel") - nwbfile.add_device(devices=emission_filter_model) + nwbfile.add_device_model(device_models=emission_filter_model) emission_filter = mock_OpticalFilter(model=emission_filter_model) nwbfile.add_device(devices=emission_filter) @@ -89,13 +92,27 @@ def test_roundtrip(self): photodetector=photodetector, dichroic_mirror=dichroic_mirror, ) + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig], + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) planar_imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") planar_microscopy_series = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries", microscopy_rig=microscopy_rig, - microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel"), + microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel", indicator=indicator), planar_imaging_space=planar_imaging_space, ) nwbfile.add_acquisition(planar_microscopy_series) @@ -126,32 +143,32 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) # nwbfile.add_device(devices=microscope) Skipping this line to simulate an untracked device excitation_source_model = mock_ExcitationSourceModel(name="ExcitationSourceModel") - nwbfile.add_device(devices=excitation_source_model) + nwbfile.add_device_model(device_models=excitation_source_model) excitation_source = mock_ExcitationSource(model=excitation_source_model) nwbfile.add_device(devices=excitation_source) excitation_filter_model = mock_OpticalFilterModel(name="OpticalFilterModel") - nwbfile.add_device(devices=excitation_filter_model) + nwbfile.add_device_model(device_models=excitation_filter_model) excitation_filter = mock_OpticalFilter(model=excitation_filter_model) nwbfile.add_device(devices=excitation_filter) dichroic_mirror_model = mock_DichroicMirrorModel(name="DichroicMirrorModel") - nwbfile.add_device(devices=dichroic_mirror_model) + nwbfile.add_device_model(device_models=dichroic_mirror_model) dichroic_mirror = mock_DichroicMirror(model=dichroic_mirror_model) nwbfile.add_device(devices=dichroic_mirror) photodetector_model = mock_PhotodetectorModel(name="PhotodetectorModel") - nwbfile.add_device(devices=photodetector_model) + nwbfile.add_device_model(device_models=photodetector_model) photodetector = mock_Photodetector(model=photodetector_model) nwbfile.add_device(devices=photodetector) emission_filter_model = mock_OpticalFilterModel(name="EmissionFilterModel") - nwbfile.add_device(devices=emission_filter_model) + nwbfile.add_device_model(device_models=emission_filter_model) emission_filter = mock_OpticalFilter(model=emission_filter_model) nwbfile.add_device(devices=emission_filter) @@ -165,13 +182,28 @@ def test_roundtrip(self): dichroic_mirror=dichroic_mirror, ) + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig], + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + # Create imaging space planar_imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") planar_microscopy_series = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries", microscopy_rig=microscopy_rig, - microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel"), + microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel", indicator=indicator), planar_imaging_space=planar_imaging_space, ) nwbfile.add_acquisition(planar_microscopy_series) @@ -194,32 +226,32 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) nwbfile.add_device(devices=microscope) excitation_source_model = mock_ExcitationSourceModel(name="ExcitationSourceModel") - nwbfile.add_device(devices=excitation_source_model) + nwbfile.add_device_model(device_models=excitation_source_model) excitation_source = mock_ExcitationSource(model=excitation_source_model) nwbfile.add_device(devices=excitation_source) excitation_filter_model = mock_OpticalFilterModel(name="OpticalFilterModel") - nwbfile.add_device(devices=excitation_filter_model) + nwbfile.add_device_model(device_models=excitation_filter_model) excitation_filter = mock_OpticalFilter(model=excitation_filter_model) nwbfile.add_device(devices=excitation_filter) dichroic_mirror_model = mock_DichroicMirrorModel(name="DichroicMirrorModel") - nwbfile.add_device(devices=dichroic_mirror_model) + nwbfile.add_device_model(device_models=dichroic_mirror_model) dichroic_mirror = mock_DichroicMirror(model=dichroic_mirror_model) nwbfile.add_device(devices=dichroic_mirror) photodetector_model = mock_PhotodetectorModel(name="PhotodetectorModel") - nwbfile.add_device(devices=photodetector_model) + nwbfile.add_device_model(device_models=photodetector_model) photodetector = mock_Photodetector(model=photodetector_model) nwbfile.add_device(devices=photodetector) emission_filter_model = mock_OpticalFilterModel(name="EmissionFilterModel") - nwbfile.add_device(devices=emission_filter_model) + nwbfile.add_device_model(device_models=emission_filter_model) emission_filter = mock_OpticalFilter(model=emission_filter_model) nwbfile.add_device(devices=emission_filter) @@ -233,12 +265,27 @@ def test_roundtrip(self): dichroic_mirror=dichroic_mirror, ) + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig], + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + volumetric_imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace") volumetric_microscopy_series = mock_VolumetricMicroscopySeries( name="VolumetricMicroscopySeries", microscopy_rig=microscopy_rig, - microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel"), + microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel", indicator=indicator), volumetric_imaging_space=volumetric_imaging_space, ) nwbfile.add_acquisition(volumetric_microscopy_series) @@ -269,32 +316,32 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) nwbfile.add_device(devices=microscope) excitation_source_model = mock_ExcitationSourceModel(name="ExcitationSourceModel") - nwbfile.add_device(devices=excitation_source_model) + nwbfile.add_device_model(device_models=excitation_source_model) excitation_source = mock_ExcitationSource(model=excitation_source_model) nwbfile.add_device(devices=excitation_source) excitation_filter_model = mock_OpticalFilterModel(name="OpticalFilterModel") - nwbfile.add_device(devices=excitation_filter_model) + nwbfile.add_device_model(device_models=excitation_filter_model) excitation_filter = mock_OpticalFilter(model=excitation_filter_model) nwbfile.add_device(devices=excitation_filter) dichroic_mirror_model = mock_DichroicMirrorModel(name="DichroicMirrorModel") - nwbfile.add_device(devices=dichroic_mirror_model) + nwbfile.add_device_model(device_models=dichroic_mirror_model) dichroic_mirror = mock_DichroicMirror(model=dichroic_mirror_model) nwbfile.add_device(devices=dichroic_mirror) photodetector_model = mock_PhotodetectorModel(name="PhotodetectorModel") - nwbfile.add_device(devices=photodetector_model) + nwbfile.add_device_model(device_models=photodetector_model) photodetector = mock_Photodetector(model=photodetector_model) nwbfile.add_device(devices=photodetector) emission_filter_model = mock_OpticalFilterModel(name="EmissionFilterModel") - nwbfile.add_device(devices=emission_filter_model) + nwbfile.add_device_model(device_models=emission_filter_model) emission_filter = mock_OpticalFilter(model=emission_filter_model) nwbfile.add_device(devices=emission_filter) @@ -307,6 +354,20 @@ def test_roundtrip(self): photodetector=photodetector, dichroic_mirror=dichroic_mirror, ) + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator], + microscopy_rigs=[microscopy_rig], + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) planar_imaging_space_1 = mock_PlanarImagingSpace( name="PlanarImagingSpace_1", origin_coordinates=[0.0, 0.0, 0.0] @@ -315,7 +376,7 @@ def test_roundtrip(self): name="PlanarImagingSpace_2", origin_coordinates=[0.0, 0.0, 1.0] ) - microscopy_channel = mock_MicroscopyChannel(name="MicroscopyChannel") + microscopy_channel = mock_MicroscopyChannel(name="MicroscopyChannel", indicator=indicator) planar_microscopy_series_1 = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries_1", @@ -364,32 +425,32 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) nwbfile.add_device(devices=microscope) excitation_source_model = mock_ExcitationSourceModel(name="ExcitationSourceModel") - nwbfile.add_device(devices=excitation_source_model) + nwbfile.add_device_model(device_models=excitation_source_model) excitation_source = mock_ExcitationSource(model=excitation_source_model) nwbfile.add_device(devices=excitation_source) excitation_filter_model = mock_OpticalFilterModel(name="OpticalFilterModel") - nwbfile.add_device(devices=excitation_filter_model) + nwbfile.add_device_model(device_models=excitation_filter_model) excitation_filter = mock_OpticalFilter(model=excitation_filter_model) nwbfile.add_device(devices=excitation_filter) dichroic_mirror_model = mock_DichroicMirrorModel(name="DichroicMirrorModel") - nwbfile.add_device(devices=dichroic_mirror_model) + nwbfile.add_device_model(device_models=dichroic_mirror_model) dichroic_mirror = mock_DichroicMirror(model=dichroic_mirror_model) nwbfile.add_device(devices=dichroic_mirror) photodetector_model = mock_PhotodetectorModel(name="PhotodetectorModel") - nwbfile.add_device(devices=photodetector_model) + nwbfile.add_device_model(device_models=photodetector_model) photodetector = mock_Photodetector(model=photodetector_model) nwbfile.add_device(devices=photodetector) emission_filter_model = mock_OpticalFilterModel(name="EmissionFilterModel") - nwbfile.add_device(devices=emission_filter_model) + nwbfile.add_device_model(device_models=emission_filter_model) emission_filter = mock_OpticalFilter(model=emission_filter_model) nwbfile.add_device(devices=emission_filter) @@ -405,17 +466,33 @@ def test_roundtrip(self): planar_imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace_1", origin_coordinates=[0.0, 0.0, 0.0]) + viral_vector = mock_ViralVector(name="ViralVector1") + viral_vector_injection = mock_ViralVectorInjection( + name="ViralVectorInjection1", + viral_vector=viral_vector, + ) + indicator_1 = mock_Indicator(name="Indicator1", viral_vector_injection=viral_vector_injection) + indicator_2 = mock_Indicator(name="Indicator2", viral_vector_injection=viral_vector_injection) + + microscopy_experiment_metadata = MicroscopyExperimentMetadata( + viral_vectors=[viral_vector], + viral_vector_injections=[viral_vector_injection], + indicators=[indicator_1, indicator_2], + microscopy_rigs=[microscopy_rig], + ) + nwbfile.add_lab_meta_data(microscopy_experiment_metadata) + planar_microscopy_series_1 = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries_1", microscopy_rig=microscopy_rig, - microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel1"), + microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel1", indicator=indicator_1), planar_imaging_space=planar_imaging_space, ) planar_microscopy_series_2 = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries_2", microscopy_rig=microscopy_rig, - microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel2"), + microscopy_channel=mock_MicroscopyChannel(name="MicroscopyChannel2", indicator=indicator_2), planar_imaging_space=planar_imaging_space, ) @@ -475,7 +552,7 @@ def test_roundtrip(self): nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope_model = mock_MicroscopeModel(name="MicroscopeModel") - nwbfile.add_device(devices=microscope_model) + nwbfile.add_device_model(device_models=microscope_model) microscope = mock_Microscope(name="Microscope", model=microscope_model) nwbfile.add_device(devices=microscope)