From c0c8837b0eae2dbe6fb5cc27588db73c78a89782 Mon Sep 17 00:00:00 2001 From: bendichter Date: Wed, 8 Mar 2023 15:46:06 -0300 Subject: [PATCH 1/7] tutorials for optogenetics --- docs/gallery/domain/ogen.py | 96 +++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/gallery/domain/ogen.py diff --git a/docs/gallery/domain/ogen.py b/docs/gallery/domain/ogen.py new file mode 100644 index 000000000..1da48bc50 --- /dev/null +++ b/docs/gallery/domain/ogen.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- +""" +Optogenetics +============ + + +This tutorial will demonstrate how to write optogenetics data. + +Creating and Writing NWB files +------------------------------ + +When creating a NWB file, the first step is to create the :py:class:`~pynwb.file.NWBFile` object. +""" + +from datetime import datetime +from uuid import uuid4 + +from dateutil.tz import tzlocal +from pynwb import NWBFile + +nwbfile = NWBFile( + session_description="my first synthetic recording", + identifier=str(uuid4()), + session_start_time=datetime.now(tzlocal()), + experimenter="Baggins, Bilbo", + lab="Bag End Laboratory", + institution="University of Middle Earth at the Shire", + experiment_description="I went on an adventure to reclaim vast treasures.", + session_id="LONELYMTN", +) + +#################### +# Adding optogenetic data +# ----------------------- +# The :py:mod:`~pynwb.ogen` module contains two data types that you will need to write optogenetics data, +# :py:class:`~pynwb.ogen.OptogeneticStimulusSite`, which contains metadata about the stimulus site, and +# :py:class:`~pynwb.ogen.OptogeneticSeries`, which contains the values of the time series. +# +# First, you need to create a :py:class:`~pynwb.device.Device` object linked to the :py:class:`~pynwb.file.NWBFile`: + +device = nwbfile.create_device( + name="device", + description="description of device", + manufacturer="optional but recommended", +) + +#################### +# Now, you can create an :py:class:`~pynwb.ogen.OptogeneticStimulusSite`. The easiest way to do this is to use the +# :py:meth:`~pynwb.file.NWBFile.create_ogen_site` method. + +ogen_site = nwbfile.create_ogen_site( + name="OptogeneticStimulusSite", + device=device, + description="This is an example optogenetic site.", + excitation_lambda=600.0, # nm + location="VISrl", +) + + +#################### +# Another equivalent approach would be to create a :py:class:`~pynwb.ogen.OptogeneticStimulusSite` and then add it to +# the :py:class:`~pynwb.file.NWBFile`: + +from pynwb.ogen import OptogeneticStimulusSite + +ogen_stim_site = OptogeneticStimulusSite( + name="OptogeneticStimulusSite2", + device=device, + description="This is an example optogenetic site.", + excitation_lambda=600.0, # nm + location="VISrl", +) + +nwbfile.add_ogen_site(ogen_stim_site) + +#################### +# The second approach is necessary if you have an extension of :py:class:`~pynwb.ogen.OptogeneticStimulusSite`. +# +# With the :py:class:`~pynwb.ogen.OptogeneticStimulusSite` added, you can now create a +# :py:class:`~pynwb.ogen.OptogeneticSeries`. Here, we will generate some random data using numpy and specify the +# timing using ``rate``. If you have samples at irregular intervals, you should use ``timestamps`` instead. + +import numpy as np +from pynwb.ogen import OptogeneticSeries + + +ogen_series = OptogeneticSeries( + name="OptogeneticSeries", + data=np.random.randn(20), + site=ogen_site, + rate=30.0, # Hz +) + +nwbfile.add_stimulus(ogen_series) + + From 4a9ff141c4f688fa8a6a22634ade9c3cbe1b3a11 Mon Sep 17 00:00:00 2001 From: bendichter Date: Wed, 8 Mar 2023 17:40:11 -0300 Subject: [PATCH 2/7] add one photon series and do some black formatting --- docs/gallery/domain/ophys.py | 232 ++++++++++++++++++++--------------- 1 file changed, 133 insertions(+), 99 deletions(-) diff --git a/docs/gallery/domain/ophys.py b/docs/gallery/domain/ophys.py index a972e1859..533ad41f6 100644 --- a/docs/gallery/domain/ophys.py +++ b/docs/gallery/domain/ophys.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -''' +""" Calcium Imaging Data ==================== @@ -17,17 +17,26 @@ The following examples will reference variables that may not be defined within the block they are used in. For clarity, we define them here: -''' +""" # sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnails_ophys.png' from datetime import datetime +from uuid import uuid4 + from dateutil.tz import tzlocal import numpy as np from pynwb import NWBFile, TimeSeries, NWBHDF5IO from pynwb.image import ImageSeries -from pynwb.ophys import TwoPhotonSeries, OpticalChannel, ImageSegmentation, \ - Fluorescence, CorrectedImageStack, MotionCorrection, RoiResponseSeries +from pynwb.ophys import ( + TwoPhotonSeries, + OpticalChannel, + ImageSegmentation, + Fluorescence, + CorrectedImageStack, + MotionCorrection, + RoiResponseSeries, +) import matplotlib.pyplot as plt @@ -36,18 +45,17 @@ # ------------------------------ # # When creating a NWB file, the first step is to create the :py:class:`~pynwb.file.NWBFile` object. - +from src.pynwb.ophys import OnePhotonSeries nwbfile = NWBFile( - 'my first synthetic recording', - 'EXAMPLE_ID', - datetime.now(tzlocal()), - experimenter='Dr. Bilbo Baggins', - lab='Bag End Laboratory', - institution='University of Middle Earth at the Shire', - experiment_description='I went on an adventure with thirteen dwarves ' - 'to reclaim vast treasures.', - session_id='LONELYMTN' + session_description="my first synthetic recording", + identifier=str(uuid4()), + session_start_time=datetime.now(tzlocal()), + experimenter="Baggins, Bilbo", + lab="Bag End Laboratory", + institution="University of Middle Earth at the Shire", + experiment_description="I went on an adventure to reclaim vast treasures.", + session_id="LONELYMTN", ) #################### @@ -81,37 +89,72 @@ device = nwbfile.create_device( name="Microscope", description="My two-photon microscope", - manufacturer="The best microscope manufacturer" + manufacturer="The best microscope manufacturer", ) optical_channel = OpticalChannel( name="OpticalChannel", description="an optical channel", - emission_lambda=500. + emission_lambda=500.0, ) imaging_plane = nwbfile.create_imaging_plane( name="ImagingPlane", optical_channel=optical_channel, - imaging_rate=30., + imaging_rate=30.0, description="a very interesting part of the brain", device=device, - excitation_lambda=600., + excitation_lambda=600.0, indicator="GFP", location="V1", - grid_spacing=[.01, .01], + grid_spacing=[0.01, 0.01], grid_spacing_unit="meters", - origin_coords=[1., 2., 3.], - origin_coords_unit="meters" + origin_coords=[1.0, 2.0, 3.0], + origin_coords_unit="meters", ) - #################### -# Two-photon Series -# ---------------------------- -# +# One-photon Series +# ----------------- # Now that we have our :py:class:`~pynwb.ophys.ImagingPlane`, we can create a -# :py:class:`~pynwb.ophys.TwoPhotonSeries` object to store our raw two-photon imaging data. +# :py:class:`~pynwb.ophys.OnePhotonSeries` object to store raw one-photon imaging data. # Here, we have two options. The first option is to supply the raw image data to PyNWB, # using the data argument. The other option is to provide a path to the image files. -# These two options have trade-offs, so it is worth spending time considering how you want to store this data. +# These two options have trade-offs, so it is worth spending time considering how you +# want to store this data. + +# using internal data. this data will be stored inside the NWB file +one_p_series1 = OnePhotonSeries( + name="OnePhotonSeries_internal", + data=np.ones((1000, 100, 100)), + imaging_plane=imaging_plane, + rate=1.0, + unit="normalized amplitude", +) + +# using external data. only the file paths will be stored inside the NWB file +one_p_series2 = OnePhotonSeries( + name="OnePhotonSeries_external", + dimension=[100, 100], + external_file=["images.tiff"], + imaging_plane=imaging_plane, + starting_frame=[0], + format="external", + starting_time=0.0, + rate=1.0, +) + +#################### +# Since these one-photon data are raw, acquired data, we will add the +# :py:class:`~pynwb.ophys.OnePhotonSeries` objects to the :py:class:`~pynwb.NWBFile` +# as acquired data. + +nwbfile.add_acquisition(one_p_series1) +nwbfile.add_acquisition(one_p_series2) + +#################### +# Two-photon Series +# ----------------- +# Another option is to create a :py:class:`~pynwb.ophys.TwoPhotonSeries` object to store +# our raw two-photon imaging data. This class behaves similarly to +# :py:class:`~pynwb.ophys.OnePhotonSeries`. # # .. only:: html # @@ -129,38 +172,32 @@ # # using internal data. this data will be stored inside the NWB file -image_series1 = TwoPhotonSeries( - name='TwoPhotonSeries1', +two_p_series1 = TwoPhotonSeries( + name="TwoPhotonSeries1", data=np.ones((1000, 100, 100)), imaging_plane=imaging_plane, rate=1.0, - unit='normalized amplitude' + unit="normalized amplitude", ) # using external data. only the file paths will be stored inside the NWB file -image_series2 = TwoPhotonSeries( - name='TwoPhotonSeries2', +two_p_series2 = TwoPhotonSeries( + name="TwoPhotonSeries2", dimension=[100, 100], - external_file=['images.tiff'], + external_file=["images.tiff"], imaging_plane=imaging_plane, starting_frame=[0], - format='external', + format="external", starting_time=0.0, - rate=1.0 + rate=1.0, ) -#################### -# Since these two-photon data are raw, acquired data, we will add the -# :py:class:`~pynwb.ophys.TwoPhotonSeries` objects to the :py:class:`~pynwb.NWBFile` -# as acquired data. - - -nwbfile.add_acquisition(image_series1) -nwbfile.add_acquisition(image_series2) +nwbfile.add_acquisition(two_p_series1) +nwbfile.add_acquisition(two_p_series2) #################### # Motion Correction (optional) -# --------------------------------- +# ---------------------------- # # You can also store the result of motion correction. # These should be stored in a :py:class:`~pynwb.ophys.MotionCorrection` object, @@ -169,31 +206,29 @@ corrected = ImageSeries( - name='corrected', # this must be named "corrected" + name="corrected", # this must be named "corrected" data=np.ones((1000, 100, 100)), - unit='na', - format='raw', + unit="na", + format="raw", starting_time=0.0, - rate=1.0 + rate=1.0, ) xy_translation = TimeSeries( - name='xy_translation', + name="xy_translation", data=np.ones((1000, 2)), - unit='pixels', + unit="pixels", starting_time=0.0, rate=1.0, ) corrected_image_stack = CorrectedImageStack( corrected=corrected, - original=image_series1, + original=one_p_series1, xy_translation=xy_translation, ) -motion_correction = MotionCorrection( - corrected_image_stacks=[corrected_image_stack] -) +motion_correction = MotionCorrection(corrected_image_stacks=[corrected_image_stack]) #################### # We will create a :py:class:`~pynwb.base.ProcessingModule` named "ophys" to store optical @@ -202,15 +237,14 @@ ophys_module = nwbfile.create_processing_module( - name='ophys', - description='optical physiology processed data' + name="ophys", description="optical physiology processed data" ) ophys_module.add(motion_correction) #################### # Plane Segmentation -# --------------------------------- +# ------------------ # # The :py:class:`~pynwb.ophys.PlaneSegmentation` class stores the detected # regions of interest in the :py:class:`~pynwb.ophys.TwoPhotonSeries` data. @@ -260,10 +294,10 @@ img_seg = ImageSegmentation() ps = img_seg.create_plane_segmentation( - name='PlaneSegmentation', - description='output from segmenting my favorite imaging plane', + name="PlaneSegmentation", + description="output from segmenting my favorite imaging plane", imaging_plane=imaging_plane, - reference_images=image_series1 # optional + reference_images=one_p_series1, # optional ) ophys_module.add(img_seg) @@ -278,8 +312,8 @@ # You can add ROIs to the :py:class:`~pynwb.ophys.PlaneSegmentation` table using # an image mask or a pixel mask. An image mask is an array that is the same size # as a single frame of the :py:class:`~pynwb.ophys.TwoPhotonSeries` that -# indicates the mask weight of each pixel in the image. -# Image mask values (weights) may be boolean or continuous between 0 and 1. +# indicates the mask weight of each pixel in the image. Image mask values (weights) may +# be boolean or continuous between 0 and 1. for _ in range(30): @@ -288,7 +322,7 @@ # randomly generate example image masks x = np.random.randint(0, 95) y = np.random.randint(0, 95) - image_mask[x:x + 5, y:y + 5] = 1 + image_mask[x : x + 5, y : y + 5] = 1 # add image mask to plane segmentation ps.add_roi(image_mask=image_mask) @@ -301,18 +335,18 @@ # ^^^^^^^^^^^ # # Alternatively, you could define ROIs using a pixel mask, which is an array of -# triplets (x, y, weight) that have a non-zero weight. All undefined pixels -# are assumed to be 0. +# triplets (x, y, weight) that have a non-zero weight. All undefined pixels are assumed +# to be 0. # # .. note:: # You need to be consistent within a :py:class:`~pynwb.ophys.PlaneSegmentation` table. # You can add ROIs either using image masks, pixel masks, or voxel masks. ps2 = img_seg.create_plane_segmentation( - name='PlaneSegmentation2', - description='output from segmenting my favorite imaging plane', + name="PlaneSegmentation2", + description="output from segmenting my favorite imaging plane", imaging_plane=imaging_plane, - reference_images=image_series1 # optional + reference_images=one_p_series1, # optional ) for _ in range(30): @@ -335,20 +369,22 @@ # # When storing the segmentation of volumetric imaging, you can use imaging masks. # Alternatively, you could define ROIs using a voxel mask, which is an array of -# triplets (x, y, z, weight) that have a non-zero weight. All undefined voxels -# are assumed to be 0. +# triplets (x, y, z, weight) that have a non-zero weight. All undefined voxels are +# assumed to be 0. # # .. note:: # You need to be consistent within a :py:class:`~pynwb.ophys.PlaneSegmentation` table. # You can add ROIs either using image masks, pixel masks, or voxel masks. ps3 = img_seg.create_plane_segmentation( - name='PlaneSegmentation3', - description='output from segmenting my favorite imaging plane', + name="PlaneSegmentation3", + description="output from segmenting my favorite imaging plane", imaging_plane=imaging_plane, - reference_images=image_series1 # optional + reference_images=one_p_series1, # optional ) +from itertools import product + for _ in range(30): # randomly generate example starting points for region x = np.random.randint(0, 95) @@ -357,18 +393,16 @@ # define an example 4 x 3 x 2 voxel region of weight '0.5' voxel_mask = [] - for ix in range(x, x + 4): - for iy in range(y, y + 3): - for iz in range(z, z + 2): - voxel_mask.append((ix, iy, iz, 0.5)) + for ix, iy, iz in product(range(x, x + 4), range(y, y + 3), range(z, z + 2)): + voxel_mask.append((ix, iy, iz, 0.5)) # add voxel mask to plane segmentation ps3.add_roi(voxel_mask=voxel_mask) #################### -# We can view the :py:class:`~pynwb.ophys.PlaneSegmentation` table with pixel -# masks in tabular form by converting it to a :py:class:`~pandas.DataFrame`. +# We can view the :py:class:`~pynwb.ophys.PlaneSegmentation` table with pixel masks in +# tabular form by converting it to a :py:class:`~pandas.DataFrame`. ps2.to_dataframe() @@ -376,8 +410,8 @@ # Storing Fluorescence Measurements # --------------------------------- # -# Now that the regions of interest are stored, you can store fluorescence data for these ROIs. -# This type of data is stored using the :py:class:`~pynwb.ophys.RoiResponseSeries` +# Now that the regions of interest are stored, you can store fluorescence data for these +# ROIs. This type of data is stored using the :py:class:`~pynwb.ophys.RoiResponseSeries` # and :py:class:`~pynwb.ophys.Fluorescence` classes. # # .. only:: html @@ -406,8 +440,7 @@ # the first two ROIs of the :py:class:`~pynwb.ophys.PlaneSegmentation` table. rt_region = ps.create_roi_table_region( - region=[0, 1], - description='the first of two ROIs' + region=[0, 1], description="the first of two ROIs" ) #################### @@ -416,11 +449,11 @@ roi_resp_series = RoiResponseSeries( - name='RoiResponseSeries', + name="RoiResponseSeries", data=np.ones((50, 2)), # 50 samples, 2 ROIs rois=rt_region, - unit='lumens', - rate=30. + unit="lumens", + rate=30.0, ) #################### @@ -465,7 +498,7 @@ # IO operations are carried out using :py:class:`~pynwb.NWBHDF5IO`. -with NWBHDF5IO('ophys_tutorial.nwb', 'w') as io: +with NWBHDF5IO("ophys_tutorial.nwb", "w") as io: io.write(nwbfile) #################### @@ -487,13 +520,12 @@ # which we named ``"RoiResponseSeries"``. -with NWBHDF5IO('ophys_tutorial.nwb', 'r') as io: +with NWBHDF5IO("ophys_tutorial.nwb", "r") as io: read_nwbfile = io.read() - print(read_nwbfile.acquisition['TwoPhotonSeries1']) - print(read_nwbfile.processing['ophys']) - print(read_nwbfile.processing['ophys']['Fluorescence']) - print( - read_nwbfile.processing['ophys']['Fluorescence']['RoiResponseSeries']) + print(read_nwbfile.acquisition["TwoPhotonSeries1"]) + print(read_nwbfile.processing["ophys"]) + print(read_nwbfile.processing["ophys"]["Fluorescence"]) + print(read_nwbfile.processing["ophys"]["Fluorescence"]["RoiResponseSeries"]) #################### # Accessing your data @@ -508,11 +540,11 @@ # object representing the fluorescence data. -with NWBHDF5IO('ophys_tutorial.nwb', 'r') as io: +with NWBHDF5IO("ophys_tutorial.nwb", "r") as io: read_nwbfile = io.read() - print(read_nwbfile.acquisition['TwoPhotonSeries1']) - print(read_nwbfile.processing['ophys']['Fluorescence']['RoiResponseSeries'].data[:]) + print(read_nwbfile.acquisition["TwoPhotonSeries1"]) + print(read_nwbfile.processing["ophys"]["Fluorescence"]["RoiResponseSeries"].data[:]) #################### # Accessing data regions @@ -526,8 +558,10 @@ # and ``0:3`` (ROIs) in the second dimension from the fluorescence data we have written. -with NWBHDF5IO('ophys_tutorial.nwb', 'r') as io: +with NWBHDF5IO("ophys_tutorial.nwb", "r") as io: read_nwbfile = io.read() - print('section of fluorescence responses:') - print(read_nwbfile.processing['ophys']['Fluorescence']['RoiResponseSeries'].data[0:10, 0:3]) + roi_resp_series = read_nwbfile.processing["ophys"]["Fluorescence"]["RoiResponseSeries"] + + print("section of fluorescence responses:") + print(roi_resp_series.data[0:10, 0:3]) From de381d36f6b0a0b9c9d392f76ee6fc4450ec965f Mon Sep 17 00:00:00 2001 From: bendichter Date: Thu, 9 Mar 2023 11:30:16 -0300 Subject: [PATCH 3/7] rmv ogen tutorial from this PR --- docs/gallery/domain/ogen.py | 96 ------------------------------------- 1 file changed, 96 deletions(-) delete mode 100644 docs/gallery/domain/ogen.py diff --git a/docs/gallery/domain/ogen.py b/docs/gallery/domain/ogen.py deleted file mode 100644 index 1da48bc50..000000000 --- a/docs/gallery/domain/ogen.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Optogenetics -============ - - -This tutorial will demonstrate how to write optogenetics data. - -Creating and Writing NWB files ------------------------------- - -When creating a NWB file, the first step is to create the :py:class:`~pynwb.file.NWBFile` object. -""" - -from datetime import datetime -from uuid import uuid4 - -from dateutil.tz import tzlocal -from pynwb import NWBFile - -nwbfile = NWBFile( - session_description="my first synthetic recording", - identifier=str(uuid4()), - session_start_time=datetime.now(tzlocal()), - experimenter="Baggins, Bilbo", - lab="Bag End Laboratory", - institution="University of Middle Earth at the Shire", - experiment_description="I went on an adventure to reclaim vast treasures.", - session_id="LONELYMTN", -) - -#################### -# Adding optogenetic data -# ----------------------- -# The :py:mod:`~pynwb.ogen` module contains two data types that you will need to write optogenetics data, -# :py:class:`~pynwb.ogen.OptogeneticStimulusSite`, which contains metadata about the stimulus site, and -# :py:class:`~pynwb.ogen.OptogeneticSeries`, which contains the values of the time series. -# -# First, you need to create a :py:class:`~pynwb.device.Device` object linked to the :py:class:`~pynwb.file.NWBFile`: - -device = nwbfile.create_device( - name="device", - description="description of device", - manufacturer="optional but recommended", -) - -#################### -# Now, you can create an :py:class:`~pynwb.ogen.OptogeneticStimulusSite`. The easiest way to do this is to use the -# :py:meth:`~pynwb.file.NWBFile.create_ogen_site` method. - -ogen_site = nwbfile.create_ogen_site( - name="OptogeneticStimulusSite", - device=device, - description="This is an example optogenetic site.", - excitation_lambda=600.0, # nm - location="VISrl", -) - - -#################### -# Another equivalent approach would be to create a :py:class:`~pynwb.ogen.OptogeneticStimulusSite` and then add it to -# the :py:class:`~pynwb.file.NWBFile`: - -from pynwb.ogen import OptogeneticStimulusSite - -ogen_stim_site = OptogeneticStimulusSite( - name="OptogeneticStimulusSite2", - device=device, - description="This is an example optogenetic site.", - excitation_lambda=600.0, # nm - location="VISrl", -) - -nwbfile.add_ogen_site(ogen_stim_site) - -#################### -# The second approach is necessary if you have an extension of :py:class:`~pynwb.ogen.OptogeneticStimulusSite`. -# -# With the :py:class:`~pynwb.ogen.OptogeneticStimulusSite` added, you can now create a -# :py:class:`~pynwb.ogen.OptogeneticSeries`. Here, we will generate some random data using numpy and specify the -# timing using ``rate``. If you have samples at irregular intervals, you should use ``timestamps`` instead. - -import numpy as np -from pynwb.ogen import OptogeneticSeries - - -ogen_series = OptogeneticSeries( - name="OptogeneticSeries", - data=np.random.randn(20), - site=ogen_site, - rate=30.0, # Hz -) - -nwbfile.add_stimulus(ogen_series) - - From 3e59d1133725a1cd3af9a0daa60cf91ed107c99b Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Thu, 9 Mar 2023 09:33:29 -0500 Subject: [PATCH 4/7] Update CHANGELOG.md --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10341071c..c932a4dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### Enhancements and minor changes - Fixed typos and added codespell GitHub action to check spelling in the future. @yarikoptic [#1648](https://github.com/NeurodataWithoutBorders/pynwb/pull/1648) +### Documentation and tutorial enhancements: +- Added `OnePhotonSeries` to [calcium imaging tutorial](https://pynwb.readthedocs.io/en/stable/tutorials/domain/ophys.html#sphx-glr-tutorials-domain-ophys-py). @bendichter [#1658](https://github.com/NeurodataWithoutBorders/pynwb/pull/1658) + ## PyNWB 2.3.1 (February 24, 2023) ### Bug fixes From a74f96c179e7adab5403974306fce315ba63092d Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Thu, 9 Mar 2023 09:33:43 -0500 Subject: [PATCH 5/7] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c932a4dfd..8cfb574d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ### Enhancements and minor changes - Fixed typos and added codespell GitHub action to check spelling in the future. @yarikoptic [#1648](https://github.com/NeurodataWithoutBorders/pynwb/pull/1648) -### Documentation and tutorial enhancements: +### Documentation and tutorial enhancements - Added `OnePhotonSeries` to [calcium imaging tutorial](https://pynwb.readthedocs.io/en/stable/tutorials/domain/ophys.html#sphx-glr-tutorials-domain-ophys-py). @bendichter [#1658](https://github.com/NeurodataWithoutBorders/pynwb/pull/1658) ## PyNWB 2.3.1 (February 24, 2023) From a9212e6a5d1ae30265d8a01e13020716697706d3 Mon Sep 17 00:00:00 2001 From: bendichter Date: Thu, 9 Mar 2023 12:05:51 -0300 Subject: [PATCH 6/7] fix import OnePhotonSeries in ophys tutorial fix problem with value checking inside OnePhotonSeries class --- docs/gallery/domain/ophys.py | 2 +- src/pynwb/ophys.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/gallery/domain/ophys.py b/docs/gallery/domain/ophys.py index 533ad41f6..71c15c1aa 100644 --- a/docs/gallery/domain/ophys.py +++ b/docs/gallery/domain/ophys.py @@ -36,6 +36,7 @@ CorrectedImageStack, MotionCorrection, RoiResponseSeries, + OnePhotonSeries, ) import matplotlib.pyplot as plt @@ -45,7 +46,6 @@ # ------------------------------ # # When creating a NWB file, the first step is to create the :py:class:`~pynwb.file.NWBFile` object. -from src.pynwb.ophys import OnePhotonSeries nwbfile = NWBFile( session_description="my first synthetic recording", diff --git a/src/pynwb/ophys.py b/src/pynwb/ophys.py index b09267ff6..e3d9f8f6d 100644 --- a/src/pynwb/ophys.py +++ b/src/pynwb/ophys.py @@ -192,7 +192,7 @@ def __init__(self, **kwargs): args_to_set = popargs_to_dict(keys_to_set, kwargs) super().__init__(**kwargs) - if args_to_set["binning"] < 0: + if args_to_set["binning"] is not None and args_to_set["binning"] < 0: raise ValueError(f"Binning value must be >= 0: {args_to_set['binning']}") if isinstance(args_to_set["binning"], int): args_to_set["binning"] = np.uint(args_to_set["binning"]) From cc5fd34240891e14a6b82552a4b8c3c259fcafae Mon Sep 17 00:00:00 2001 From: bendichter Date: Thu, 9 Mar 2023 12:13:05 -0300 Subject: [PATCH 7/7] fix NWBFile links --- docs/gallery/domain/ophys.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/gallery/domain/ophys.py b/docs/gallery/domain/ophys.py index 71c15c1aa..6baf58d35 100644 --- a/docs/gallery/domain/ophys.py +++ b/docs/gallery/domain/ophys.py @@ -80,7 +80,7 @@ # :alt: imaging plane UML diagram # :align: center # -# Create a :py:class:`~pynwb.device.Device` named ``"Microscope"`` in the :py:class:`~pynwb.NWBFile` object. Then +# Create a :py:class:`~pynwb.device.Device` named ``"Microscope"`` in the :py:class:`~pynwb.file.NWBFile` object. Then # create an :py:class:`~pynwb.ophys.OpticalChannel` named ``"OpticalChannel"`` and an # :py:class:`~pynwb.ophys.ImagingPlane` named ``"ImagingPlane"``, passing in the :py:class:`~pynwb.ophys.OpticalChannel` # object and the :py:class:`~pynwb.device.Device` object. @@ -143,7 +143,7 @@ #################### # Since these one-photon data are raw, acquired data, we will add the -# :py:class:`~pynwb.ophys.OnePhotonSeries` objects to the :py:class:`~pynwb.NWBFile` +# :py:class:`~pynwb.ophys.OnePhotonSeries` objects to the :py:class:`~pynwb.file.NWBFile` # as acquired data. nwbfile.add_acquisition(one_p_series1) @@ -532,7 +532,7 @@ # ------------------------------ # # Data arrays are read passively from the file. -# Calling the data attribute on a :py:class:`~pynwb.base.pynwb.TimeSeries` +# Calling the data attribute on a :py:class:`~pynwb.base.TimeSeries` # such as a :py:class:`~pynwb.ophys.RoiResponseSeries` does not read the data # values, but presents an :py:class:`~h5py` object that can be indexed to read data. # You can use the ``[:]`` operator to read the entire data array into memory.