From 889b33a05d28f59dbf5b907681ffa7bdfea9fcf5 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 26 Aug 2024 16:57:28 +0200 Subject: [PATCH 01/10] add object from ndx-ophys-devices --- spec/ndx-microscopy.extensions.yaml | 126 +++++++++++---------------- spec/ndx-microscopy.namespace.yaml | 1 + src/pynwb/ndx_microscopy/__init__.py | 17 ++-- 3 files changed, 63 insertions(+), 81 deletions(-) diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index 118a9da..9312b3b 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -6,67 +6,45 @@ groups: attributes: - name: model dtype: text - doc: Model identifier of the light source device. + doc: Model identifier of the microscope. required: false - - neurodata_type_def: MicroscopyLightSource - neurodata_type_inc: Device - doc: Light source used to illuminate an imaging space. + - neurodata_type_def: ExcitationLightPath + neurodata_type_inc: NWBContainer + doc: Excitation light path that illuminates an imaging space. attributes: - - name: model - dtype: text - doc: Model identifier of the light source device. - required: false - - name: filter_description - dtype: text - doc: Filter used to obtain the excitation wavelength of light, e.g. 'Short pass at 1040 nm'. - required: false - name: excitation_wavelength_in_nm dtype: numeric doc: Excitation wavelength of light, in nanometers. + links: + - name: excitation_source + target_type: ExcitationSource + doc: Link to ExcitationSource object which contains metadata about the excitation source device. If it is a pulsed excitation source link a PulsedExcitationSource object. required: false - - name: peak_power_in_W - dtype: numeric - doc: Incident power of stimulation device (in Watts). - required: false - - name: peak_pulse_energy_in_J - dtype: numeric - doc: If device is pulsed light source, pulse energy (in Joules). - required: false - - name: intensity_in_W_per_m2 - dtype: numeric - doc: Intensity of the excitation in W/m^2, if known. + - name: excitation_filter + target_type: OpticalFilter + doc: Link to OpticalFilter object which contains metadata about the optical filter in this excitation light path. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). required: false - - name: exposure_time_in_s + + - neurodata_type_def: EmissionLightPath + neurodata_type_inc: NWBContainer + doc: Emission light path from an imaging space. + attributes: + - name: emission_wavelength_in_nm dtype: numeric - doc: Exposure time of the sample (in sec). + doc: Emission wavelength of light, in nanometers. + links: + - name: photodetector + target_type: Photodetector + doc: Link to Photodetector object which contains metadata about the photodetector device. required: false - - name: pulse_rate_in_Hz - dtype: numeric - doc: If device is pulsed light source, pulse rate (in Hz) used for stimulation. + - name: emission_filter + target_type: OpticalFilter + doc: Link to OpticalFilter object which contains metadata about the optical filter in this emission light path. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). required: false - - # Microscopy is added on to this only to differentiate from the OpticalChannel in the core namespace - # It would be removed when this structure is merged to core - - neurodata_type_def: MicroscopyOpticalChannel - neurodata_type_inc: LabMetaData # Would prefer basic NWBContainer - doc: An optical channel used to filter light emission from an imaging space. - datasets: - - name: description - doc: Description or other notes about the channel. - dtype: text - attributes: - - name: indicator - doc: Identifier for the indicator pertaining to this optical channel. - dtype: text - - name: filter_description - doc: Metadata information about the filter used by this optical channel. - dtype: text - required: false - - name: emission_wavelength_in_nm - doc: Emission wavelength for this optical channel, in nanometers. - dtype: numeric - required: false + - name: indicator + target_type: Indicator + doc: Link to Indicator object which contains metadata about the indicator used in this light path. - neurodata_type_def: ImagingSpace neurodata_type_inc: LabMetaData # Would prefer basic NWBContainer @@ -97,10 +75,6 @@ groups: Use standard atlas names for anatomical regions when possible. Specify 'whole brain' if the entire brain is strictly contained within the space. required: false - links: - - name: microscope - target_type: Microscope - doc: Link to Microscope object which contains metadata about the device which imaged this space. - neurodata_type_def: PlanarImagingSpace neurodata_type_inc: ImagingSpace @@ -273,13 +247,13 @@ groups: - name: microscope doc: Link to a Microscope object containing metadata about the device used to acquire this imaging data. target_type: Microscope - - name: light_source - doc: Link to a MicroscopyLightSource object containing metadata about the device used to illuminate the imaging space. - target_type: MicroscopyLightSource - - name: optical_channel - doc: Link to a MicroscopyOpticalChannel object containing metadata about the indicator and filters used to collect + - name: excitation_light_path + doc: Link to a ExcitationLightPath object containing metadata about the device used to illuminate the imaging space. + target_type: ExcitationLightPath + - name: emission_light_path + doc: Link to a EmissionLightPath object containing metadata about the indicator and filters used to collect this data. - target_type: MicroscopyOpticalChannel + target_type: EmissionLightPath - neurodata_type_def: PlanarMicroscopySeries neurodata_type_inc: MicroscopySeries @@ -392,24 +366,24 @@ groups: - null - null - null - - name: light_sources - doc: An ordered list of references to MicroscopyLightSource objects containing metadata about the excitation methods. + - name: excitation_light_paths + doc: An ordered list of references to ExcitationLightPath objects containing metadata about the excitation methods. neurodata_type_inc: VectorData dtype: reftype: object - target_type: MicroscopyLightSource + target_type: ExcitationLightPath dims: - - light_sources + - excitation_light_paths shape: - null - - name: optical_channels - doc: An ordered list of references to MicroscopyOpticalChannel objects containing metadata about the indicator and filters used to collect this data. This maps to the last dimension of `data`, i.e., the i-th MicroscopyOpticalChannel contains metadata about the indicator and filters used to collect the volume at `data[:,:,:,i]`. + - name: emission_light_paths + doc: An ordered list of references to EmissionLightPath objects containing metadata about the indicator and filters used to collect this data. This maps to the last dimension of `data`, i.e., the i-th MicroscopyOpticalChannel contains metadata about the indicator and filters used to collect the volume at `data[:,:,:,i]`. neurodata_type_inc: VectorData dtype: reftype: object - target_type: MicroscopyOpticalChannel + target_type: EmissionLightPath dims: - - optical_channels + - emission_light_paths shape: - null links: @@ -478,24 +452,24 @@ groups: - depths shape: - null - - name: light_sources - doc: An ordered list of references to MicroscopyLightSource objects containing metadata about the excitation methods. + - name: excitation_light_paths + doc: An ordered list of references to ExcitationLightPath objects containing metadata about the excitation methods. neurodata_type_inc: VectorData dtype: reftype: object - target_type: MicroscopyLightSource + target_type: ExcitationLightPath dims: - - light_sources + - excitation_light_paths shape: - null - - name: optical_channels - doc: An ordered list of references to MicroscopyOpticalChannel objects containing metadata about the indicator and filters used to collect this data. This maps to the last dimension of `data`, i.e., the i-th MicroscopyOpticalChannel contains metadata about the indicator and filters used to collect the volume at `data[:,:,:,i]`. + - name: emission_light_paths + doc: An ordered list of references to EmissionLightPath objects containing metadata about the indicator and filters used to collect this data. This maps to the last dimension of `data`, i.e., the i-th MicroscopyOpticalChannel contains metadata about the indicator and filters used to collect the volume at `data[:,:,:,i]`. neurodata_type_inc: VectorData dtype: reftype: object - target_type: MicroscopyOpticalChannel + target_type: EmissionLightPath dims: - - optical_channels + - emission_light_paths shape: - null links: diff --git a/spec/ndx-microscopy.namespace.yaml b/spec/ndx-microscopy.namespace.yaml index 8399608..75bf1c6 100644 --- a/spec/ndx-microscopy.namespace.yaml +++ b/spec/ndx-microscopy.namespace.yaml @@ -9,5 +9,6 @@ namespaces: - alessandra.trapani@catalystneuro.com schema: - namespace: core + - namespace: ndx-ophys-devices - source: ndx-microscopy.extensions.yaml version: 0.1.0 diff --git a/src/pynwb/ndx_microscopy/__init__.py b/src/pynwb/ndx_microscopy/__init__.py index 8dfeac9..1351b27 100644 --- a/src/pynwb/ndx_microscopy/__init__.py +++ b/src/pynwb/ndx_microscopy/__init__.py @@ -19,10 +19,15 @@ __spec_path = __location_of_this_file.parent.parent.parent / "spec" / f"{extension_name}.namespace.yaml" load_namespaces(str(__spec_path)) - +from ndx_ophys_devices import ( + OpticalFilter, + ExcitationSource, + Indicator, + Photodetector, +) Microscope = get_class("Microscope", extension_name) -MicroscopyLightSource = get_class("MicroscopyLightSource", extension_name) -MicroscopyOpticalChannel = get_class("MicroscopyOpticalChannel", extension_name) +ExcitationLightPath = get_class("ExcitationLightPath", extension_name) +EmissionLightPath = get_class("EmissionLightPath", extension_name) ImagingSpace = get_class("ImagingSpace", extension_name) PlanarImagingSpace = get_class("PlanarImagingSpace", extension_name) VolumetricImagingSpace = get_class("VolumetricImagingSpace", extension_name) @@ -36,9 +41,11 @@ VariableDepthMultiChannelMicroscopyVolume = get_class("VariableDepthMultiChannelMicroscopyVolume", extension_name) __all__ = [ + "OpticalFilter", + "ExcitationSource", + "Indicator", + "Photodetector", "Microscope", - "MicroscopyLightSource", - "MicroscopyOpticalChannel", "ImagingSpace", "PlanarImagingSpace", "VolumetricImagingSpace", From ceebfa6ff2b2423e9b7f171ee41ad221f25e1910 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 26 Aug 2024 16:57:41 +0200 Subject: [PATCH 02/10] add tests --- src/pynwb/ndx_microscopy/testing/__init__.py | 8 +- src/pynwb/ndx_microscopy/testing/_mock.py | 114 +++++++++---------- src/pynwb/tests/test_constructors.py | 92 +++++++-------- src/pynwb/tests/test_roundtrip.py | 86 +++++++------- 4 files changed, 144 insertions(+), 156 deletions(-) diff --git a/src/pynwb/ndx_microscopy/testing/__init__.py b/src/pynwb/ndx_microscopy/testing/__init__.py index e27103f..9b7679e 100644 --- a/src/pynwb/ndx_microscopy/testing/__init__.py +++ b/src/pynwb/ndx_microscopy/testing/__init__.py @@ -1,7 +1,7 @@ from ._mock import ( mock_Microscope, - mock_MicroscopyLightSource, - mock_MicroscopyOpticalChannel, + mock_ExcitationLightPath, + mock_EmissionLightPath, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, @@ -15,8 +15,8 @@ __all__ = [ "mock_Microscope", - "mock_MicroscopyLightSource", - "mock_MicroscopyOpticalChannel", + "mock_ExcitationLightPath", + "mock_EmissionLightPath", "mock_PlanarImagingSpace", "mock_VolumetricImagingSpace", "mock_MicroscopySegmentations", diff --git a/src/pynwb/ndx_microscopy/testing/_mock.py b/src/pynwb/ndx_microscopy/testing/_mock.py index 862889d..47b4a64 100644 --- a/src/pynwb/ndx_microscopy/testing/_mock.py +++ b/src/pynwb/ndx_microscopy/testing/_mock.py @@ -7,6 +7,18 @@ import ndx_microscopy +from ndx_ophys_devices import ( + OpticalFilter, + ExcitationSource, + Indicator, + Photodetector, +) +from ndx_ophys_devices.testing import ( + mock_Indicator, + mock_Photodetector, + mock_OpticalFilter, + mock_ExcitationSource, +) def mock_Microscope( *, @@ -24,57 +36,46 @@ def mock_Microscope( return microscope -def mock_MicroscopyLightSource( +def mock_ExcitationLightPath( *, name: Optional[str] = None, - description: str = "This is a mock instance of a MicroscopyLightSource type to be used for rapid testing.", - manufacturer: str = "A fake manufacturer of the mock light source.", - model: str = "A fake model of the mock light source.", - filter_description: str = "A description about the fake filter used by the mock light source.", + description: str = "This is a mock instance of a ExcitationLightPath type to be used for rapid testing.", excitation_wavelength_in_nm: float = 500.0, - peak_power_in_W: float = 0.7, - peak_pulse_energy_in_J: float = 0.7, - intensity_in_W_per_m2: float = 0.005, - exposure_time_in_s: float = 2.51e-13, - pulse_rate_in_Hz: float = 2.0e6, -) -> ndx_microscopy.MicroscopyLightSource: - light_source = ndx_microscopy.MicroscopyLightSource( - name=name or name_generator("MicroscopyLightSource"), + excitation_source: ExcitationSource = None, + excitation_filter: OpticalFilter = None, +) -> ndx_microscopy.ExcitationLightPath: + excitation_light_path = ndx_microscopy.ExcitationLightPath( + name=name or name_generator("ExcitationLightPath"), description=description, - manufacturer=manufacturer, - model=model, - filter_description=filter_description, excitation_wavelength_in_nm=excitation_wavelength_in_nm, - peak_power_in_W=peak_power_in_W, - peak_pulse_energy_in_J=peak_pulse_energy_in_J, - intensity_in_W_per_m2=intensity_in_W_per_m2, - exposure_time_in_s=exposure_time_in_s, - pulse_rate_in_Hz=pulse_rate_in_Hz, + excitation_source=excitation_source or mock_ExcitationSource(), + excitation_filter=excitation_filter or mock_OpticalFilter(), ) - return light_source + return excitation_light_path -def mock_MicroscopyOpticalChannel( +def mock_EmissionLightPath( *, name: Optional[str] = None, - description: str = "This is a mock instance of a MicroscopyOpticalChannel type to be used for rapid testing.", - indicator: str = "The indicator targeted by the mock optical channel.", - filter_description: str = "A description about the fake filter used by the mock optical channel.", + description: str = "This is a mock instance of a EmissionLightPath type to be used for rapid testing.", + indicator: Indicator = None, + photodetector: Photodetector = None, + emission_filter: OpticalFilter = None, emission_wavelength_in_nm: float = 450.0, -) -> ndx_microscopy.MicroscopyOpticalChannel: - optical_channel = ndx_microscopy.MicroscopyOpticalChannel( - name=name or name_generator("MicroscopyOpticalChannel"), +) -> ndx_microscopy.EmissionLightPath: + emission_light_path = ndx_microscopy.EmissionLightPath( + name=name or name_generator("EmissionLightPath"), description=description, - indicator=indicator, - filter_description=filter_description, + indicator=indicator or mock_Indicator(), + photodetector=photodetector or mock_Photodetector(), + emission_filter=emission_filter or mock_OpticalFilter(), emission_wavelength_in_nm=emission_wavelength_in_nm, ) - return optical_channel + return emission_light_path def mock_PlanarImagingSpace( *, - microscope: ndx_microscopy.Microscope, name: Optional[str] = None, description: str = "This is a mock instance of a PlanarImagingSpace type to be used for rapid testing.", origin_coordinates: Tuple[float, float, float] = (-1.2, -0.6, -2), @@ -85,7 +86,6 @@ def mock_PlanarImagingSpace( planar_imaging_space = ndx_microscopy.PlanarImagingSpace( name=name or name_generator("PlanarImagingSpace"), description=description, - microscope=microscope, origin_coordinates=origin_coordinates, grid_spacing_in_um=grid_spacing_in_um, location=location, @@ -96,7 +96,6 @@ def mock_PlanarImagingSpace( def mock_VolumetricImagingSpace( *, - microscope: ndx_microscopy.Microscope, name: Optional[str] = None, description: str = "This is a mock instance of a VolumetricImagingSpace type to be used for rapid testing.", origin_coordinates: Tuple[float, float, float] = (-1.2, -0.6, -2), @@ -107,7 +106,6 @@ def mock_VolumetricImagingSpace( volumetric_imaging_space = ndx_microscopy.VolumetricImagingSpace( name=name or name_generator("VolumetricImagingSpace"), description=description, - microscope=microscope, origin_coordinates=origin_coordinates, grid_spacing_in_um=grid_spacing_in_um, location=location, @@ -122,9 +120,7 @@ def mock_MicroscopySegmentations( microscopy_plane_segmentations: Optional[Iterable[ndx_microscopy.MicroscopyPlaneSegmentation]] = None, ) -> ndx_microscopy.MicroscopySegmentations: name = name or name_generator("MicroscopySegmentations") - - microscope = mock_Microscope() - imaging_space = mock_PlanarImagingSpace(microscope=microscope) + imaging_space = mock_PlanarImagingSpace() microscopy_plane_segmentations = microscopy_plane_segmentations or [ mock_MicroscopyPlaneSegmentation(imaging_space=imaging_space) ] @@ -162,9 +158,9 @@ def mock_MicroscopyPlaneSegmentation( def mock_PlanarMicroscopySeries( *, microscope: ndx_microscopy.Microscope, - light_source: ndx_microscopy.MicroscopyLightSource, + excitation_light_path: ndx_microscopy.ExcitationLightPath, imaging_space: ndx_microscopy.PlanarImagingSpace, - optical_channel: ndx_microscopy.MicroscopyOpticalChannel, + emission_light_path: ndx_microscopy.EmissionLightPath, name: Optional[str] = None, description: str = "This is a mock instance of a PlanarMicroscopySeries type to be used for rapid testing.", data: Optional[np.ndarray] = None, @@ -200,9 +196,9 @@ def mock_PlanarMicroscopySeries( name=series_name, description=description, microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, data=series_data, unit=unit, conversion=conversion, @@ -217,9 +213,9 @@ def mock_PlanarMicroscopySeries( def mock_VariableDepthMicroscopySeries( *, microscope: ndx_microscopy.Microscope, - light_source: ndx_microscopy.MicroscopyLightSource, + excitation_light_path: ndx_microscopy.ExcitationLightPath, imaging_space: ndx_microscopy.PlanarImagingSpace, - optical_channel: ndx_microscopy.MicroscopyOpticalChannel, + emission_light_path: ndx_microscopy.EmissionLightPath, name: Optional[str] = None, description: str = "This is a mock instance of a PlanarMicroscopySeries type to be used for rapid testing.", data: Optional[np.ndarray] = None, @@ -262,9 +258,9 @@ def mock_VariableDepthMicroscopySeries( name=series_name, description=description, microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, data=series_data, depth_per_frame_in_um=depth_per_frame_in_um, unit=unit, @@ -280,9 +276,9 @@ def mock_VariableDepthMicroscopySeries( def mock_VolumetricMicroscopySeries( *, microscope: ndx_microscopy.Microscope, - light_source: ndx_microscopy.MicroscopyLightSource, + excitation_light_path: ndx_microscopy.ExcitationLightPath, imaging_space: ndx_microscopy.VolumetricImagingSpace, - optical_channel: ndx_microscopy.MicroscopyOpticalChannel, + emission_light_path: ndx_microscopy.EmissionLightPath, name: Optional[str] = None, description: str = "This is a mock instance of a VolumetricMicroscopySeries type to be used for rapid testing.", data: Optional[np.ndarray] = None, @@ -318,9 +314,9 @@ def mock_VolumetricMicroscopySeries( name=series_name, description=description, microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, data=series_data, unit=unit, conversion=conversion, @@ -336,8 +332,8 @@ def mock_MultiChannelMicroscopyVolume( *, microscope: ndx_microscopy.Microscope, imaging_space: ndx_microscopy.VolumetricImagingSpace, - light_sources: pynwb.base.VectorData, - optical_channels: pynwb.base.VectorData, + excitation_light_paths: pynwb.base.VectorData, + emission_light_paths: pynwb.base.VectorData, name: Optional[str] = None, description: str = "This is a mock instance of a MultiChannelMicroscopyVolume type to be used for rapid testing.", data: Optional[np.ndarray] = None, @@ -353,8 +349,8 @@ def mock_MultiChannelMicroscopyVolume( description=description, microscope=microscope, imaging_space=imaging_space, - light_sources=light_sources, - optical_channels=optical_channels, + excitation_light_paths=excitation_light_paths, + emission_light_paths=emission_light_paths, data=imaging_data, unit=unit, conversion=conversion, @@ -367,8 +363,8 @@ def mock_VariableDepthMultiChannelMicroscopyVolume( *, microscope: ndx_microscopy.Microscope, imaging_space: ndx_microscopy.VolumetricImagingSpace, - light_sources: pynwb.base.VectorData, - optical_channels: pynwb.base.VectorData, + excitation_light_paths: pynwb.base.VectorData, + emission_light_paths: pynwb.base.VectorData, name: Optional[str] = None, description: str = "This is a mock instance of a MultiChannelMicroscopyVolume type to be used for rapid testing.", data: Optional[np.ndarray] = None, @@ -393,8 +389,8 @@ def mock_VariableDepthMultiChannelMicroscopyVolume( description=description, microscope=microscope, imaging_space=imaging_space, - light_sources=light_sources, - optical_channels=optical_channels, + excitation_light_paths=excitation_light_paths, + emission_light_paths=emission_light_paths, data=imaging_data, depth_per_frame_in_um=volume_depth_per_frame_in_um, unit=unit, diff --git a/src/pynwb/tests/test_constructors.py b/src/pynwb/tests/test_constructors.py index f5c29aa..3935c0d 100644 --- a/src/pynwb/tests/test_constructors.py +++ b/src/pynwb/tests/test_constructors.py @@ -5,8 +5,8 @@ import pynwb from ndx_microscopy.testing import ( mock_Microscope, - mock_MicroscopyLightSource, - mock_MicroscopyOpticalChannel, + mock_ExcitationLightPath, + mock_EmissionLightPath, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, @@ -23,24 +23,20 @@ def test_constructor_microscope(): mock_Microscope() -def test_constructor_light_source(): - mock_MicroscopyLightSource() +def test_constructor_excitation_light_path(): + mock_ExcitationLightPath() -def test_constructor_microscopy_optical_channel(): - mock_MicroscopyOpticalChannel() +def test_constructor_microscopy_emission_light_path(): + mock_EmissionLightPath() def test_constructor_planar_image_space(): - microscope = mock_Microscope() - - mock_PlanarImagingSpace(microscope=microscope) + mock_PlanarImagingSpace() def test_constructor_volumetric_image_space(): - microscope = mock_Microscope() - - mock_VolumetricImagingSpace(microscope=microscope) + mock_VolumetricImagingSpace() def test_constructor_microscopy_segmentations(): @@ -48,16 +44,12 @@ def test_constructor_microscopy_segmentations(): def test_constructor_microscopy_plane_segmentation(): - microscope = mock_Microscope() - imaging_space = mock_PlanarImagingSpace(microscope=microscope) - + imaging_space = mock_PlanarImagingSpace() mock_MicroscopyPlaneSegmentation(imaging_space=imaging_space) def test_constructor_microscopy_image_segmentation_with_plane_segmentation(): - microscope = mock_Microscope() - imaging_space = mock_PlanarImagingSpace(microscope=microscope) - + imaging_space = mock_PlanarImagingSpace() plane_segmentation_1 = mock_MicroscopyPlaneSegmentation( imaging_space=imaging_space, name="MicroscopyPlaneSegmentation1" ) @@ -71,84 +63,84 @@ def test_constructor_microscopy_image_segmentation_with_plane_segmentation(): def test_constructor_planar_microscopy_series(): microscope = mock_Microscope() - light_source = mock_MicroscopyLightSource() - imaging_space = mock_PlanarImagingSpace(microscope=microscope) - optical_channel = mock_MicroscopyOpticalChannel() + excitation_light_path = mock_ExcitationLightPath() + imaging_space = mock_PlanarImagingSpace() + emission_light_path = mock_EmissionLightPath() mock_PlanarMicroscopySeries( - microscope=microscope, light_source=light_source, imaging_space=imaging_space, optical_channel=optical_channel + microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path ) def test_constructor_variable_depth_microscopy_series(): microscope = mock_Microscope() - light_source = mock_MicroscopyLightSource() - imaging_space = mock_PlanarImagingSpace(microscope=microscope) - optical_channel = mock_MicroscopyOpticalChannel() + excitation_light_path = mock_ExcitationLightPath() + imaging_space = mock_PlanarImagingSpace() + emission_light_path = mock_EmissionLightPath() mock_VariableDepthMicroscopySeries( - microscope=microscope, light_source=light_source, imaging_space=imaging_space, optical_channel=optical_channel + microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path ) def test_constructor_volumetric_microscopy_series(): microscope = mock_Microscope() - light_source = mock_MicroscopyLightSource() - imaging_space = mock_VolumetricImagingSpace(microscope=microscope) - optical_channel = mock_MicroscopyOpticalChannel() + excitation_light_path = mock_ExcitationLightPath() + imaging_space = mock_VolumetricImagingSpace() + emission_light_path = mock_EmissionLightPath() mock_VolumetricMicroscopySeries( - microscope=microscope, light_source=light_source, imaging_space=imaging_space, optical_channel=optical_channel + microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path ) def test_constructor_multi_channel_microscopy_volume(): microscope = mock_Microscope() - imaging_space = mock_VolumetricImagingSpace(microscope=microscope) - light_sources = [mock_MicroscopyLightSource()] - optical_channels = [mock_MicroscopyOpticalChannel()] + imaging_space = mock_VolumetricImagingSpace() + excitation_light_paths = [mock_ExcitationLightPath()] + emission_light_paths = [mock_EmissionLightPath()] - light_sources_used_by_volume = pynwb.base.VectorData( - name="light_sources", description="Light sources used by this MultiChannelVolume.", data=light_sources + excitation_light_paths_used_by_volume = pynwb.base.VectorData( + name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths ) - optical_channels_used_by_volume = pynwb.base.VectorData( - name="optical_channels", + emission_light_paths_used_by_volume = pynwb.base.VectorData( + name="emission_light_paths", description=( "Optical channels ordered to correspond to the third axis (e.g., [0, 0, :, 0]) " "of the data for this MultiChannelVolume." ), - data=optical_channels, + data=emission_light_paths, ) mock_MultiChannelMicroscopyVolume( microscope=microscope, imaging_space=imaging_space, - light_sources=light_sources_used_by_volume, - optical_channels=optical_channels_used_by_volume, + excitation_light_paths=excitation_light_paths_used_by_volume, + emission_light_paths=emission_light_paths_used_by_volume, ) def test_constructor_variable_depth_multi_channel_microscopy_volume(): microscope = mock_Microscope() - imaging_space = mock_VolumetricImagingSpace(microscope=microscope) - light_sources = [mock_MicroscopyLightSource()] - optical_channels = [mock_MicroscopyOpticalChannel()] + imaging_space = mock_VolumetricImagingSpace() + excitation_light_paths = [mock_ExcitationLightPath()] + emission_light_paths = [mock_EmissionLightPath()] - light_sources_used_by_volume = pynwb.base.VectorData( - name="light_sources", description="Light sources used by this MultiChannelVolume.", data=light_sources + excitation_light_paths_used_by_volume = pynwb.base.VectorData( + name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths ) - optical_channels_used_by_volume = pynwb.base.VectorData( - name="optical_channels", + emission_light_paths_used_by_volume = pynwb.base.VectorData( + name="emission_light_paths", description=( "Optical channels ordered to correspond to the third axis (e.g., [0, 0, :, 0]) " "of the data for this MultiChannelVolume." ), - data=optical_channels, + data=emission_light_paths, ) mock_VariableDepthMultiChannelMicroscopyVolume( microscope=microscope, imaging_space=imaging_space, - light_sources=light_sources_used_by_volume, - optical_channels=optical_channels_used_by_volume, + excitation_light_paths=excitation_light_paths_used_by_volume, + emission_light_paths=emission_light_paths_used_by_volume, ) diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index 4d9f830..fe5d512 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -7,8 +7,8 @@ import pynwb from ndx_microscopy.testing import ( mock_Microscope, - mock_MicroscopyLightSource, - mock_MicroscopyOpticalChannel, + mock_ExcitationLightPath, + mock_EmissionLightPath, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, @@ -35,21 +35,21 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - light_source = mock_MicroscopyLightSource(name="MicroscopyLightSource") - nwbfile.add_device(devices=light_source) + excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() - optical_channel = mock_MicroscopyOpticalChannel(name="MicroscopyOpticalChannel") - nwbfile.add_lab_meta_data(lab_meta_data=optical_channel) + emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) planar_microscopy_series = mock_PlanarMicroscopySeries( name="PlanarMicroscopySeries", microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, ) nwbfile.add_acquisition(nwbdata=planar_microscopy_series) @@ -60,10 +60,10 @@ def test_roundtrip(self): read_nwbfile = io.read() self.assertContainerEqual(microscope, read_nwbfile.devices["Microscope"]) - self.assertContainerEqual(light_source, read_nwbfile.devices["MicroscopyLightSource"]) + self.assertContainerEqual(excitation_light_path, read_nwbfile.lab_meta_data["ExcitationLightPath"]) self.assertContainerEqual(imaging_space, read_nwbfile.lab_meta_data["PlanarImagingSpace"]) - self.assertContainerEqual(optical_channel, read_nwbfile.lab_meta_data["MicroscopyOpticalChannel"]) + self.assertContainerEqual(emission_light_path, read_nwbfile.lab_meta_data["EmissionLightPath"]) self.assertContainerEqual(planar_microscopy_series, read_nwbfile.acquisition["PlanarMicroscopySeries"]) @@ -83,21 +83,21 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - light_source = mock_MicroscopyLightSource(name="MicroscopyLightSource") - nwbfile.add_device(devices=light_source) + excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace", microscope=microscope) nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() - optical_channel = mock_MicroscopyOpticalChannel(name="MicroscopyOpticalChannel") - nwbfile.add_lab_meta_data(lab_meta_data=optical_channel) + emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) volumetric_microscopy_series = mock_VolumetricMicroscopySeries( name="VolumetricMicroscopySeries", microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, ) nwbfile.add_acquisition(nwbdata=volumetric_microscopy_series) @@ -108,10 +108,10 @@ def test_roundtrip(self): read_nwbfile = io.read() self.assertContainerEqual(microscope, read_nwbfile.devices["Microscope"]) - self.assertContainerEqual(light_source, read_nwbfile.devices["MicroscopyLightSource"]) + self.assertContainerEqual(excitation_light_path, read_nwbfile.lab_meta_data["ExcitationLightPath"]) self.assertContainerEqual(imaging_space, read_nwbfile.lab_meta_data["VolumetricImagingSpace"]) - self.assertContainerEqual(optical_channel, read_nwbfile.lab_meta_data["MicroscopyOpticalChannel"]) + self.assertContainerEqual(emission_light_path, read_nwbfile.lab_meta_data["EmissionLightPath"]) self.assertContainerEqual( volumetric_microscopy_series, read_nwbfile.acquisition["VolumetricMicroscopySeries"] @@ -133,21 +133,21 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - light_source = mock_MicroscopyLightSource(name="MicroscopyLightSource") - nwbfile.add_device(devices=light_source) + excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() - optical_channel = mock_MicroscopyOpticalChannel(name="MicroscopyOpticalChannel") - nwbfile.add_lab_meta_data(lab_meta_data=optical_channel) + emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) variable_depth_microscopy_series = mock_VariableDepthMicroscopySeries( name="VariableDepthMicroscopySeries", microscope=microscope, - light_source=light_source, + excitation_light_path=excitation_light_path, imaging_space=imaging_space, - optical_channel=optical_channel, + emission_light_path=emission_light_path, ) nwbfile.add_acquisition(nwbdata=variable_depth_microscopy_series) @@ -158,10 +158,10 @@ def test_roundtrip(self): read_nwbfile = io.read() self.assertContainerEqual(microscope, read_nwbfile.devices["Microscope"]) - self.assertContainerEqual(light_source, read_nwbfile.devices["MicroscopyLightSource"]) + self.assertContainerEqual(excitation_light_path, read_nwbfile.lab_meta_data["ExcitationLightPath"]) self.assertContainerEqual(imaging_space, read_nwbfile.lab_meta_data["PlanarImagingSpace"]) - self.assertContainerEqual(optical_channel, read_nwbfile.lab_meta_data["MicroscopyOpticalChannel"]) + self.assertContainerEqual(emission_light_path, read_nwbfile.lab_meta_data["EmissionLightPath"]) self.assertContainerEqual( variable_depth_microscopy_series, read_nwbfile.acquisition["VariableDepthMicroscopySeries"] @@ -186,35 +186,35 @@ def test_roundtrip(self): imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace", microscope=microscope) nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() - light_sources = list() - light_source_0 = mock_MicroscopyLightSource(name="LightSource") - nwbfile.add_device(devices=light_source_0) - light_sources.append(light_source_0) + excitation_light_paths = list() + excitation_light_path_0 = mock_ExcitationLightPath(name="ExcitationLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path_0) + excitation_light_paths.append(excitation_light_path_0) - optical_channels = list() - optical_channel_0 = mock_MicroscopyOpticalChannel(name="MicroscopyOpticalChannel") - nwbfile.add_lab_meta_data(lab_meta_data=optical_channel_0) - optical_channels.append(optical_channel_0) + emission_light_paths = list() + emission_light_path_0 = mock_EmissionLightPath(name="EmissionLightPath") + nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path_0) + emission_light_paths.append(emission_light_path_0) # TODO: It might be more convenient in Python to have a custom constructor that takes in a list of # light sources and optical channels and does the VectorData wrapping internally - light_sources_used_by_volume = pynwb.base.VectorData( - name="light_sources", description="Light sources used by this MultiChannelVolume.", data=light_sources + excitation_light_paths_used_by_volume = pynwb.base.VectorData( + name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths ) - optical_channels_used_by_volume = pynwb.base.VectorData( - name="optical_channels", + emission_light_paths_used_by_volume = pynwb.base.VectorData( + name="emission_light_paths", description=( "Optical channels ordered to correspond to the third axis (e.g., [0, 0, :, 0]) " "of the data for this MultiChannelVolume." ), - data=optical_channels, + data=emission_light_paths, ) multi_channel_microscopy_volume = mock_MultiChannelMicroscopyVolume( name="MultiChannelMicroscopyVolume", microscope=microscope, imaging_space=imaging_space, - light_sources=light_sources_used_by_volume, - optical_channels=optical_channels_used_by_volume, + excitation_light_paths=excitation_light_paths_used_by_volume, + emission_light_paths=emission_light_paths_used_by_volume, ) nwbfile.add_acquisition(nwbdata=multi_channel_microscopy_volume) @@ -225,10 +225,10 @@ def test_roundtrip(self): read_nwbfile = io.read() self.assertContainerEqual(microscope, read_nwbfile.devices["Microscope"]) - self.assertContainerEqual(light_source_0, read_nwbfile.devices["LightSource"]) + self.assertContainerEqual(excitation_light_path_0, read_nwbfile.lab_meta_data["ExcitationLightPath"]) self.assertContainerEqual(imaging_space, read_nwbfile.lab_meta_data["VolumetricImagingSpace"]) - self.assertContainerEqual(optical_channel_0, read_nwbfile.lab_meta_data["MicroscopyOpticalChannel"]) + self.assertContainerEqual(emission_light_path_0, read_nwbfile.lab_meta_data["EmissionLightPath"]) self.assertContainerEqual( multi_channel_microscopy_volume, read_nwbfile.acquisition["MultiChannelMicroscopyVolume"] From caa1802a8398f697848b26d9b3094ae995a64ce1 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 26 Aug 2024 17:15:04 +0200 Subject: [PATCH 03/10] update requirements --- requirements-min.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-min.txt b/requirements-min.txt index cd0c3e6..a2358dc 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1 +1,2 @@ pynwb +ndx-ophys-devices git+https://github.com/catalystneuro/ndx-ophys-devices.git@create_specs \ No newline at end of file From 8356ca5414d033029663ac891edab97f0a3fdfe7 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 26 Aug 2024 17:39:43 +0200 Subject: [PATCH 04/10] update README.md --- README.md | 64 ++++++++++++++++++++--------- spec/ndx-microscopy.extensions.yaml | 2 +- 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 148426c..c1c5e80 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,8 @@ classDiagram links -------------------------------------- microscope : Microscope - light_source : MicroscopyLightSource - optical_channel : MicroscopyOpticalChannel + excitation_light_path : ExcitationLightPath + emission_light_path : EmissionLightPath } class PlanarMicroscopySeries { @@ -86,6 +86,32 @@ classDiagram imaging_space : VolumetricImageSpace } + class MultiChannelMicroscopyVolume { + <> + + -------------------------------------- + attributes + -------------------------------------- + description : text, optional + unit : text, optional + conversion : numeric, optional + offset : numeric, optional + + -------------------------------------- + datasets + -------------------------------------- + data : numeric, frame x height x width x depth x emission_light_paths + --> unit : text + excitation_light_paths : ExcitationLightPath, excitation_light_paths + emission_light_paths : EmissionLightPath, emission_light_paths + + -------------------------------------- + links + -------------------------------------- + imaging_space : VolumetricImageSpace + microscope : Microscope + } + class ImagingSpace{ <> @@ -132,36 +158,34 @@ classDiagram reference_frame : text, optional } - class MicroscopyOpticalChannel{ + class ExcitationLightPath{ <> -------------------------------------- - datasets + links -------------------------------------- - description : text + excitation_source : ExcitationSource, optional + excitation_filter : OpticalFilter, optional -------------------------------------- attributes -------------------------------------- - indicator : text - filter_description : text, optional - emission_wavelength_in_nm : numeric, optional + excitation_wavelength_in_nm : numeric } - class MicroscopyLightSource{ - <> + class EmissionLightPath{ + <> + + -------------------------------------- + links + -------------------------------------- + photodetector : Photodetector, optional + emission_filter : OpticalFilter, optional -------------------------------------- attributes -------------------------------------- - model : text, optional - filter_description : text, optional - excitation_wavelength_in_nm : numeric, optional - peak_power_in_W : numeric, optional - peak_pulse_energy_in_J : numeric, optional - intensity_in_W_per_m2 : numeric, optional - exposure_time_in_s : numeric, optional - pulse_rate_in_Hz : numeric, optional + emission_wavelength_in_nm : numeric } class Microscope{ @@ -182,8 +206,8 @@ classDiagram PlanarImagingSpace *-- ImagingSpace : extends VolumetricImagingSpace *-- ImagingSpace : extends MicroscopySeries ..> Microscope : links - MicroscopySeries ..> MicroscopyLightSource : links - MicroscopySeries ..> MicroscopyOpticalChannel : links + MicroscopySeries ..> ExcitationLightPath : links + MicroscopySeries ..> EmissionLightPath : links ``` --- diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index 9312b3b..4a2e75a 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -360,7 +360,7 @@ groups: - height - width - depths - - optical_channels + - emission_light_paths shape: - null - null From 6f235426e3adc8c06404b93d74e63c13730b2e75 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:45:10 +0000 Subject: [PATCH 05/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- README.md | 6 ++--- requirements-min.txt | 2 +- src/pynwb/ndx_microscopy/__init__.py | 8 ++---- src/pynwb/ndx_microscopy/testing/__init__.py | 4 +-- src/pynwb/ndx_microscopy/testing/_mock.py | 19 +++++--------- src/pynwb/tests/test_constructors.py | 27 +++++++++++++++----- src/pynwb/tests/test_roundtrip.py | 8 +++--- 7 files changed, 40 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c1c5e80..bc78799 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ classDiagram class MultiChannelMicroscopyVolume { <> - + -------------------------------------- attributes -------------------------------------- @@ -104,14 +104,14 @@ classDiagram --> unit : text excitation_light_paths : ExcitationLightPath, excitation_light_paths emission_light_paths : EmissionLightPath, emission_light_paths - + -------------------------------------- links -------------------------------------- imaging_space : VolumetricImageSpace microscope : Microscope } - + class ImagingSpace{ <> diff --git a/requirements-min.txt b/requirements-min.txt index a2358dc..1299ede 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,2 +1,2 @@ pynwb -ndx-ophys-devices git+https://github.com/catalystneuro/ndx-ophys-devices.git@create_specs \ No newline at end of file +ndx-ophys-devices git+https://github.com/catalystneuro/ndx-ophys-devices.git@create_specs diff --git a/src/pynwb/ndx_microscopy/__init__.py b/src/pynwb/ndx_microscopy/__init__.py index 1351b27..467f201 100644 --- a/src/pynwb/ndx_microscopy/__init__.py +++ b/src/pynwb/ndx_microscopy/__init__.py @@ -19,12 +19,8 @@ __spec_path = __location_of_this_file.parent.parent.parent / "spec" / f"{extension_name}.namespace.yaml" load_namespaces(str(__spec_path)) -from ndx_ophys_devices import ( - OpticalFilter, - ExcitationSource, - Indicator, - Photodetector, -) +from ndx_ophys_devices import ExcitationSource, Indicator, OpticalFilter, Photodetector + Microscope = get_class("Microscope", extension_name) ExcitationLightPath = get_class("ExcitationLightPath", extension_name) EmissionLightPath = get_class("EmissionLightPath", extension_name) diff --git a/src/pynwb/ndx_microscopy/testing/__init__.py b/src/pynwb/ndx_microscopy/testing/__init__.py index 9b7679e..97ddbba 100644 --- a/src/pynwb/ndx_microscopy/testing/__init__.py +++ b/src/pynwb/ndx_microscopy/testing/__init__.py @@ -1,7 +1,7 @@ from ._mock import ( - mock_Microscope, - mock_ExcitationLightPath, mock_EmissionLightPath, + mock_ExcitationLightPath, + mock_Microscope, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, diff --git a/src/pynwb/ndx_microscopy/testing/_mock.py b/src/pynwb/ndx_microscopy/testing/_mock.py index 47b4a64..8b9f024 100644 --- a/src/pynwb/ndx_microscopy/testing/_mock.py +++ b/src/pynwb/ndx_microscopy/testing/_mock.py @@ -3,22 +3,17 @@ import numpy as np import pynwb.base -from pynwb.testing.mock.utils import name_generator - -import ndx_microscopy - -from ndx_ophys_devices import ( - OpticalFilter, - ExcitationSource, - Indicator, - Photodetector, -) +from ndx_ophys_devices import ExcitationSource, Indicator, OpticalFilter, Photodetector from ndx_ophys_devices.testing import ( + mock_ExcitationSource, mock_Indicator, - mock_Photodetector, mock_OpticalFilter, - mock_ExcitationSource, + mock_Photodetector, ) +from pynwb.testing.mock.utils import name_generator + +import ndx_microscopy + def mock_Microscope( *, diff --git a/src/pynwb/tests/test_constructors.py b/src/pynwb/tests/test_constructors.py index 3935c0d..0f5573e 100644 --- a/src/pynwb/tests/test_constructors.py +++ b/src/pynwb/tests/test_constructors.py @@ -4,9 +4,9 @@ import pynwb from ndx_microscopy.testing import ( - mock_Microscope, - mock_ExcitationLightPath, mock_EmissionLightPath, + mock_ExcitationLightPath, + mock_Microscope, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, @@ -68,7 +68,10 @@ def test_constructor_planar_microscopy_series(): emission_light_path = mock_EmissionLightPath() mock_PlanarMicroscopySeries( - microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path + microscope=microscope, + excitation_light_path=excitation_light_path, + imaging_space=imaging_space, + emission_light_path=emission_light_path, ) @@ -79,7 +82,10 @@ def test_constructor_variable_depth_microscopy_series(): emission_light_path = mock_EmissionLightPath() mock_VariableDepthMicroscopySeries( - microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path + microscope=microscope, + excitation_light_path=excitation_light_path, + imaging_space=imaging_space, + emission_light_path=emission_light_path, ) @@ -90,7 +96,10 @@ def test_constructor_volumetric_microscopy_series(): emission_light_path = mock_EmissionLightPath() mock_VolumetricMicroscopySeries( - microscope=microscope, excitation_light_path=excitation_light_path, imaging_space=imaging_space, emission_light_path=emission_light_path + microscope=microscope, + excitation_light_path=excitation_light_path, + imaging_space=imaging_space, + emission_light_path=emission_light_path, ) @@ -101,7 +110,9 @@ def test_constructor_multi_channel_microscopy_volume(): emission_light_paths = [mock_EmissionLightPath()] excitation_light_paths_used_by_volume = pynwb.base.VectorData( - name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths + name="excitation_light_paths", + description="Light sources used by this MultiChannelVolume.", + data=excitation_light_paths, ) emission_light_paths_used_by_volume = pynwb.base.VectorData( name="emission_light_paths", @@ -126,7 +137,9 @@ def test_constructor_variable_depth_multi_channel_microscopy_volume(): emission_light_paths = [mock_EmissionLightPath()] excitation_light_paths_used_by_volume = pynwb.base.VectorData( - name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths + name="excitation_light_paths", + description="Light sources used by this MultiChannelVolume.", + data=excitation_light_paths, ) emission_light_paths_used_by_volume = pynwb.base.VectorData( name="emission_light_paths", diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index fe5d512..99d2a16 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -6,9 +6,9 @@ import pynwb from ndx_microscopy.testing import ( - mock_Microscope, - mock_ExcitationLightPath, mock_EmissionLightPath, + mock_ExcitationLightPath, + mock_Microscope, mock_MicroscopyPlaneSegmentation, mock_MicroscopySegmentations, mock_MultiChannelMicroscopyVolume, @@ -199,7 +199,9 @@ def test_roundtrip(self): # TODO: It might be more convenient in Python to have a custom constructor that takes in a list of # light sources and optical channels and does the VectorData wrapping internally excitation_light_paths_used_by_volume = pynwb.base.VectorData( - name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", data=excitation_light_paths + name="excitation_light_paths", + description="Light sources used by this MultiChannelVolume.", + data=excitation_light_paths, ) emission_light_paths_used_by_volume = pynwb.base.VectorData( name="emission_light_paths", From f191e4761d985301c554272e3772e91ee385d656 Mon Sep 17 00:00:00 2001 From: Alessandra Trapani <55453048+alessandratrapani@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:31:02 +0100 Subject: [PATCH 06/10] Update with newer ndx-template version (#26) * update to new template * add ndx-ophys-devices as requirement * Import ndx-ophys-devices first * minor fixes --------- Co-authored-by: rly --- .github/workflows/check_external_links.yml | 32 ++++ .github/workflows/codespell.yml | 4 +- .github/workflows/ruff.yml | 14 ++ .github/workflows/run_all_tests.yml | 49 +++--- .github/workflows/run_coverage.yml | 34 ++-- .github/workflows/validate_schema.yml | 22 +++ .gitignore | 67 +++++++- CHANGELOG.md | 1 + NEXTSTEPS.md | 69 +++++--- docs/Makefile | 179 +++++++++++++++++++++ docs/README.md | 121 ++++++++++++++ docs/make.bat | 35 ++++ docs/source/_static/theme_overrides.css | 13 ++ docs/source/conf.py | 112 +++++++++++++ docs/source/conf_doc_autogen.py | 90 +++++++++++ docs/source/credits.rst | 21 +++ docs/source/description.rst | 5 + docs/source/format.rst | 12 ++ docs/source/index.rst | 30 ++++ docs/source/release_notes.rst | 5 + pyproject.toml | 135 +++++++++++++--- requirements-dev.txt | 16 +- requirements-min.txt | 9 +- setup.cfg | 17 -- setup.py | 69 -------- spec/ndx-microscopy.extensions.yaml | 10 +- src/pynwb/ndx_microscopy/__init__.py | 5 +- src/pynwb/tests/test_roundtrip.py | 14 +- 28 files changed, 992 insertions(+), 198 deletions(-) create mode 100644 .github/workflows/check_external_links.yml create mode 100644 .github/workflows/ruff.yml create mode 100644 .github/workflows/validate_schema.yml create mode 100644 CHANGELOG.md create mode 100644 docs/Makefile create mode 100644 docs/README.md create mode 100644 docs/make.bat create mode 100644 docs/source/_static/theme_overrides.css create mode 100644 docs/source/conf.py create mode 100644 docs/source/conf_doc_autogen.py create mode 100644 docs/source/credits.rst create mode 100644 docs/source/description.rst create mode 100644 docs/source/format.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/release_notes.rst delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/check_external_links.yml b/.github/workflows/check_external_links.yml new file mode 100644 index 0000000..9dd1a84 --- /dev/null +++ b/.github/workflows/check_external_links.yml @@ -0,0 +1,32 @@ +name: Check Sphinx external links +on: + push: + schedule: + - cron: '0 5 * * 0' # once every Sunday at midnight ET + workflow_dispatch: + +jobs: + check-external-links: + name: Check for broken Sphinx external links + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 # tags are required to determine the version + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install Sphinx dependencies and package + run: | + python -m pip install --upgrade pip + python -m pip install -r requirements-dev.txt + python -m pip install . + + - name: Check Sphinx external links + run: | + cd docs # run_doc_autogen assumes spec is found in ../spec/ + sphinx-build -b linkcheck ./source ./test_build diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 6575870..314b085 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -1,6 +1,6 @@ name: Codespell on: - pull_request: + push: workflow_dispatch: jobs: @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Codespell uses: codespell-project/actions-codespell@v2 diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 0000000..9b4f05d --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,14 @@ +name: Ruff +on: + push: + workflow_dispatch: + +jobs: + ruff: + name: Check for style errors and common problems + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Ruff + uses: chartboost/ruff-action@v1 diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index 8f16226..6292cad 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -1,6 +1,6 @@ name: Run all tests on: - pull_request: + push: schedule: - cron: '0 5 * * 0' # once every Sunday at midnight ET workflow_dispatch: @@ -12,6 +12,9 @@ jobs: defaults: run: shell: bash + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.name }} + cancel-in-progress: true strategy: fail-fast: false matrix: @@ -38,19 +41,13 @@ jobs: - { name: macos-python3.12 , requirements: pinned , python-ver: "3.12", os: macos-latest } - { name: macos-python3.12-upgraded , requirements: upgraded , python-ver: "3.12", os: macos-latest } steps: - - name: Cancel non-latest runs - uses: styfle/cancel-workflow-action@0.11.0 + - name: Checkout repo + uses: actions/checkout@v4 with: - all_but_latest: true - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - fetch-depth: 0 # fetch tags + fetch-depth: 0 # tags are required to determine the version - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-ver }} @@ -64,19 +61,20 @@ jobs: if: ${{ matrix.requirements == 'minimum' }} run: | python -m pip install -r requirements-min.txt -r requirements-dev.txt - python -m pip install -e . + python -m pip install . - name: Install run requirements (pinned) if: ${{ matrix.requirements == 'pinned' }} run: | python -m pip install -r requirements-dev.txt - python -m pip install -e . + python -m pip install . - name: Install run requirements (upgraded) if: ${{ matrix.requirements == 'upgraded' }} run: | python -m pip install -r requirements-dev.txt - python -m pip install -U -e . + # force upgrade of all dependencies to latest versions within allowed range + python -m pip install -U --upgrade-strategy eager . - name: Run tests run: | @@ -110,6 +108,9 @@ jobs: defaults: run: shell: bash -l {0} # needed for conda environment to work + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.name }} + cancel-in-progress: true strategy: fail-fast: false matrix: @@ -122,18 +123,13 @@ jobs: - { name: conda-linux-python3.12 , requirements: pinned , python-ver: "3.12", os: ubuntu-latest } - { name: conda-linux-python3.12-upgraded , requirements: upgraded , python-ver: "3.12", os: ubuntu-latest } steps: - - name: Cancel any previous incomplete runs - uses: styfle/cancel-workflow-action@0.11.0 - with: - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 + - name: Checkout repo + uses: actions/checkout@v4 with: - submodules: 'recursive' - fetch-depth: 0 # fetch tags + fetch-depth: 0 # tags are required to determine the version - name: Set up Conda - uses: conda-incubator/setup-miniconda@v2 + uses: conda-incubator/setup-miniconda@v3 with: auto-update-conda: true auto-activate-base: true @@ -151,19 +147,20 @@ jobs: if: ${{ matrix.requirements == 'minimum' }} run: | python -m pip install -r requirements-min.txt -r requirements-dev.txt - python -m pip install -e . + python -m pip install . - name: Install run requirements (pinned) if: ${{ matrix.requirements == 'pinned' }} run: | python -m pip install -r requirements-dev.txt - python -m pip install -e . + python -m pip install . - name: Install run requirements (upgraded) if: ${{ matrix.requirements == 'upgraded' }} run: | python -m pip install -r requirements-dev.txt - python -m pip install -U -e . + # force upgrade of all dependencies to latest versions within allowed range + python -m pip install -U --upgrade-strategy eager . - name: Run tests run: | diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 4883bf4..5aa6f9e 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -1,6 +1,6 @@ name: Run code coverage on: - pull_request: + push: workflow_dispatch: jobs: @@ -13,26 +13,23 @@ jobs: defaults: run: shell: bash + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.os }} + cancel-in-progress: true strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] env: # used by codecov-action OS: ${{ matrix.os }} - PYTHON: '3.11' + PYTHON: '3.12' steps: - - name: Cancel any previous incomplete runs - uses: styfle/cancel-workflow-action@0.11.0 + - name: Checkout repo + uses: actions/checkout@v4 with: - all_but_latest: true - access_token: ${{ github.token }} - - - uses: actions/checkout@v3 - with: - submodules: 'recursive' - fetch-depth: 0 # fetch tags + fetch-depth: 0 # tags are required to determine the version - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON }} @@ -43,17 +40,18 @@ jobs: - name: Install package run: | - python -m pip install -e . # must install in editable mode for coverage to find sources + python -m pip install . python -m pip list - name: Run tests and generate coverage report run: | - pytest --cov - python -m coverage xml # codecov uploader requires xml format - python -m coverage report -m + pytest --cov --cov-report=xml --cov-report=term # codecov uploader requires xml format - # TODO uncomment after setting up repo on codecov.io + # TODO uncomment after setting up repo on codecov.io and adding token # - name: Upload coverage to Codecov - # uses: codecov/codecov-action@v3 + # uses: codecov/codecov-action@v4 # with: # fail_ci_if_error: true + # file: ./coverage.xml + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/validate_schema.yml b/.github/workflows/validate_schema.yml new file mode 100644 index 0000000..d050de1 --- /dev/null +++ b/.github/workflows/validate_schema.yml @@ -0,0 +1,22 @@ +name: Validate schema + +on: [push, pull_request, workflow_dispatch] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: "3.12" + - name: Install HDMF + run: | + pip install hdmf + - name: Download latest nwb schema language specification + run: | + curl -L https://raw.githubusercontent.com/NeurodataWithoutBorders/nwb-schema/dev/nwb.schema.json -o nwb.schema.json + - name: Validate schema specification + run: | + validate_hdmf_spec spec -m nwb.schema.json \ No newline at end of file diff --git a/.gitignore b/.gitignore index a40ad0b..056fce3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,6 @@ # generated docs docs/source/_format_auto_docs -# copied spec files -src/pynwb/ndx_microscopy/spec/*.yaml - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -29,6 +26,7 @@ parts/ sdist/ var/ wheels/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg @@ -47,14 +45,18 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover +*.py,cover .hypothesis/ .pytest_cache/ +cover/ +.ruff_cache/ # Translations *.mo @@ -64,6 +66,7 @@ coverage.xml *.log local_settings.py db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -76,16 +79,49 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version +# IPython +profile_default/ +ipython_config.py -# celery beat schedule file +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff celerybeat-schedule +celerybeat.pid # SageMath parsed files *.sage.py @@ -111,9 +147,24 @@ venv.bak/ # mypy .mypy_cache/ +.dmypy.json +dmypy.json -# Mac finder -.DS_Store +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ # PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. .idea/ + +# Mac finder +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..db3ef69 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog for ndx-microscopy diff --git a/NEXTSTEPS.md b/NEXTSTEPS.md index 0faf651..e67a62a 100644 --- a/NEXTSTEPS.md +++ b/NEXTSTEPS.md @@ -4,12 +4,12 @@ ## Creating Your Extension -1. In a terminal, change directory into the new ndx-microscopy directory. +1. In a terminal, change directory into the new ndx-microscopy directory: `cd ndx-microscopy` -2. Add any packages required by your extension to `requirements.txt` and `setup.py`. +2. Add any packages required by your extension to the `dependencies` key in `pyproject.toml`. -3. Run `python -m pip install -r requirements.txt -r requirements-dev.txt` to install the `pynwb` package -and any other packages required to install, develop, and document your extension. +3. Run `python -m pip install -e .` to install your new extension Python package +and any other packages required to develop, document, and run your extension. 4. Modify `src/spec/create_extension_spec.py` to define your extension. @@ -19,16 +19,18 @@ and any other packages required to install, develop, and document your extension 6. Define API classes for your new extension data types. - - As a starting point, `src/pynwb/__init__.py` includes an example for how to use - the `pynwb.get_class` to get a basic Python class for your new extension data + - As a starting point, `src/pynwb/ndx_microscopy/__init__.py` includes an + example for how to use + the `pynwb.get_class` to generate a basic Python class for your new extension data type. This class contains a constructor and properties for the new data type. - Instead of using `pynwb.get_class`, you can define your own custom class for the new type, which will allow you to customize the class methods, customize the - object mapping, and create convenience functions. See - [https://pynwb.readthedocs.io/en/stable/tutorials/general/extensions.html](https://pynwb.readthedocs.io/en/stable/tutorials/general/extensions.html) + object mapping, and create convenience functions. See the + [Extending NWB tutorial](https://pynwb.readthedocs.io/en/stable/tutorials/general/extensions.html) for more details. -7. Define tests for your new extension data types in `src/pynwb/tests` or `src/matnwb/tests`. +7. Define tests for your new extension data types in +`src/pynwb/ndx_microscopy/tests` or `src/matnwb/tests`. A test for the example `TetrodeSeries` data type is provided as a reference and should be replaced or removed. @@ -40,10 +42,26 @@ replaced or removed. new functions) and **integration tests** (e.g., write the new data types to file, read the file, and confirm the read data types are equal to the written data types) is highly encouraged. - -8. You may need to modify `setup.py` and re-run `python setup.py install` if you + - By default, to aid with debugging, the project is configured NOT to run code coverage as + part of the tests. + Code coverage reporting is useful to help with creation of tests and report test coverage. + However, with this option enabled, breakpoints for debugging with pdb are being ignored. + To enable this option for code coverage reporting, uncomment out the following line in + your `pyproject.toml`: [line](https://github.com/nwb-extensions/ndx-template/blob/11ae225b3fd3934fa3c56e6e7b563081793b3b43/%7B%7B%20cookiecutter.namespace%20%7D%7D/pyproject.toml#L82-L83 +) + +7. (Optional) Define custom visualization widgets for your new extension data types in +`src/pynwb/ndx_microscopy/widgets` so that the visualizations can be displayed with +[nwbwidgets](https://github.com/NeurodataWithoutBorders/nwbwidgets). +You will also need to update the `vis_spec` dictionary in +`src/pynwb/ndx_microscopy/widgets/__init__.py` so that +nwbwidgets can find your custom visualizations. + +8. You may need to modify `pyproject.toml` and re-run `python -m pip install -e .` if you use any dependencies. +9. Update the `CHANGELOG.md` regularly to document changes to your extension. + ## Documenting and Publishing Your Extension to the Community @@ -65,16 +83,26 @@ your extension. 7. Add a license file. Permissive licenses should be used if possible. **A [BSD license](https://opensource.org/licenses/BSD-3-Clause) is recommended.** -8. Make a release for the extension on GitHub with the version number specified. e.g. if version is 0.1.0, then this page should exist: https://github.com/CodyCBakerPhD/ndx-microscopy/releases/tag/0.1.0 . For instructions on how to make a release on GitHub see [here](https://help.github.com/en/github/administering-a-repository/creating-releases). +8. Update the `CHANGELOG.md` to document changes to your extension. + +8. Push your repository to GitHub. A default set of GitHub Actions workflows is set up to +test your code on Linux, Windows, Mac OS, and Linux using conda; upload code coverage +stats to codecov.io; check for spelling errors; check for style errors; and check for broken +links in the documentation. For the code coverage workflow to work, you will need to +set up the repo on codecov.io and uncomment the "Upload coverage to Codecov" step +in `.github/workflows/run_coverage.yml`. + +8. Make a release for the extension on GitHub with the version number specified. e.g. if version is 0.1.0, then this page should exist: https://github.com/alessandratrapani/ndx-microscopy/releases/tag/0.1.0 . For instructions on how to make a release on GitHub see [here](https://help.github.com/en/github/administering-a-repository/creating-releases). 9. Publish your updated extension on [PyPI](https://pypi.org/). - - Follow these directions: https://packaging.python.org/tutorials/packaging-projects/ - - You may need to modify `setup.py` + - Follow these directions: https://packaging.python.org/en/latest/tutorials/packaging-projects/ + - You may need to modify `pyproject.toml` - If your extension version is 0.1.0, then this page should exist: https://pypi.org/project/ndx-microscopy/0.1.0 - Once your GitHub release and ``setup.py`` are ready, publishing on PyPI: + Once your GitHub release and `pyproject.toml` are ready, publishing on PyPI: ```bash - python setup.py sdist bdist_wheel + python -m pip install --upgrade build twine + python -m build twine upload dist/* ``` @@ -104,11 +132,12 @@ with information on where to find your NWB extension. ```yaml name: ndx-microscopy version: 0.1.0 - src: https://github.com/CodyCBakerPhD/ndx-microscopy + src: https://github.com/alessandratrapani/ndx-microscopy pip: https://pypi.org/project/ndx-microscopy/ - license: MIT - maintainers: - - CodyCBakerPhD + license: BSD-3 + maintainers: + - alessandratrapani + - codycbakerphd ``` 14. Edit `staged-extensions/ndx-microscopy/README.md` diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..54e6545 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,179 @@ + +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXAPIDOC = sphinx-apidoc +PAPER = +BUILDDIR = build +SRCDIR = ../src +RSTDIR = source +CONFDIR = $(PWD)/source + + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext fulldoc allclean + +help: + @echo "To update documentation sources from the format specification please use \`make apidoc'" + @echo "" + @echo "To build the documentation please use \`make ' where is one of" + @echo " fulldoc to rebuild the apidoc, html, and latexpdf all at once" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + @echo " apidoc to to build RST from source code" + @echo " clean to clean all documents built by Sphinx in _build" + @echo " allclean to clean all autogenerated documents both from Sphinx and apidoc" + +allclean: + -rm -rf $(BUILDDIR)/* $(RSTDIR)/modules.rst + -rm $(RSTDIR)/_format_auto_docs/*.png + -rm $(RSTDIR)/_format_auto_docs/*.pdf + -rm $(RSTDIR)/_format_auto_docs/*.rst + -rm $(RSTDIR)/_format_auto_docs/*.inc + +clean: + -rm -rf $(BUILDDIR)/* $(RSTDIR)/modules.rst + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/sample.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/sample.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/sample" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/sample" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " "results in $(BUILDDIR)/doctest/output.txt." + +apidoc: + PYTHONPATH=$(CONFDIR):$(PYTHONPATH) nwb_generate_format_docs + @echo + @echo "Generate rst source files from NWB spec." + +fulldoc: + $(MAKE) allclean + @echo + @echo "Rebuilding apidoc, html, latexpdf" + $(MAKE) apidoc + $(MAKE) html + $(MAKE) latexpdf diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9a3a30d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,121 @@ + +# Getting started + +## Generate Documentation + +* To generate the HTML version of your documentation run ``make html``. +* The [hdmf-docutils](https://pypi.org/project/hdmf-docutils/) package must be installed. + +## Customize Your Extension Documentation + +* **extension description** + * Edit ``source/description.rst`` to describe your extension. + +* **release notes** + * Edit ``source/release_notes.rst`` to document improvements and fixes of your extension. + +* **documentation build settings** + * Edit ``source/conf.py`` to customize your extension documentation configuration. + * Edit ``source/conf_doc_autogen.py`` to customize the format documentation auto-generation based on + the YAML specification files. + + +# Overview + +The specification documentation uses Sphinx [http://www.sphinx-doc.org/en/stable/index.html](http://www.sphinx-doc.org/en/stable/index.html) + +## Rebuilding All + +To rebuild the full documentation in html, latex, and PDF simply run: + +``` +make fulldoc +``` + +This is a convenience function that is equivalent to: + +``` +make allclean +make apidoc +make html +make latexpdf +``` + +## Generating the format documentation from the format spec + +The format documentation is auto-generated from the format specification (YAML) sources via: + +``` +make apidoc +``` + +This will invoke the executable: + +``` +hdmf_generate_format_docs +``` + +The script automatically generates a series of .rst, .png, and .pdf files that are stored in the folder `source/format_auto_docs`. The generated .rst files are included in `source/format.rst` and the png and pdf files are used as figures in the autogenerated docs. + +The folder `source/format_auto_docs` is reserved for autogenerated files, i.e., files in the folder should not be added or edited by hand as they will be deleted and rebuilt during the full built of the documentation. + +By default the Sphinx configuration is setup to always regenerate the sources whenever the docs are being built (see next section). This behavior can be customized via the `spec_doc_rebuild_always` parameter in `source/conf.py` + +## Building a specific document type + +To build the documentation, run: + +``` +make +``` + +where `` is, e.g., `latexpdf`, `html`, `singlehtml`, or `man`. For a complete list of supported doc-types, see: + +``` +make help +``` + +## Cleaning up + +`make clean` cleans up all builds of the documentation located in `_build`. + +`make allclean` cleans up all builds of the documentation located in `_build` as well as all autogenerated sources stored in `source/format_auto_docs`. + +## Configuration + +The build of the documentation can be customized via a broad range of Sphinx options in: + +`source/conf_doc_autogen.py` + +In addition to standard Sphinx options, there are a number of additional options used to customize the content and structure of the autogenerated documents, e.g.: + +* `spec_show_yaml_src` - Boolean indicating whether the YAML sources should be included for the different Neurodata types +* `spec_generate_src_file` - Boolean indicating whether the YAML sources of the neurodata_types should be rendered in a separate section (True) or in the same location as the main documentation +* `spec_show_hierarchy_plots` - Boolean indicating whether we should generate and show figures of the hierarchy defined by the specifications as part of the documentation +* `spec_file_per_type` - Boolean indicating whether we should generate separate .inc reStructuredText for each neurodata_type (True) +or should all text be added to the main file (False) +* `spec_show_subgroups_in_tables` - Should subgroups of the main groups be rendered in the table as well. Usually this is disabled since groups are rendered as separate sections in the text +* `spec_appreviate_main_object_doc_in_tables` - Abbreviate the documentation of the main object for which a table is rendered in the table. This is commonly set to True as doc of the main object is already rendered as the main intro for the section describing the object +* `spec_show_title_for_tables` - Add a title for the table showing the specifications. +* `spec_show_subgroups_in_seperate_table` - Should top-level subgroups be listed in a separate table or as part of the main dataset and attributes table +* `spec_table_depth_char` - Char to be used as prefix to indicate the depth of an object in the specification hierarchy. NOTE: The char used should be supported by LaTeX. +* `spec_add_latex_clearpage_after_ndt_sections` - Add a LaTeX clearpage after each main section describing a neurodata_type. This helps in LaTeX to keep the ordering of figures, tables, and code blocks consistent in particular when the hierarchy_plots are included. +* `spec_resolve_type_inc` - Resolve includes to always show the full list of objects that are part of a type (True) or to show only the parts that are actually new to a current type while only linking to base types (False) + +In addition, the location of the input format specification can be customized as follows: + +* `spec_input_spec_dir` - Directory where the YAML files for the namespace to be documented are located +* `spec_input_namespace_filename` - Name of the YAML file with the specification of the Namespace to be documented +* `spec_input_default_namespace` - Name of the default namespace in the file + +Finally, the name and location of output files can be customized as follows: + +* `spec_output_dir` - Directory where the autogenerated files should be stored +* `spec_output_master_filename` - Name of the master .rst file that includes all the autogenerated docs +* `spec_output_doc_filename` - Name of the file where the main documentation goes +* `spec_output_src_filename` - Name of the file where the sources of the format spec go. NOTE: This file is only generated if `spec_generate_src_file` is enabled +* `spec_output_doc_type_hierarchy_filename` - Name of the file containing the type hierarchy. (Included in `spec_output_doc_filename`) + +In the regular Sphinx `source/conf.py` file, we can then also set: + +* `spec_doc_rebuild_always` - Boolean to define whether to always rebuild the source docs from YAML when doing a regular build of the sources (e.g., via `make html`) even if the folder with the source files already exists diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..dc1312a --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/theme_overrides.css b/docs/source/_static/theme_overrides.css new file mode 100644 index 0000000..63ee6cc --- /dev/null +++ b/docs/source/_static/theme_overrides.css @@ -0,0 +1,13 @@ +/* override table width restrictions */ +@media screen and (min-width: 767px) { + + .wy-table-responsive table td { + /* !important prevents the common CSS stylesheets from overriding + this as on RTD they are loaded after this stylesheet */ + white-space: normal !important; + } + + .wy-table-responsive { + overflow: visible !important; + } +} diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..002c717 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,112 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'ndx-microscopy' +copyright = '2024, Alessandra Trapani, Cody Baker' +author = 'Alessandra Trapani, Cody Baker' + +version = '0.1.0' +release = 'alpha' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + 'sphinx.ext.ifconfig', + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', +] + +templates_path = ['_templates'] +exclude_patterns = [] + +language = 'en' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'alabaster' +html_static_path = ['_static'] + +# -- Options for intersphinx extension --------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration + +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), +} + + +############################################################################ +# CUSTOM CONFIGURATIONS ADDED BY THE NWB TOOL FOR GENERATING FORMAT DOCS +########################################################################### + +import sphinx_rtd_theme # noqa: E402 +import textwrap # noqa: E402 + +# -- Options for intersphinx --------------------------------------------- +intersphinx_mapping.update({ + 'core': ('https://nwb-schema.readthedocs.io/en/latest/', None), + 'hdmf-common': ('https://hdmf-common-schema.readthedocs.io/en/latest/', None), +}) + +# -- Generate sources from YAML--------------------------------------------------- +# Always rebuild the source docs from YAML even if the folder with the source files already exists +spec_doc_rebuild_always = True + + +def run_doc_autogen(_): + # Execute the autogeneration of Sphinx format docs from the YAML sources + import sys + import os + conf_file_dir = os.path.dirname(os.path.abspath(__file__)) + sys.path.append(conf_file_dir) # Need so that generate format docs can find the conf_doc_autogen file + from conf_doc_autogen import spec_output_dir + + if spec_doc_rebuild_always or not os.path.exists(spec_output_dir): + sys.path.append('./docs') # needed to enable import of generate_format docs + from hdmf_docutils.generate_format_docs import main as generate_docs + generate_docs() + + +def setup(app): + app.connect('builder-inited', run_doc_autogen) + # overrides for wide tables in RTD theme + try: + app.add_css_file("theme_overrides.css") # Used by newer Sphinx versions + except AttributeError: + app.add_stylesheet("theme_overrides.css") # Used by older version of Sphinx + +# -- Customize sphinx settings +numfig = True +autoclass_content = 'both' +autodoc_docstring_signature = True +autodoc_member_order = 'bysource' +add_function_parentheses = False + + +# -- HTML sphinx options +html_theme = "sphinx_rtd_theme" +html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# LaTeX Sphinx options +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + 'preamble': textwrap.dedent( + ''' + \\setcounter{tocdepth}{3} + \\setcounter{secnumdepth}{6} + \\usepackage{enumitem} + \\setlistdepth{100} + '''), +} diff --git a/docs/source/conf_doc_autogen.py b/docs/source/conf_doc_autogen.py new file mode 100644 index 0000000..aed891b --- /dev/null +++ b/docs/source/conf_doc_autogen.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Configuration file for generating sources for the format documentation from the YAML specification files + +import os + +# -- Input options for the specification files to be used ----------------------- + +# Directory where the YAML files for the namespace to be documented are located +spec_input_spec_dir = '..\spec' + +# Name of the YAML file with the specification of the Namespace to be documented +spec_input_namespace_filename = 'ndx-microscopy.namespace.yaml' + +# Name of the default namespace in the file +spec_input_default_namespace = 'ndx-microscopy' + + +# -- Options for customizing the locations of output files + +# Directory where the autogenerated files should be stored +spec_output_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_format_auto_docs") + +# Name of the master rst file that includes all the autogenerated docs +spec_output_master_filename = 'format_spec_main.inc' + +# Name of the file where the main documentation goes +spec_output_doc_filename = 'format_spec_doc.inc' + +# Name of the file where the sources of the format spec go. NOTE: This file is only generated if +# spec_generate_src_file is enabled +spec_output_src_filename = 'format_spec_sources.inc' + +# Name of the file containing the type hierarchy. (Included in spec_output_doc_filename) +spec_output_doc_type_hierarchy_filename = 'format_spec_type_hierarchy.inc' + +# Clean up the output directory before we build if the git hash is out of date +spec_clean_output_dir_if_old_git_hash = True + +# Do not rebuild the format sources if we have previously build the sources and the git hash matches +spec_skip_doc_autogen_if_current_git_hash = False + + +# -- Options for the generation of the documentation from source ---------------- + +# Should the YAML sources be included for the different modules +spec_show_yaml_src = True + +# Show figure of the hierarchy of objects defined by the spec +spec_show_hierarchy_plots = True + +# Should the sources of the neurodata_types (YAML) be rendered in a separate section (True) or +# in the same location as the base documentation +spec_generate_src_file = True + +# Should separate .inc reStructuredText files be generated for each neurodata_type (True) +# or should all text be added to the main file +spec_file_per_type = True + +# Should top-level subgroups be listed in a separate table or as part of the main dataset and attributes table +spec_show_subgroups_in_seperate_table = True + +# Abbreviate the documentation of the main object for which a table is rendered in the table. +# This is commonly set to True as doc of the main object is alrready rendered as the main intro for the +# section describing the object +spec_appreviate_main_object_doc_in_tables = True + +# Show a title for the tables +spec_show_title_for_tables = True + +# Char to be used as prefix to indicate the depth of an object in the specification hierarchy +spec_table_depth_char = '.' # '→' '.' + +# Add a LaTeX clearpage after each main section describing a neurodata_type. This helps in LaTeX to keep the ordering +# of figures, tables, and code blocks consistent in particular when the hierarchy_plots are included +spec_add_latex_clearpage_after_ndt_sections = True + +# Resolve includes to always show the full list of objects that are part of a type (True) +# or to show only the parts that are actually new to a current type while only linking to base types +spec_resolve_type_inc = False + +# Default type map to be used. This is the type map where dependent namespaces are stored. In the case of +# NWB this is spec_default_type_map = pynwb.get_type_map() +import pynwb # noqa: E402 +spec_default_type_map = pynwb.get_type_map() + +# Default specification classes for groups datasets and namespaces. In the case of NWB these are the NWB-specfic +# spec classes. In the general cases these are the spec classes from HDMF +spec_group_spec_cls = pynwb.spec.NWBGroupSpec +spec_dataset_spec_cls = pynwb.spec.NWBDatasetSpec +spec_namespace_spec_cls = pynwb.spec.NWBNamespace diff --git a/docs/source/credits.rst b/docs/source/credits.rst new file mode 100644 index 0000000..da5cda1 --- /dev/null +++ b/docs/source/credits.rst @@ -0,0 +1,21 @@ +******* +Credits +******* + +.. note:: + Add the credits for your extension here + +Acknowledgments +=============== + + +Authors +======= + + +***** +Legal +***** + +License +======= diff --git a/docs/source/description.rst b/docs/source/description.rst new file mode 100644 index 0000000..6f8553e --- /dev/null +++ b/docs/source/description.rst @@ -0,0 +1,5 @@ +Overview +======== + +.. note:: + Add the description of your extension here diff --git a/docs/source/format.rst b/docs/source/format.rst new file mode 100644 index 0000000..4b88782 --- /dev/null +++ b/docs/source/format.rst @@ -0,0 +1,12 @@ + +.. _ndx-microscopy: + +************** +ndx-microscopy +************** + +Version |release| |today| + +.. .. contents:: + +.. include:: _format_auto_docs/format_spec_main.inc diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..207c9a0 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,30 @@ +Specification for the ndx-microscopy extension +============================================== + +.. toctree:: + :numbered: + :maxdepth: 8 + :caption: Table of Contents + + description + +.. toctree:: + :numbered: + :maxdepth: 3 + :caption: Extension Specification + + format + +.. toctree:: + :maxdepth: 2 + :caption: History & Legal + + release_notes + credits + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst new file mode 100644 index 0000000..39ccd1c --- /dev/null +++ b/docs/source/release_notes.rst @@ -0,0 +1,5 @@ +Release Notes +============= + +.. note:: + Add the release notes of your extension here diff --git a/pyproject.toml b/pyproject.toml index 2b90ddd..8feb95b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,25 +1,114 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "ndx-microscopy" +version = "0.1.0" +authors = [ + { name="Alessandra Trapani", email="alessandra.trapani@catalystneuro.com" }, + { name="Cody Baker", email="cody.baker@catalystneuro.com" }, +] +description = "An NWB extension to demonstrate the TAB proposal for enhancements to optical physiology neurodata types." +readme = "README.md" +# requires-python = ">=3.8" +license = {text = "BSD-3"} +classifiers = [ + # TODO: add classifiers before release + # "Programming Language :: Python", + # "Programming Language :: Python :: 3.8", + # "Programming Language :: Python :: 3.9", + # "Programming Language :: Python :: 3.10", + # "Programming Language :: Python :: 3.11", + # "Programming Language :: Python :: 3.12", + # "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", +] +keywords = [ + 'NeurodataWithoutBorders', + 'NWB', + 'nwb-extension', + 'ndx-extension', +] +dependencies = [ + "pynwb>=2.8.0", + "hdmf>=3.14.1", + "ndx-ophys-devices>=0.1.0" +] + +# TODO: add URLs before release +# [project.urls] +# "Homepage" = "https://github.com/organization/package" +# "Documentation" = "https://package.readthedocs.io/" +# "Bug Tracker" = "https://github.com/organization/package/issues" +# "Discussions" = "https://github.com/organization/package/discussions" +# "Changelog" = "https://github.com/organization/package/blob/main/CHANGELOG.md" + +# Include only the source code under `src/pynwb/ndx_microscopy` and the spec files under `spec` +# in the wheel. +[tool.hatch.build.targets.wheel] +packages = [ + "src/pynwb/ndx_microscopy", + "spec" +] + +# Rewrite the path to the `spec` directory to `ndx_microscopy/spec`. +# `ndx_microscopy/__init__.py` will look there first for the spec files. +# The resulting directory structure within the wheel will be: +# ndx_microscopy/ +# ├── __init__.py +# ├── spec +# └── widgets +[tool.hatch.build.targets.wheel.sources] +"spec" = "ndx_microscopy/spec" + +# The source distribution includes everything in the package except for the `src/matnwb` directory and +# git and github-related files. +[tool.hatch.build.targets.sdist] +exclude = [ + ".git*", + "src/matnwb", +] + +[tool.pytest.ini_options] +# uncomment below to run pytest always with code coverage reporting. NOTE: breakpoints may not work +# addopts = "--cov --cov-report html" + +[tool.codespell] +skip = "htmlcov,.git,.mypy_cache,.pytest_cache,.coverage,*.pdf,*.svg,venvs,.tox,hdmf-common-schema,./docs/_build/*,*.ipynb" + +[tool.coverage.run] +branch = true +source = ["ndx_microscopy"] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "@abstract" +] + [tool.black] line-length = 120 -target-version = ['py39', 'py310', 'py311', 'py312'] -include = '\.pyi?$' -extend-exclude = ''' -/( - \.toml - |\.yml - |\.txt - |\.sh - |\.git - |\.ini - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | build - | dist -)/ -''' - -[tool.isort] -profile = "black" -reverse_relative = true -known_first_party = ["ndx_microscopy"] +preview = true +exclude = ".git|.mypy_cache|.tox|.venv|venv|.ipynb_checkpoints|_build/|dist/|__pypackages__|.ipynb|docs/" + +[tool.ruff] +lint.select = ["E", "F", "T100", "T201", "T203"] +exclude = [ + ".git", + ".tox", + "__pycache__", + "build/", + "dist/", + "docs/source/conf.py", +] +line-length = 120 + +[tool.ruff.lint.per-file-ignores] +"src/pynwb/ndx_microscopy/__init__.py" = ["E402", "F401"] +"src/spec/create_extension_spec.py" = ["T201"] + +[tool.ruff.lint.mccabe] +max-complexity = 17 diff --git a/requirements-dev.txt b/requirements-dev.txt index 9955dec..0db92de 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,14 @@ -pytest -pytest-cov +# pinned dependencies to reproduce an entire development environment to +# run tests, check code style, and generate documentation +black==24.4.2 +codespell==2.3.0 +coverage==7.5.4 +hdmf==3.14.1 +hdmf-docutils==0.4.7 +pre-commit==3.5.0 # latest pre-commit does not support py3.8 +pynwb==2.8.0 +pytest==8.2.2 +pytest-cov==5.0.0 +pytest-subtests==0.12.1 +python-dateutil==2.8.2 +ruff==0.4.10 diff --git a/requirements-min.txt b/requirements-min.txt index b99b50e..e104c04 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,3 +1,6 @@ -pynwb -git+https://github.com/catalystneuro/ndx-ophys-devices.git@main#egg=ndx-ophys-devices - +# minimum versions of package dependencies for installation +# these should match the minimum versions specified in pyproject.toml +# NOTE: it may be possible to relax these minimum requirements +pynwb==2.8.0 +hdmf==3.14.1 +ndx-ophys-devices==0.1.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f6c4008..0000000 --- a/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[wheel] -universal = 1 - -[flake8] -max-line-length = 120 -max-complexity = 17 -exclude = - .git, - .tox, - __pycache__, - build/, - dist/, - docs/source/conf.py - versioneer.py - -[metadata] -description-file = README.md diff --git a/setup.py b/setup.py deleted file mode 100644 index 33da03e..0000000 --- a/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- - -import os -from shutil import copy2 - -from setuptools import find_packages, setup - -# load README.md/README.rst file -try: - if os.path.exists("README.md"): - with open("README.md", "r") as fp: - readme = fp.read() - readme_type = "text/markdown; charset=UTF-8" - elif os.path.exists("README.rst"): - with open("README.rst", "r") as fp: - readme = fp.read() - readme_type = "text/x-rst; charset=UTF-8" - else: - readme = "" -except Exception: - readme = "" - -setup_args = { - "name": "ndx-microscopy", - "version": "0.1.0", - "description": "An example extension to demonstrate the TAB proposal for enhancements to optical physiology neurodata types.", - "long_description": readme, - "long_description_content_type": readme_type, - "author": "Cody Baker and Alessandra Trapani", - "author_email": "cody.baker@catalystneuro.com", - "url": "", - "license": "MIT", - "install_requires": [ - "pynwb>=1.5.0,<3", - "hdmf>=2.5.6,<4", - ], - "packages": find_packages("src/pynwb", exclude=["tests", "tests.*"]), - "package_dir": {"": "src/pynwb"}, - "package_data": { - "ndx_microscopy": [ - "spec/ndx-microscopy.namespace.yaml", - "spec/ndx-microscopy.extensions.yaml", - ] - }, - "classifiers": [ - "Intended Audience :: Developers", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - ], - "keywords": ["NeurodataWithoutBorders", "NWB", "nwb-extension", "ndx-extension"], - "zip_safe": False, -} - - -def _copy_spec_files(project_dir): - ns_path = os.path.join(project_dir, "spec", "ndx-microscopy.namespace.yaml") - ext_path = os.path.join(project_dir, "spec", "ndx-microscopy.extensions.yaml") - - dst_dir = os.path.join(project_dir, "src", "pynwb", "ndx_microscopy", "spec") - if not os.path.exists(dst_dir): - os.mkdir(dst_dir) - - copy2(ns_path, dst_dir) - copy2(ext_path, dst_dir) - - -if __name__ == "__main__": - _copy_spec_files(os.path.dirname(__file__)) - setup(**setup_args) diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index d34397a..3af474a 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -10,12 +10,15 @@ groups: required: false - neurodata_type_def: ExcitationLightPath - neurodata_type_inc: NWBContainer + neurodata_type_inc: LabMetaData doc: Excitation light path that illuminates an imaging space. attributes: - name: excitation_wavelength_in_nm dtype: numeric doc: Excitation wavelength of light, in nanometers. + - name: description + dtype: text + doc: Description of the excitation light path. links: - name: excitation_source target_type: ExcitationSource @@ -27,12 +30,15 @@ groups: required: false - neurodata_type_def: EmissionLightPath - neurodata_type_inc: NWBContainer + neurodata_type_inc: LabMetaData doc: Emission light path from an imaging space. attributes: - name: emission_wavelength_in_nm dtype: numeric doc: Emission wavelength of light, in nanometers. + - name: description + dtype: text + doc: Description of the emission light path. links: - name: photodetector target_type: Photodetector diff --git a/src/pynwb/ndx_microscopy/__init__.py b/src/pynwb/ndx_microscopy/__init__.py index 6b5f389..394c41b 100644 --- a/src/pynwb/ndx_microscopy/__init__.py +++ b/src/pynwb/ndx_microscopy/__init__.py @@ -8,6 +8,10 @@ # TODO: Remove when python 3.9 becomes the new minimum from importlib_resources import files +# 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 ExcitationSource, Indicator, OpticalFilter, Photodetector + extension_name = "ndx-microscopy" # Get path to the namespace.yaml file with the expected location when installed not in editable mode @@ -19,7 +23,6 @@ __spec_path = __location_of_this_file.parent.parent.parent / "spec" / f"{extension_name}.namespace.yaml" load_namespaces(str(__spec_path)) -from ndx_ophys_devices import ExcitationSource, Indicator, OpticalFilter, Photodetector Microscope = get_class("Microscope", extension_name) ExcitationLightPath = get_class("ExcitationLightPath", extension_name) diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index 192ec93..9e97279 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -39,7 +39,7 @@ def test_roundtrip(self): excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) - imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) + imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") @@ -87,7 +87,7 @@ def test_roundtrip(self): excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) - imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace", microscope=microscope) + imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") @@ -137,7 +137,7 @@ def test_roundtrip(self): excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) - imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) + imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") @@ -184,7 +184,7 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace", microscope=microscope) + imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() excitation_light_paths = list() @@ -198,7 +198,7 @@ def test_roundtrip(self): emission_light_paths.append(emission_light_path_0) # TODO: It might be more convenient in Python to have a custom constructor that takes in a list of - # light sources and optical channels and does the VectorData wrapping internally + # excitation light paths and emission light paths and does the VectorData wrapping internally excitation_light_paths_used_by_volume = pynwb.base.VectorData( name="excitation_light_paths", description="Light sources used by this MultiChannelVolume.", @@ -253,7 +253,7 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) + imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() plane_segmentation_1 = mock_MicroscopyPlaneSegmentation( @@ -298,7 +298,7 @@ def test_roundtrip(self): microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace", microscope=microscope) + imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() microscopy_plane_segmentations = mock_MicroscopyPlaneSegmentation( From e8ba1bd3da78bcc678e9f95d17578c2a3276580d Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Thu, 19 Dec 2024 15:30:08 +0100 Subject: [PATCH 07/10] fix tests --- spec/ndx-microscopy.extensions.yaml | 7 +- src/pynwb/tests/test_roundtrip.py | 108 ++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 17 deletions(-) diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index 3af474a..8fef111 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -39,6 +39,10 @@ groups: - name: description dtype: text doc: Description of the emission light path. + groups: + - neurodata_type_inc: Indicator + doc: Indicator object which contains metadata about the indicator used in this light path. + quantity: 1 links: - name: photodetector target_type: Photodetector @@ -48,9 +52,6 @@ groups: target_type: OpticalFilter doc: Link to OpticalFilter object which contains metadata about the optical filter in this emission light path. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). required: false - - name: indicator - target_type: Indicator - doc: Link to Indicator object which contains metadata about the indicator used in this light path. - neurodata_type_def: ImagingSpace neurodata_type_inc: LabMetaData # Would prefer basic NWBContainer diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index 9e97279..35c6438 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -1,10 +1,14 @@ """Test roundtrip (write and read back) of the Python API for the ndx-microscopy extension.""" +from datetime import datetime +from ndx_ophys_devices import Photodetector +from pytz import UTC import pytest from pynwb.testing import TestCase as pynwb_TestCase from pynwb.testing.mock.file import mock_NWBFile import pynwb +from ndx_ophys_devices.testing import mock_ExcitationSource, mock_Photodetector, mock_OpticalFilter from ndx_microscopy.testing import ( mock_EmissionLightPath, mock_ExcitationLightPath, @@ -31,18 +35,38 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + excitation_source=mock_ExcitationSource() + nwbfile.add_device(devices=excitation_source) + + excitation_filter=mock_OpticalFilter() + nwbfile.add_device(devices=excitation_filter) + + excitation_light_path = mock_ExcitationLightPath( + name="ExcitationLightPath", + excitation_source=excitation_source, + excitation_filter=excitation_filter + ) nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() - emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + photodetector=mock_Photodetector() + nwbfile.add_device(devices=photodetector) + + emission_filter=mock_OpticalFilter() + nwbfile.add_device(devices=emission_filter) + + emission_light_path = mock_EmissionLightPath( + name="EmissionLightPath", + emission_filter=emission_filter, + photodetector=photodetector + ) nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) planar_microscopy_series = mock_PlanarMicroscopySeries( @@ -79,18 +103,38 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + excitation_source=mock_ExcitationSource() + nwbfile.add_device(devices=excitation_source) + + excitation_filter=mock_OpticalFilter() + nwbfile.add_device(devices=excitation_filter) + + excitation_light_path = mock_ExcitationLightPath( + name="ExcitationLightPath", + excitation_source=excitation_source, + excitation_filter=excitation_filter + ) nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_VolumetricImagingSpace(name="VolumetricImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_spacec() - emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + photodetector=mock_Photodetector() + nwbfile.add_device(devices=photodetector) + + emission_filter=mock_OpticalFilter() + nwbfile.add_device(devices=emission_filter) + + emission_light_path = mock_EmissionLightPath( + name="EmissionLightPath", + emission_filter=emission_filter, + photodetector=photodetector + ) nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) volumetric_microscopy_series = mock_VolumetricMicroscopySeries( @@ -129,18 +173,38 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) - excitation_light_path = mock_ExcitationLightPath(name="ExcitationLightPath") + excitation_source=mock_ExcitationSource() + nwbfile.add_device(devices=excitation_source) + + excitation_filter=mock_OpticalFilter() + nwbfile.add_device(devices=excitation_filter) + + excitation_light_path = mock_ExcitationLightPath( + name="ExcitationLightPath", + excitation_source=excitation_source, + excitation_filter=excitation_filter + ) nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path) imaging_space = mock_PlanarImagingSpace(name="PlanarImagingSpace") nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() - emission_light_path = mock_EmissionLightPath(name="EmissionLightPath") + photodetector=mock_Photodetector() + nwbfile.add_device(devices=photodetector) + + emission_filter=mock_OpticalFilter() + nwbfile.add_device(devices=emission_filter) + + emission_light_path = mock_EmissionLightPath( + name="EmissionLightPath", + emission_filter=emission_filter, + photodetector=photodetector + ) nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path) variable_depth_microscopy_series = mock_VariableDepthMicroscopySeries( @@ -179,7 +243,7 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) @@ -188,12 +252,28 @@ def test_roundtrip(self): nwbfile.add_lab_meta_data(lab_meta_data=imaging_space) # Would prefer .add_imaging_space() excitation_light_paths = list() - excitation_light_path_0 = mock_ExcitationLightPath(name="ExcitationLightPath") + excitation_source=mock_ExcitationSource() + nwbfile.add_device(devices=excitation_source) + excitation_filter=mock_OpticalFilter() + nwbfile.add_device(devices=excitation_filter) + excitation_light_path_0 = mock_ExcitationLightPath( + name="ExcitationLightPath", + excitation_source=excitation_source, + excitation_filter=excitation_filter + ) nwbfile.add_lab_meta_data(lab_meta_data=excitation_light_path_0) excitation_light_paths.append(excitation_light_path_0) emission_light_paths = list() - emission_light_path_0 = mock_EmissionLightPath(name="EmissionLightPath") + photodetector=mock_Photodetector() + nwbfile.add_device(devices=photodetector) + emission_filter=mock_OpticalFilter() + nwbfile.add_device(devices=emission_filter) + emission_light_path_0 = mock_EmissionLightPath( + name="EmissionLightPath", + photodetector=photodetector, + emission_filter=emission_filter + ) nwbfile.add_lab_meta_data(lab_meta_data=emission_light_path_0) emission_light_paths.append(emission_light_path_0) @@ -248,7 +328,7 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) @@ -293,7 +373,7 @@ def tearDown(self): pynwb.testing.remove_test_file(self.nwbfile_path) def test_roundtrip(self): - nwbfile = mock_NWBFile() + nwbfile = mock_NWBFile(session_start_time=datetime(2000, 1, 1, tzinfo=UTC)) microscope = mock_Microscope(name="Microscope") nwbfile.add_device(devices=microscope) From 4ff646660c84bfc34d19ffe104d78b73b144c3df Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Thu, 19 Dec 2024 15:51:06 +0100 Subject: [PATCH 08/10] remove unused imports --- src/pynwb/tests/test_roundtrip.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pynwb/tests/test_roundtrip.py b/src/pynwb/tests/test_roundtrip.py index 35c6438..dcd447d 100644 --- a/src/pynwb/tests/test_roundtrip.py +++ b/src/pynwb/tests/test_roundtrip.py @@ -1,7 +1,6 @@ """Test roundtrip (write and read back) of the Python API for the ndx-microscopy extension.""" from datetime import datetime -from ndx_ophys_devices import Photodetector from pytz import UTC import pytest from pynwb.testing import TestCase as pynwb_TestCase From 43e1cfd1ce509eb04d27a2225c32a02fb021821d Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Fri, 20 Dec 2024 11:26:27 +0100 Subject: [PATCH 09/10] fix schema validation error --- spec/ndx-microscopy.extensions.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/ndx-microscopy.extensions.yaml b/spec/ndx-microscopy.extensions.yaml index 8fef111..066cc17 100644 --- a/spec/ndx-microscopy.extensions.yaml +++ b/spec/ndx-microscopy.extensions.yaml @@ -23,11 +23,11 @@ groups: - name: excitation_source target_type: ExcitationSource doc: Link to ExcitationSource object which contains metadata about the excitation source device. If it is a pulsed excitation source link a PulsedExcitationSource object. - required: false + quantity: '?' - name: excitation_filter target_type: OpticalFilter doc: Link to OpticalFilter object which contains metadata about the optical filter in this excitation light path. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). - required: false + quantity: '?' - neurodata_type_def: EmissionLightPath neurodata_type_inc: LabMetaData @@ -47,11 +47,11 @@ groups: - name: photodetector target_type: Photodetector doc: Link to Photodetector object which contains metadata about the photodetector device. - required: false + quantity: '?' - name: emission_filter target_type: OpticalFilter doc: Link to OpticalFilter object which contains metadata about the optical filter in this emission light path. It can be either a BandOpticalFilter (e.g., 'Bandpass', 'Bandstop', 'Longpass', 'Shortpass') or a EdgeOpticalFilter (Longpass or Shortpass). - required: false + quantity: '?' - neurodata_type_def: ImagingSpace neurodata_type_inc: LabMetaData # Would prefer basic NWBContainer From d2d9d5698e1015bd012c08f8fc6a8525f1ae4483 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Fri, 20 Dec 2024 11:31:55 +0100 Subject: [PATCH 10/10] fix spec_input_spec_dir definition --- docs/source/conf_doc_autogen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf_doc_autogen.py b/docs/source/conf_doc_autogen.py index aed891b..a89e1b0 100644 --- a/docs/source/conf_doc_autogen.py +++ b/docs/source/conf_doc_autogen.py @@ -6,7 +6,7 @@ # -- Input options for the specification files to be used ----------------------- # Directory where the YAML files for the namespace to be documented are located -spec_input_spec_dir = '..\spec' +spec_input_spec_dir = os.path.join('..', 'spec') # Name of the YAML file with the specification of the Namespace to be documented spec_input_namespace_filename = 'ndx-microscopy.namespace.yaml'