ndx-patterned-ogen


Namendx-patterned-ogen JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
Summarypatterned (holographic) optogenetic extension to NWB standard
upload_time2024-07-17 21:33:14
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseBSD-3
keywords nwb neurodatawithoutborders ndx-extension nwb-extension
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 0.75370s