# ndx-patterned-ogen Extension for NWB
<div style="display:inline">
This is a <a href="https://www.nwb.org/">NeuroData Without Borders (NWB)</a> extension for storing data and metadata from patterned optogenetic (<a href="https://www.nature.com/articles/nmeth.3217">holographic</a>) photostimulation methods. It includes containers for storing photostimulation-specific device parameters, photostimulation target, stimulation patterns and time interval data related to photostimulation.
</div>
![extension schema](ndx-patterned-ogen.png)
<br>We release ten <a href="https://pynwb.readthedocs.io/en/stable/">PyNWB</a> containers as part of this extension (we currently only have a Python implementation, rather than both Python and a MATLAB ones -- this is why the `matnwb` directory is empty):
* The `SpatialLightModulator2D`/`SpatialLightModulator3D` and `LightSource` containers store metadata about the spatial light modulator (either bidimensional or threedimensional) and light source used in the photostimulation, respectively. These containers are then linked to the `PatternedOptogeneticStimulusSite` parent container, which stores the remaining photostimulation method-specifici metadata.
* `OptogeneticStimulusPattern` stores parameters for a generic photostimulation pattern.
* `TemporalFocusing` stores parameters associated with the temporal focusing pattern.
* `SpiralScanning` stores parameters associated with the spiral scanning pattern.
* `OptogeneticStimulusTarget` container stored a subset of targeted ROIs `targeted_rois`, that is a `DynamicTableRegion` referencing the rows of a `PlaneSegmentation`. Optionally, we can store the corresponding segmented ROIs that have been successfully photostimulated. `segmented_rois` is also a `DynamicTableRegion` referencing the rows of a `PlaneSegmentation`. Since not all targeted ROIs may result in an actual photostimulation a `global_roi_ids` column should be added to both `PlaneSegmentation` object to express the correspondence between the targeted and segmented ROIs.
* `PhotostimulationTable` is an `TimeIntervals` table. Each row stores a stimulus onset - defined by `start`, `stop`, `power` (optionally `frequency` and `pulse_width`). Each stimulus onset reference a specific `OptogeneticStimulusTarget` and `PhotostimulationPattern`
NB: When `power`(`frequency` and `pulse_width`) is defined, its value apply to all the ROIs in `targets`. When `power_per_roi`(`frequency_per_roi` and `pulse_width_per_roi`) id defined, the length must equal to the number of ROIs in targets, and each element refers to a specific targeted ROI.
## Background
<img src="https://github.com/histedlab/ndx-photostim/blob/main/docs/images/Cap1.PNG?raw=True" width="225em" align="left" style=" margin:0.5em 0.5em 0.5em 0.5em;">
State-of-the-art <a href="https://www.nature.com/articles/s41467-017-01031-3">patterned photostimulation methods</a>, used in concert with <a href="https://www.nature.com/articles/nmeth818">two-photon imaging</a>,
allow unprecedented
control and measurement of cell activity in the living brain. Methods for managing data for two-photon imaging
experiments are improving, but there is little to no standardization of data for these stimulation methods.
Stimulation in vivo depends on fine-tuning many experimental variables, which poses a challenge for reproducibility
and data sharing between researchers. To improve <a href="https://www.sciencedirect.com/science/article/pii/S0896627321009557">standardization</a> of photostimulation data storage and processing,
we release this extension as a generic data format for simultaneous patterned optogenetic stimulation experiments,
using the NWB format to store experimental details and data relating to both acquisition
and photostimulation.
## Installation
To install the extension, first clone the `ndx-patterned-ogen` repository to the desired folder using the command
```angular2svg
git clone https://github.com/https://github.com/catalystneuro/ndx-patterned-ogen.git
```
Then, to install the requisite python packages and extension, run:
```angular2svg
python -m pip install -r requirements.txt -r requirements-dev.txt
python setup.py install
```
The extension can then be imported into python scripts via `import ndx_patterned_ogen`.
## Usage
**For full example usage, see [tutorial.ipynb](https://github.com/catalystneuro/ndx-patterned-ogen/blob/main/tutorial_patterned_ogen.ipynb)**
#### Example Usage for the ndx-patterne-ogen-stimulation for 2D stimulus
In the following tutorial, we demonstrate use of the `ndx-patterned-ogen` extension to the NWB data standard. Specifically we:
1. Create `SpatialLightModulator2D` and `LightSource` containers, representing the devices used in the paradigm.
2. Use the `PatternedOptogeneticStimulusSite` container to store information about location, the opsin and excitation wavelength used in the paradigm
3. Use the `OptogeneticStimulus2DPattern` (or `SpiralScanning` or `TemporalFocusing`) container to store the pattern-specific parameters of the stimulus onset.
4. Record the stimulus presentation within the `PatternedOptogeneticStimulusTable` container
6. Write all devices, stimuli, and presentation tables to an `.nwb` file and confirm it can be read back
```python
# First, we import then necessary files and create an empty `NWBFile`.
import datetime
import numpy as np
from pynwb import NWBFile, NWBHDF5IO
from ndx_patterned_ogen import (
SpatialLightModulator2D,
LightSource,
PatternedOptogeneticStimulusSite,
PatternedOptogeneticStimulusTable,
OptogeneticStimulus2DPattern,
OptogeneticStimulusTarget,
SpiralScanning,
TemporalFocusing,
)
from hdmf.common.table import DynamicTableRegion
from pynwb.ophys import PlaneSegmentation, ImageSegmentation, OpticalChannel
nwbfile = NWBFile(
session_description="patterned optogenetic synthetic experiment (all optical system)",
identifier="identifier",
session_start_time=datetime.datetime.now(datetime.timezone.utc),
)
# metadata for spiatial light modulator
spatial_light_modulator = SpatialLightModulator2D(
name="SpatialLightModulator2D",
description="Generic description for the slm",
model="slm model",
manufacturer="slm manufacturer",
spatial_resolution=[512, 512],
)
nwbfile.add_device(spatial_light_modulator)
# metadata for the light source
light_source = LightSource(
name="Laser",
model="laser model",
manufacturer="laser manufacturer",
stimulation_wavelength=1035.0, # nm
filter_description="Short pass at 1040 nm",
description="Generic description for the laser",
peak_power=70e-3, # the peak power of stimulation in Watts
intensity=0.005, # the intensity of excitation in W/mm^2
exposure_time=2.51e-13, # the exposure time of the sample in seconds
pulse_rate=1 / 2.51e-13, # the pulse rate of the laser is in Hz
)
nwbfile.add_device(light_source)
# metadata for the microscope
microscope = nwbfile.create_device(
name="2P_microscope",
description="My two-photon microscope",
manufacturer="The best microscope manufacturer",
)
# metadata for the stimulus methods
site = PatternedOptogeneticStimulusSite(
name="PatternedOptogeneticStimulusSite",
description="Scanning", # Scanning or scanless method for shaping optogenetic light (e.g., diffraction limited points, 3D shot, disks, etc.).
excitation_lambda=600.0, # nm
effector="ChR2",
location="VISrl",
device=microscope,
spatial_light_modulator=spatial_light_modulator,
light_source=light_source,
)
nwbfile.add_ogen_site(site)
# For demonstrative purpose, we define here fout different stimulation pattern:
# 1. two generic where the `sweep_size` and the `sweep_mask` can be defined to describe the spatial pattern. If `sweep_size` is a scalar, the sweep pattern is assumed to be a circle with diameter `sweep_size`. If `sweep_size` is a two or three dimensional array, the the sweep pattern is assumed to be a rectangle, with dimensions [width, height]. If the shape is neither a circle or a rectangular, the shape can be save in `sweep_mask`.
# 2. one spiral pattern
# 3. one temporal focusing beam pattern
# metadata for a generic stimulus pattern
import numpy as np
import matplotlib.pyplot as plt
# auxiliary function to generate the sweep shape, either circular or rectangular
def generate_image_mask_np(width, height, sweep_size_in_pizels):
# Create a black image mask
image_mask = np.zeros((height, width), dtype=np.uint8)
# Calculate the position for the center of the white spot
center_x = width // 2
center_y = height // 2
if isinstance(sweep_size_in_pizels, int):
# Circular spot
Y, X = np.ogrid[:height, :width]
dist_from_center = np.sqrt((X - center_x) ** 2 + (Y - center_y) ** 2)
image_mask[dist_from_center <= sweep_size_in_pizels / 2] = 255
elif len(sweep_size_in_pizels) == 2:
# Rectangular spot
half_width = sweep_size_in_pizels[0] // 2
half_height = sweep_size_in_pizels[1] // 2
top_left = (center_x - half_width, center_y - half_height)
bottom_right = (center_x + half_width, center_y + half_height)
image_mask[top_left[1] : bottom_right[1], top_left[0] : bottom_right[0]] = 255
else:
raise ValueError("Invalid sweep_size_in_pizels. Should be a scalar or a 2-element array.")
return image_mask
sweep_size = 8
circular_image_mask_np = generate_image_mask_np(
width=sweep_size * 2, height=sweep_size * 2, sweep_size_in_pizels=sweep_size
) # assuming 1 pixel=1 um
generic_circular_pattern = OptogeneticStimulus2DPattern(
name="CircularOptogeneticStimulusPattern",
description="circular pattern",
sweep_size=sweep_size, # um
# sweep_mask=circular_image_mask_np,
)
nwbfile.add_lab_meta_data(generic_circular_pattern)
# Display the image mask using matplotlib
plt.imshow(circular_image_mask_np, cmap="gray")
plt.show()
sweep_size = [5, 10]
rectangular_image_mask_np = generate_image_mask_np(width=20, height=20, sweep_size_in_pizels=sweep_size)
generic_rectangular_pattern = OptogeneticStimulus2DPattern(
name="RectangularOptogeneticStimulusPattern",
description="rectangular pattern",
sweep_size=sweep_size, # um
sweep_mask=rectangular_image_mask_np,
)
nwbfile.add_lab_meta_data(generic_rectangular_pattern)
# Display the image mask using matplotlib
plt.imshow(rectangular_image_mask_np, cmap="gray")
plt.show()
# metadata for spiral scanning pattern
spiral_scanning = SpiralScanning(
name="SpiralScanning",
diameter=15, # um
height=10, # um
number_of_revolutions=5,
description="scanning beam pattern",
)
nwbfile.add_lab_meta_data(spiral_scanning)
# metadata for temporal focusing pattern
temporal_focusing = TemporalFocusing(
name="TemporalFocusing",
description="scanless beam pattern",
lateral_point_spread_function="9 um ± 0.7 um",
axial_point_spread_function="32 um ± 1.6 um",
)
nwbfile.add_lab_meta_data(temporal_focusing)
# Define two `PlaneSegmentation` tables; one for post-hoc ROI (possibly cell) identification; the other for targeted ROIs. Additional columns on both tables can indicate if the ROI is a cell, and the two tables can be harmonized with the use of a global_roi_id field that matches ROI IDs from one table to the other.
# To do so, we need to define an `ImagingPlane` and an `OpticalChannel` first.
optical_channel = OpticalChannel(
name="OpticalChannel",
description="an optical channel",
emission_lambda=500.0,
)
imaging_plane = nwbfile.create_imaging_plane(
name="ImagingPlane",
optical_channel=optical_channel,
imaging_rate=30.0,
description="a very interesting part of the brain",
device=microscope,
excitation_lambda=600.0,
indicator="GFP",
location="V1",
grid_spacing=[0.01, 0.01],
grid_spacing_unit="meters",
origin_coords=[1.0, 2.0, 3.0],
origin_coords_unit="meters",
)
# All the ROIs simultaneously illuminated are stored in `targeted_rois` in an `OptogeneticStimulusTarget` container, as a table region referencing the `TargetPlaneSegmentation`.
# In this example, the targeted ROIs are 45 in total, divided in 3 groups of 15 ROIs that will be simultaneously illuminated with the same stimulus pattern. Only 30 of them, 10 for each group, results in a successful photostimulation.
# Therefore, we define a `PlaneSegmentation` containing 30 ROIs in total and 3 `roi_table_region` containing 10 ROIs each that would be segmented after being stimulated, and stored in three separate `OptogeneticStimulusTarget` containers.
n_targeted_rois = 45
n_targeted_rois_per_group = n_targeted_rois // 3
targeted_rois_centroids = np.array([[i, i, 0] for i in np.arange(n_targeted_rois, dtype=int)])
targeted_plane_segmentation = PlaneSegmentation(
name="TargetPlaneSegmentation",
description="Table for storing the targeted roi centroids, defined by a one-pixel mask",
imaging_plane=imaging_plane,
)
for roi_centroid in targeted_rois_centroids:
targeted_plane_segmentation.add_roi(pixel_mask=[roi_centroid])
if nwbfile is not None:
if "ophys" not in nwbfile.processing:
nwbfile.create_processing_module("ophys", "ophys")
nwbfile.processing["ophys"].add(targeted_plane_segmentation)
targeted_rois_1 = targeted_plane_segmentation.create_roi_table_region(
name="targeted_rois", # it must be called "segmented_rois"
description="targeted rois",
region=list(np.arange(n_targeted_rois_per_group, dtype=int)),
)
targeted_rois_2 = targeted_plane_segmentation.create_roi_table_region(
name="targeted_rois", # it must be called "segmented_rois"
description="targeted rois",
region=list(np.arange(n_targeted_rois_per_group, 2 * n_targeted_rois_per_group, dtype=int)),
)
targeted_rois_3 = targeted_plane_segmentation.create_roi_table_region(
name="targeted_rois", # it must be called "segmented_rois"
description="targeted rois",
region=list(np.arange(2 * n_targeted_rois_per_group, n_targeted_rois, dtype=int)),
)
n_segmented_rois = 30
n_segmented_rois_per_group = n_segmented_rois // 3
plane_segmentation = PlaneSegmentation(
name="PlaneSegmentation",
description="output from segmenting my favorite imaging plane",
imaging_plane=imaging_plane,
)
# TODO add global_roi_id
for _ in range(n_segmented_rois):
plane_segmentation.add_roi(image_mask=np.zeros((512, 512)))
if nwbfile is not None:
if "ophys" not in nwbfile.processing:
nwbfile.create_processing_module("ophys", "ophys")
nwbfile.processing["ophys"].add(plane_segmentation)
segmented_rois_1 = plane_segmentation.create_roi_table_region(
name="segmented_rois", # it must be called "segmented_rois"
description="segmented rois",
region=list(np.arange(n_segmented_rois_per_group, dtype=int)),
)
segmented_rois_2 = plane_segmentation.create_roi_table_region(
name="segmented_rois",
description="segmented rois",
region=list(np.arange(n_segmented_rois_per_group, 2 * n_segmented_rois_per_group, dtype=int)),
)
segmented_rois_3 = plane_segmentation.create_roi_table_region(
name="segmented_rois",
description="segmented rois",
region=list(np.arange(2 * n_segmented_rois_per_group, n_segmented_rois, dtype=int)),
)
hologram_1 = OptogeneticStimulusTarget(name="Hologram1", segmented_rois=segmented_rois_1, targeted_rois=targeted_rois_1)
nwbfile.add_lab_meta_data(hologram_1)
hologram_2 = OptogeneticStimulusTarget(name="Hologram2", segmented_rois=segmented_rois_2, targeted_rois=targeted_rois_2)
nwbfile.add_lab_meta_data(hologram_2)
hologram_3 = OptogeneticStimulusTarget(name="Hologram3", targeted_rois=targeted_rois_3)
nwbfile.add_lab_meta_data(hologram_3)
# Define the stimulus sequences on the targeted ROIs previously defined in the imaging frame coordinates
# If `power`,`frequency` and `pulse_width` are defined as a scalar it is assumed that all the ROIs defined in `targets` receive the same stimulus `power`,`frequency` and `pulse_width`. However, we can also define `power`,`frequency` and `pulse_width` as 1D arrays of dimension equal to the number of ROIs in targets, so we can define different `power`,`frequency` and `pulse_width` for each target.
stimulus_table = PatternedOptogeneticStimulusTable(
name="PatternedOptogeneticStimulusTable", description="Patterned stimulus"
)
stimulus_table.add_interval(
start_time=0.0,
stop_time=1.0,
power=70e-3,
frequency=20.0,
pulse_width=0.1,
stimulus_pattern=temporal_focusing,
targets=nwbfile.lab_meta_data["Hologram1"],
stimulus_site=site,
)
stimulus_table.add_interval(
start_time=0.5,
stop_time=1.0,
power=50e-3,
stimulus_pattern=spiral_scanning,
targets=hologram_2,
stimulus_site=site,
)
stimulus_table.add_interval(
start_time=0.8,
stop_time=1.7,
power=40e-3,
frequency=20.0,
pulse_width=0.1,
stimulus_pattern=generic_circular_pattern,
targets=hologram_3,
stimulus_site=site,
)
nwbfile.add_time_intervals(stimulus_table)
hologram_3.add_segmented_rois(segmented_rois_3)
# Write and read the NWB File
nwbfile_path = "basics_tutorial_patterned_ogen.nwb"
with NWBHDF5IO(nwbfile_path, mode="w") as io:
io.write(nwbfile)
with NWBHDF5IO(nwbfile_path, mode="r") as io:
nwbfile_in = io.read()
```
## Documentation
### Specification
Documentation for the extension's <a href="https://schema-language.readthedocs.io/en/latest/">specification</a>, which is based on the YAML files, is generated and stored in
the `./docs` folder. To create it, run the following from the home directory:
```angular2svg
cd docs
make fulldoc
```
This will save documentation to the `./docs/build` folder, and can be accessed via the
`./docs/build/html/index.html` file.
### API
To generate documentation for the Python API (stores in `./api_docs`), we use <a href="https://www.sphinx-doc.org/en/master/">Sphinx</a>
and a template from <a href="https://readthedocs.org/">ReadTheDocs</a>. API documentation can
be created by running
```angular2svg
sphinx-build -b html api_docs/source/ api_docs/build/
```
from the home folder. Similar to the specification docs, API documentation is stored in `./api_docs/build`. Select
`./api_docs/build/index.html` to access the API documentation in a website format.
## Credit
Code by Alessandra Trapani. Collaboration between the [CatalystNeuro Team](https://www.catalystneuro.com/) and [Histed Lab](https://www.nimh.nih.gov/research/research-conducted-at-nimh/research-areas/clinics-and-labs/ncb).
This extension was created using [ndx-template](https://github.com/nwb-extensions/ndx-template).
Raw data
{
"_id": null,
"home_page": null,
"name": "ndx-patterned-ogen",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "NWB, NeurodataWithoutBorders, ndx-extension, nwb-extension",
"author": null,
"author_email": "Alessandra Trapani <alessandra.trapani@catalystneuro.com>",
"download_url": "https://files.pythonhosted.org/packages/f3/d1/ae1b2dfca1cb33dca00c4216c860eb1d086b1587f3691575c70c94be55a4/ndx_patterned_ogen-0.1.0.tar.gz",
"platform": null,
"description": "# ndx-patterned-ogen Extension for NWB\n\n<div style=\"display:inline\">\nThis is a <a href=\"https://www.nwb.org/\">NeuroData Without Borders (NWB)</a> extension for storing data and metadata from patterned optogenetic (<a href=\"https://www.nature.com/articles/nmeth.3217\">holographic</a>) photostimulation methods. It includes containers for storing photostimulation-specific device parameters, photostimulation target, stimulation patterns and time interval data related to photostimulation.\n</div>\n\n![extension schema](ndx-patterned-ogen.png)\n\n<br>We release ten <a href=\"https://pynwb.readthedocs.io/en/stable/\">PyNWB</a> containers as part of this extension (we currently only have a Python implementation, rather than both Python and a MATLAB ones -- this is why the `matnwb` directory is empty):\n\n* The `SpatialLightModulator2D`/`SpatialLightModulator3D` and `LightSource` containers store metadata about the spatial light modulator (either bidimensional or threedimensional) and light source used in the photostimulation, respectively. These containers are then linked to the `PatternedOptogeneticStimulusSite` parent container, which stores the remaining photostimulation method-specifici metadata.\n* `OptogeneticStimulusPattern` stores parameters for a generic photostimulation pattern.\n * `TemporalFocusing` stores parameters associated with the temporal focusing pattern.\n * `SpiralScanning` stores parameters associated with the spiral scanning pattern.\n* `OptogeneticStimulusTarget` container stored a subset of targeted ROIs `targeted_rois`, that is a `DynamicTableRegion` referencing the rows of a `PlaneSegmentation`. Optionally, we can store the corresponding segmented ROIs that have been successfully photostimulated. `segmented_rois` is also a `DynamicTableRegion` referencing the rows of a `PlaneSegmentation`. Since not all targeted ROIs may result in an actual photostimulation a `global_roi_ids` column should be added to both `PlaneSegmentation` object to express the correspondence between the targeted and segmented ROIs.\n* `PhotostimulationTable` is an `TimeIntervals` table. Each row stores a stimulus onset - defined by `start`, `stop`, `power` (optionally `frequency` and `pulse_width`). Each stimulus onset reference a specific `OptogeneticStimulusTarget` and `PhotostimulationPattern`\nNB: When `power`(`frequency` and `pulse_width`) is defined, its value apply to all the ROIs in `targets`. When `power_per_roi`(`frequency_per_roi` and `pulse_width_per_roi`) id defined, the length must equal to the number of ROIs in targets, and each element refers to a specific targeted ROI.\n\n## Background\n\n<img src=\"https://github.com/histedlab/ndx-photostim/blob/main/docs/images/Cap1.PNG?raw=True\" width=\"225em\" align=\"left\" style=\" margin:0.5em 0.5em 0.5em 0.5em;\">\nState-of-the-art <a href=\"https://www.nature.com/articles/s41467-017-01031-3\">patterned photostimulation methods</a>, used in concert with <a href=\"https://www.nature.com/articles/nmeth818\">two-photon imaging</a>, \nallow unprecedented \ncontrol and measurement of cell activity in the living brain. Methods for managing data for two-photon imaging \nexperiments are improving, but there is little to no standardization of data for these stimulation methods. \nStimulation in vivo depends on fine-tuning many experimental variables, which poses a challenge for reproducibility \nand data sharing between researchers. To improve <a href=\"https://www.sciencedirect.com/science/article/pii/S0896627321009557\">standardization</a> of photostimulation data storage and processing, \nwe release this extension as a generic data format for simultaneous patterned optogenetic stimulation experiments, \nusing the NWB format to store experimental details and data relating to both acquisition \nand photostimulation.\n\n## Installation\n\nTo install the extension, first clone the `ndx-patterned-ogen` repository to the desired folder using the command\n```angular2svg\ngit clone https://github.com/https://github.com/catalystneuro/ndx-patterned-ogen.git\n```\nThen, to install the requisite python packages and extension, run:\n```angular2svg\npython -m pip install -r requirements.txt -r requirements-dev.txt\npython setup.py install\n```\nThe extension can then be imported into python scripts via `import ndx_patterned_ogen`.\n\n## Usage\n\n**For full example usage, see [tutorial.ipynb](https://github.com/catalystneuro/ndx-patterned-ogen/blob/main/tutorial_patterned_ogen.ipynb)**\n\n#### Example Usage for the ndx-patterne-ogen-stimulation for 2D stimulus \nIn the following tutorial, we demonstrate use of the `ndx-patterned-ogen` extension to the NWB data standard. Specifically we:\n1. Create `SpatialLightModulator2D` and `LightSource` containers, representing the devices used in the paradigm.\n2. Use the `PatternedOptogeneticStimulusSite` container to store information about location, the opsin and excitation wavelength used in the paradigm\n3. Use the `OptogeneticStimulus2DPattern` (or `SpiralScanning` or `TemporalFocusing`) container to store the pattern-specific parameters of the stimulus onset.\n4. Record the stimulus presentation within the `PatternedOptogeneticStimulusTable` container\n6. Write all devices, stimuli, and presentation tables to an `.nwb` file and confirm it can be read back\n\n\n\n\n```python\n# First, we import then necessary files and create an empty `NWBFile`.\nimport datetime\nimport numpy as np\nfrom pynwb import NWBFile, NWBHDF5IO\nfrom ndx_patterned_ogen import (\n SpatialLightModulator2D,\n LightSource,\n PatternedOptogeneticStimulusSite,\n PatternedOptogeneticStimulusTable,\n OptogeneticStimulus2DPattern,\n OptogeneticStimulusTarget,\n SpiralScanning,\n TemporalFocusing,\n)\nfrom hdmf.common.table import DynamicTableRegion\n\nfrom pynwb.ophys import PlaneSegmentation, ImageSegmentation, OpticalChannel\n\nnwbfile = NWBFile(\n session_description=\"patterned optogenetic synthetic experiment (all optical system)\",\n identifier=\"identifier\",\n session_start_time=datetime.datetime.now(datetime.timezone.utc),\n)\n\n# metadata for spiatial light modulator\nspatial_light_modulator = SpatialLightModulator2D(\n name=\"SpatialLightModulator2D\",\n description=\"Generic description for the slm\",\n model=\"slm model\",\n manufacturer=\"slm manufacturer\",\n spatial_resolution=[512, 512],\n)\nnwbfile.add_device(spatial_light_modulator)\n\n# metadata for the light source\nlight_source = LightSource(\n name=\"Laser\",\n model=\"laser model\",\n manufacturer=\"laser manufacturer\",\n stimulation_wavelength=1035.0, # nm\n filter_description=\"Short pass at 1040 nm\",\n description=\"Generic description for the laser\",\n peak_power=70e-3, # the peak power of stimulation in Watts\n intensity=0.005, # the intensity of excitation in W/mm^2\n exposure_time=2.51e-13, # the exposure time of the sample in seconds\n pulse_rate=1 / 2.51e-13, # the pulse rate of the laser is in Hz\n)\nnwbfile.add_device(light_source)\n\n# metadata for the microscope\nmicroscope = nwbfile.create_device(\n name=\"2P_microscope\",\n description=\"My two-photon microscope\",\n manufacturer=\"The best microscope manufacturer\",\n)\n\n# metadata for the stimulus methods\nsite = PatternedOptogeneticStimulusSite(\n name=\"PatternedOptogeneticStimulusSite\",\n description=\"Scanning\", # Scanning or scanless method for shaping optogenetic light (e.g., diffraction limited points, 3D shot, disks, etc.).\n excitation_lambda=600.0, # nm\n effector=\"ChR2\",\n location=\"VISrl\",\n device=microscope,\n spatial_light_modulator=spatial_light_modulator,\n light_source=light_source,\n)\nnwbfile.add_ogen_site(site)\n\n# For demonstrative purpose, we define here fout different stimulation pattern:\n# 1. two generic where the `sweep_size` and the `sweep_mask` can be defined to describe the spatial pattern. If `sweep_size` is a scalar, the sweep pattern is assumed to be a circle with diameter `sweep_size`. If `sweep_size` is a two or three dimensional array, the the sweep pattern is assumed to be a rectangle, with dimensions [width, height]. If the shape is neither a circle or a rectangular, the shape can be save in `sweep_mask`.\n# 2. one spiral pattern\n# 3. one temporal focusing beam pattern\n\n# metadata for a generic stimulus pattern\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n\n# auxiliary function to generate the sweep shape, either circular or rectangular\ndef generate_image_mask_np(width, height, sweep_size_in_pizels):\n # Create a black image mask\n image_mask = np.zeros((height, width), dtype=np.uint8)\n\n # Calculate the position for the center of the white spot\n center_x = width // 2\n center_y = height // 2\n\n if isinstance(sweep_size_in_pizels, int):\n # Circular spot\n Y, X = np.ogrid[:height, :width]\n dist_from_center = np.sqrt((X - center_x) ** 2 + (Y - center_y) ** 2)\n image_mask[dist_from_center <= sweep_size_in_pizels / 2] = 255\n\n elif len(sweep_size_in_pizels) == 2:\n # Rectangular spot\n half_width = sweep_size_in_pizels[0] // 2\n half_height = sweep_size_in_pizels[1] // 2\n top_left = (center_x - half_width, center_y - half_height)\n bottom_right = (center_x + half_width, center_y + half_height)\n image_mask[top_left[1] : bottom_right[1], top_left[0] : bottom_right[0]] = 255\n else:\n raise ValueError(\"Invalid sweep_size_in_pizels. Should be a scalar or a 2-element array.\")\n return image_mask\n\n\nsweep_size = 8\ncircular_image_mask_np = generate_image_mask_np(\n width=sweep_size * 2, height=sweep_size * 2, sweep_size_in_pizels=sweep_size\n) # assuming 1 pixel=1 um\ngeneric_circular_pattern = OptogeneticStimulus2DPattern(\n name=\"CircularOptogeneticStimulusPattern\",\n description=\"circular pattern\",\n sweep_size=sweep_size, # um\n # sweep_mask=circular_image_mask_np,\n)\nnwbfile.add_lab_meta_data(generic_circular_pattern)\n\n# Display the image mask using matplotlib\nplt.imshow(circular_image_mask_np, cmap=\"gray\")\nplt.show()\n\nsweep_size = [5, 10]\nrectangular_image_mask_np = generate_image_mask_np(width=20, height=20, sweep_size_in_pizels=sweep_size)\ngeneric_rectangular_pattern = OptogeneticStimulus2DPattern(\n name=\"RectangularOptogeneticStimulusPattern\",\n description=\"rectangular pattern\",\n sweep_size=sweep_size, # um\n sweep_mask=rectangular_image_mask_np,\n)\nnwbfile.add_lab_meta_data(generic_rectangular_pattern)\n\n# Display the image mask using matplotlib\nplt.imshow(rectangular_image_mask_np, cmap=\"gray\")\nplt.show()\n\n# metadata for spiral scanning pattern\nspiral_scanning = SpiralScanning(\n name=\"SpiralScanning\",\n diameter=15, # um\n height=10, # um\n number_of_revolutions=5,\n description=\"scanning beam pattern\",\n)\nnwbfile.add_lab_meta_data(spiral_scanning)\n\n# metadata for temporal focusing pattern\ntemporal_focusing = TemporalFocusing(\n name=\"TemporalFocusing\",\n description=\"scanless beam pattern\",\n lateral_point_spread_function=\"9 um \u00b1 0.7 um\",\n axial_point_spread_function=\"32 um \u00b1 1.6 um\",\n)\nnwbfile.add_lab_meta_data(temporal_focusing)\n\n# Define two `PlaneSegmentation` tables; one for post-hoc ROI (possibly cell) identification; the other for targeted ROIs. Additional columns on both tables can indicate if the ROI is a cell, and the two tables can be harmonized with the use of a global_roi_id field that matches ROI IDs from one table to the other.\n# To do so, we need to define an `ImagingPlane` and an `OpticalChannel` first.\noptical_channel = OpticalChannel(\n name=\"OpticalChannel\",\n description=\"an optical channel\",\n emission_lambda=500.0,\n)\nimaging_plane = nwbfile.create_imaging_plane(\n name=\"ImagingPlane\",\n optical_channel=optical_channel,\n imaging_rate=30.0,\n description=\"a very interesting part of the brain\",\n device=microscope,\n excitation_lambda=600.0,\n indicator=\"GFP\",\n location=\"V1\",\n grid_spacing=[0.01, 0.01],\n grid_spacing_unit=\"meters\",\n origin_coords=[1.0, 2.0, 3.0],\n origin_coords_unit=\"meters\",\n)\n\n\n# All the ROIs simultaneously illuminated are stored in `targeted_rois` in an `OptogeneticStimulusTarget` container, as a table region referencing the `TargetPlaneSegmentation`.\n# In this example, the targeted ROIs are 45 in total, divided in 3 groups of 15 ROIs that will be simultaneously illuminated with the same stimulus pattern. Only 30 of them, 10 for each group, results in a successful photostimulation.\n# Therefore, we define a `PlaneSegmentation` containing 30 ROIs in total and 3 `roi_table_region` containing 10 ROIs each that would be segmented after being stimulated, and stored in three separate `OptogeneticStimulusTarget` containers.\nn_targeted_rois = 45\nn_targeted_rois_per_group = n_targeted_rois // 3\n\ntargeted_rois_centroids = np.array([[i, i, 0] for i in np.arange(n_targeted_rois, dtype=int)])\n\ntargeted_plane_segmentation = PlaneSegmentation(\n name=\"TargetPlaneSegmentation\",\n description=\"Table for storing the targeted roi centroids, defined by a one-pixel mask\",\n imaging_plane=imaging_plane,\n)\n\nfor roi_centroid in targeted_rois_centroids:\n targeted_plane_segmentation.add_roi(pixel_mask=[roi_centroid])\n\nif nwbfile is not None:\n if \"ophys\" not in nwbfile.processing:\n nwbfile.create_processing_module(\"ophys\", \"ophys\")\n nwbfile.processing[\"ophys\"].add(targeted_plane_segmentation)\n\ntargeted_rois_1 = targeted_plane_segmentation.create_roi_table_region(\n name=\"targeted_rois\", # it must be called \"segmented_rois\"\n description=\"targeted rois\",\n region=list(np.arange(n_targeted_rois_per_group, dtype=int)),\n)\n\ntargeted_rois_2 = targeted_plane_segmentation.create_roi_table_region(\n name=\"targeted_rois\", # it must be called \"segmented_rois\"\n description=\"targeted rois\",\n region=list(np.arange(n_targeted_rois_per_group, 2 * n_targeted_rois_per_group, dtype=int)),\n)\n\ntargeted_rois_3 = targeted_plane_segmentation.create_roi_table_region(\n name=\"targeted_rois\", # it must be called \"segmented_rois\"\n description=\"targeted rois\",\n region=list(np.arange(2 * n_targeted_rois_per_group, n_targeted_rois, dtype=int)),\n)\n\nn_segmented_rois = 30\nn_segmented_rois_per_group = n_segmented_rois // 3\n\nplane_segmentation = PlaneSegmentation(\n name=\"PlaneSegmentation\",\n description=\"output from segmenting my favorite imaging plane\",\n imaging_plane=imaging_plane,\n)\n\n# TODO add global_roi_id\n\nfor _ in range(n_segmented_rois):\n plane_segmentation.add_roi(image_mask=np.zeros((512, 512)))\n\nif nwbfile is not None:\n if \"ophys\" not in nwbfile.processing:\n nwbfile.create_processing_module(\"ophys\", \"ophys\")\n nwbfile.processing[\"ophys\"].add(plane_segmentation)\n\n\nsegmented_rois_1 = plane_segmentation.create_roi_table_region(\n name=\"segmented_rois\", # it must be called \"segmented_rois\"\n description=\"segmented rois\",\n region=list(np.arange(n_segmented_rois_per_group, dtype=int)),\n)\n\nsegmented_rois_2 = plane_segmentation.create_roi_table_region(\n name=\"segmented_rois\",\n description=\"segmented rois\",\n region=list(np.arange(n_segmented_rois_per_group, 2 * n_segmented_rois_per_group, dtype=int)),\n)\n\nsegmented_rois_3 = plane_segmentation.create_roi_table_region(\n name=\"segmented_rois\",\n description=\"segmented rois\",\n region=list(np.arange(2 * n_segmented_rois_per_group, n_segmented_rois, dtype=int)),\n)\n\n\nhologram_1 = OptogeneticStimulusTarget(name=\"Hologram1\", segmented_rois=segmented_rois_1, targeted_rois=targeted_rois_1)\nnwbfile.add_lab_meta_data(hologram_1)\n\nhologram_2 = OptogeneticStimulusTarget(name=\"Hologram2\", segmented_rois=segmented_rois_2, targeted_rois=targeted_rois_2)\n\nnwbfile.add_lab_meta_data(hologram_2)\n\nhologram_3 = OptogeneticStimulusTarget(name=\"Hologram3\", targeted_rois=targeted_rois_3)\n\nnwbfile.add_lab_meta_data(hologram_3)\n\n# Define the stimulus sequences on the targeted ROIs previously defined in the imaging frame coordinates\n# If `power`,`frequency` and `pulse_width` are defined as a scalar it is assumed that all the ROIs defined in `targets` receive the same stimulus `power`,`frequency` and `pulse_width`. However, we can also define `power`,`frequency` and `pulse_width` as 1D arrays of dimension equal to the number of ROIs in targets, so we can define different `power`,`frequency` and `pulse_width` for each target.\nstimulus_table = PatternedOptogeneticStimulusTable(\n name=\"PatternedOptogeneticStimulusTable\", description=\"Patterned stimulus\"\n)\nstimulus_table.add_interval(\n start_time=0.0,\n stop_time=1.0,\n power=70e-3,\n frequency=20.0,\n pulse_width=0.1,\n stimulus_pattern=temporal_focusing,\n targets=nwbfile.lab_meta_data[\"Hologram1\"],\n stimulus_site=site,\n)\nstimulus_table.add_interval(\n start_time=0.5,\n stop_time=1.0,\n power=50e-3,\n stimulus_pattern=spiral_scanning,\n targets=hologram_2,\n stimulus_site=site,\n)\nstimulus_table.add_interval(\n start_time=0.8,\n stop_time=1.7,\n power=40e-3,\n frequency=20.0,\n pulse_width=0.1,\n stimulus_pattern=generic_circular_pattern,\n targets=hologram_3,\n stimulus_site=site,\n)\nnwbfile.add_time_intervals(stimulus_table)\n\nhologram_3.add_segmented_rois(segmented_rois_3)\n\n# Write and read the NWB File\nnwbfile_path = \"basics_tutorial_patterned_ogen.nwb\"\nwith NWBHDF5IO(nwbfile_path, mode=\"w\") as io:\n io.write(nwbfile)\n\nwith NWBHDF5IO(nwbfile_path, mode=\"r\") as io:\n nwbfile_in = io.read()\n\n```\n\n## Documentation\n\n### Specification\n\n\nDocumentation for the extension's <a href=\"https://schema-language.readthedocs.io/en/latest/\">specification</a>, which is based on the YAML files, is generated and stored in\nthe `./docs` folder. To create it, run the following from the home directory:\n```angular2svg\ncd docs\nmake fulldoc\n```\nThis will save documentation to the `./docs/build` folder, and can be accessed via the \n`./docs/build/html/index.html` file.\n\n### API\n\nTo generate documentation for the Python API (stores in `./api_docs`), we use <a href=\"https://www.sphinx-doc.org/en/master/\">Sphinx</a> \nand a template from <a href=\"https://readthedocs.org/\">ReadTheDocs</a>. API documentation can\nbe created by running \n```angular2svg\nsphinx-build -b html api_docs/source/ api_docs/build/\n```\nfrom the home folder. Similar to the specification docs, API documentation is stored in `./api_docs/build`. Select \n`./api_docs/build/index.html` to access the API documentation in a website format.\n\n\n## Credit\n\nCode by Alessandra Trapani. Collaboration between the [CatalystNeuro Team](https://www.catalystneuro.com/) and [Histed Lab](https://www.nimh.nih.gov/research/research-conducted-at-nimh/research-areas/clinics-and-labs/ncb).\n\n\nThis extension was created using [ndx-template](https://github.com/nwb-extensions/ndx-template).\n\n",
"bugtrack_url": null,
"license": "BSD-3",
"summary": "patterned (holographic) optogenetic extension to NWB standard",
"version": "0.1.0",
"project_urls": null,
"split_keywords": [
"nwb",
" neurodatawithoutborders",
" ndx-extension",
" nwb-extension"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "b7198ca02792b15146536f13f7d69bdf8646a18e967bd4dd054c749dc028c03c",
"md5": "71ff942190c82da452695cee9a8ed005",
"sha256": "383affffd7891dbf7042a398eea878ac8d7985963d20cfb253e1690ffddeca53"
},
"downloads": -1,
"filename": "ndx_patterned_ogen-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "71ff942190c82da452695cee9a8ed005",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 14171,
"upload_time": "2024-07-17T21:33:12",
"upload_time_iso_8601": "2024-07-17T21:33:12.746861Z",
"url": "https://files.pythonhosted.org/packages/b7/19/8ca02792b15146536f13f7d69bdf8646a18e967bd4dd054c749dc028c03c/ndx_patterned_ogen-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "f3d1ae1b2dfca1cb33dca00c4216c860eb1d086b1587f3691575c70c94be55a4",
"md5": "a9bd39662be1cce1e1d7af314f704429",
"sha256": "6b1aa5156856340d1f6ff112a0a7dc2412d153003666dfd4b75c5d8c76566deb"
},
"downloads": -1,
"filename": "ndx_patterned_ogen-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "a9bd39662be1cce1e1d7af314f704429",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 24587,
"upload_time": "2024-07-17T21:33:14",
"upload_time_iso_8601": "2024-07-17T21:33:14.324156Z",
"url": "https://files.pythonhosted.org/packages/f3/d1/ae1b2dfca1cb33dca00c4216c860eb1d086b1587f3691575c70c94be55a4/ndx_patterned_ogen-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-07-17 21:33:14",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "ndx-patterned-ogen"
}