patterned (holographic) optogenetic extension to NWB standard
项目描述
ndx-patterned-ogen NWB扩展
我们作为此扩展的一部分发布了十个 PyNWB 容器(我们目前只有Python实现,而不是Python和MATLAB的混合实现 -- 这也是为什么matnwb目录为空的原因)
SpatialLightModulator2D
/SpatialLightModulator3D
和LightSource
容器分别存储了用于光照刺激的二维/三维空间光调制器和光源的元数据。然后这些容器被链接到存储剩余特定于光照刺激方法元数据的父容器PatternedOptogeneticStimulusSite
。OptogeneticStimulusPattern
存储了通用光照刺激模式的参数。TemporalFocusing
存储与时序聚焦模式相关的参数。SpiralScanning
存储与螺旋扫描模式相关的参数。
OptogeneticStimulusTarget
容器存储了目标 ROIs 的子集targeted_rois
,即一个DynamicTableRegion
,它引用了PlaneSegmentation
的行。可选地,我们可以存储已经被成功光刺激的对应分割 ROIs。segmented_rois
也是一个DynamicTableRegion
,它引用了PlaneSegmentation
的行。由于并非所有目标 ROIs 都会导致实际的光刺激,因此应在两个PlaneSegmentation
对象中添加一个global_roi_ids
列,以表达目标 ROIs 和分割 ROIs 之间的对应关系。PhotostimulationTable
是一个TimeIntervals
表格。每一行存储一个刺激开始 - 由start
、stop
、power
(可选frequency
和pulse_width
)定义。每个刺激开始引用一个特定的OptogeneticStimulusTarget
和PhotostimulationPattern
。注意:当power
(frequency
和pulse_width
)被定义时,其值适用于targets
中的所有 ROIs。当power_per_roi
(frequency_per_roi
和pulse_width_per_roi
)被定义时,其长度必须等于targets
中 ROIs 的数量,并且每个元素都指向一个特定的目标 ROI。
背景
安装
要安装扩展,首先使用以下命令将 ndx-patterned-ogen
存储库克隆到所需的文件夹
git clone https://github.com/https://github.com/catalystneuro/ndx-patterned-ogen.git
然后,为了安装必需的 Python 包和扩展,运行
python -m pip install -r requirements.txt -r requirements-dev.txt
python setup.py install
然后,可以通过 import ndx_patterned_ogen
将扩展导入到 Python 脚本中。
用法
有关完整示例用法,请参阅 tutorial.ipynb
ndx-patterne-ogen-stimulation 的 2D 刺激示例用法
在以下教程中,我们演示了如何使用 ndx-patterned-ogen
扩展到 NWB 数据标准。具体来说,我们
- 创建
SpatialLightModulator2D
和LightSource
容器,代表范式中所使用的设备。 - 使用
PatternedOptogeneticStimulusSite
容器来存储关于位置、范式中所使用的 opsins 和激发波长的信息 - 使用
OptogeneticStimulus2DPattern
(或SpiralScanning
或TemporalFocusing
)容器来存储刺激开始的特定参数。 - 在
PatternedOptogeneticStimulusTable
容器中记录刺激呈现 - 将所有设备、刺激和呈现表格写入一个
.nwb
文件,并确认它可以被读回
# 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()
文档
规范
扩展的 规范 文档(基于 YAML 文件),已生成并存储在 ./docs
文件夹中。要创建它,请在主目录中运行以下命令
cd docs
make fulldoc
这将在 ./docs/build
文件夹中保存文档,可以通过 ./docs/build/html/index.html
文件访问。
API
为了生成 Python API 的文档(存储在 ./api_docs
),我们使用 Sphinx 和来自 ReadTheDocs 的模板。API 文档可以通过运行
sphinx-build -b html api_docs/source/ api_docs/build/
来自主目录。与规范文档类似,API 文档存储在 ./api_docs/build
。选择 ./api_docs/build/index.html
以网站格式访问 API 文档。
致谢
代码由 Alessandra Trapani 编写。由 CatalystNeuro 团队 与 Histed 实验室 合作。
本扩展使用 ndx-template 创建。
项目详情
下载文件
下载适合您平台的文件。如果您不确定选择哪个,请了解更多关于 安装软件包 的信息。
源代码分发
构建分发
ndx_patterned_ogen-0.1.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 6b1aa5156856340d1f6ff112a0a7dc2412d153003666dfd4b75c5d8c76566deb |
|
MD5 | a9bd39662be1cce1e1d7af314f704429 |
|
BLAKE2b-256 | f3d1ae1b2dfca1cb33dca00c4216c860eb1d086b1587f3691575c70c94be55a4 |
ndx_patterned_ogen-0.1.0-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 383affffd7891dbf7042a398eea878ac8d7985963d20cfb253e1690ffddeca53 |
|
MD5 | 71ff942190c82da452695cee9a8ed005 |
|
BLAKE2b-256 | b7198ca02792b15146536f13f7d69bdf8646a18e967bd4dd054c749dc028c03c |