NeurodataWithoutBorders / pynwb

A Python API for working with Neurodata stored in the NWB Format
https://pynwb.readthedocs.io
Other
178 stars 84 forks source link

[Documentation]: question about pynwb.ophys modules #1921

Closed ssapir closed 4 months ago

ssapir commented 5 months ago

What would you like changed or added to the documentation and why?

  1. What is the practical difference between pynwb.ophys.Fluorescence and pynwb.ophys.DfOverF? (Both inherits the same classes, and contain similar fields, based on the documentation as well as tutorial: https://github.com/NeurodataWithoutBorders/nwb_tutorial/blob/main/HCK08/ophys_tutorial.ipynb).
  2. pynwb.ophys.ImagingPlane has a parameter "calcium indicator". How is it used (it seems like a free text)? Can it be extended to additional indicators (such as voltage or protenomics)?
  3. Is there an example for hierarchical image segmentation / ROI masks? (for example: define synapse, and subregions within this ROI. Via standard way to define it and not as separated layers requireing postprocessing to connect them)

Thank you!

Do you have any interest in helping write or edit the documentation?

Yes.

Code of Conduct

stephprince commented 5 months ago

Thanks for submitting these questions @ssapir.

@alessandratrapani would you be able to help answer these questions?

alessandratrapani commented 5 months ago

Hi @ssapir!

  1. What is the practical difference between pynwb.ophys.Fluorescence and pynwb.ophys.DfOverF? (Both inherits the same classes, and contain similar fields, based on the documentation as well as tutorial: https://github.com/NeurodataWithoutBorders/nwb_tutorial/blob/main/HCK08/ophys_tutorial.ipynb).

The pynwb.ophys.Fluorescence object is intended to store raw fluorescence traces (RoiResponseSereis), while pynwb.ophys.DfOverF is intended to store baseline corrected fluorescence traces.

  1. pynwb.ophys.ImagingPlane has a parameter "calcium indicator". How is it used (it seems like a free text)? Can it be extended to additional indicators (such as voltage or protenomics)?

The pynwb.ophys.ImagingPlane property indicator is a free text where you can specified the label of the indicator. The indicator is not strictly reserved for calcium indicators, thus you can used it for whatever type of indicator is used to generate the fluorescent signal.

  1. Is there an example for hierarchical image segmentation / ROI masks? (for example: define synapse, and subregions within this ROI. Via standard way to define it and not as separated layers requireing postprocessing to connect them)

Interesting point, I am not aware of an already existing example but, I can propose this solution (@rly do you think this could be a good solution?):

from pynwb.testing.mock.file import mock_NWBFile
from pynwb.testing.mock.ophys import mock_ImagingPlane
from pynwb.ophys import PlaneSegmentation
import numpy as np

nwbfile = mock_NWBFile()

# create a plane segmentation for the higher level of hierarchical image segmentation 
plane_segmentation_synapses = PlaneSegmentation(
    description="higher level of hierarchical image segmentation",
    imaging_plane=mock_ImagingPlane(nwbfile=nwbfile),
    name="PlaneSegmentationSynapses",
)

# add synapses masks
for _ in range(3):
    plane_segmentation_synapses.add_roi(image_mask=np.zeros((10, 10)))

# create a plane segmentation for the lower level of hierarchical image segmentation 
plane_segmentation_subregions = PlaneSegmentation(
    description="lower level of hierarchical image segmentation",
    imaging_plane=mock_ImagingPlane(nwbfile=nwbfile),
    name="PlaneSegmentationSubregions",
)

# adda all subregions masks
for _ in range(10):
    plane_segmentation_subregions.add_roi(image_mask=np.zeros((3, 3)))

# create roi table regions pointing to the plane_segmentation_subregions, one for each synapse
table_region_0 = plane_segmentation_subregions.create_roi_table_region(description="description", region=[0,1,2])
table_region_1 = plane_segmentation_subregions.create_roi_table_region(description="description", region=[3,4,5])
table_region_2 = plane_segmentation_subregions.create_roi_table_region(description="description", region=[6,7,8])

# add a column to hold the links to the table region that correspond to each synapse
plane_segmentation_synapses.add_column(name="synapse_subregions",description="description", data=[table_region_1, table_region_2, table_region_3])

if nwbfile is not None:
    if "ophys" not in nwbfile.processing:
        nwbfile.create_processing_module("ophys", "ophys")
    nwbfile.processing["ophys"].add(plane_segmentation_synapses)
    nwbfile.processing["ophys"].add(plane_segmentation_subregions)
rly commented 5 months ago

@alessandratrapani I think that would be a good solution, but I have one suggestion - it is more conventional to use a single ragged DynamicTableRegion instead of a column with one DynamicTableRegion per row. So I would do instead:

from pynwb.testing.mock.file import mock_NWBFile
from pynwb.testing.mock.ophys import mock_ImagingPlane
from pynwb.ophys import PlaneSegmentation
import numpy as np

nwbfile = mock_NWBFile()

nwbfile.create_processing_module("ophys", "ophys")

# create a plane segmentation for the higher level of hierarchical image segmentation 
plane_segmentation_synapses = PlaneSegmentation(
    description="higher level of hierarchical image segmentation",
    imaging_plane=mock_ImagingPlane(nwbfile=nwbfile),
    name="PlaneSegmentationSynapses",
)
nwbfile.processing["ophys"].add(plane_segmentation_synapses)

# add synapses masks
for _ in range(3):
    plane_segmentation_synapses.add_roi(image_mask=np.zeros((10, 10)))

# create a plane segmentation for the lower level of hierarchical image segmentation 
plane_segmentation_subregions = PlaneSegmentation(
    description="lower level of hierarchical image segmentation",
    imaging_plane=mock_ImagingPlane(nwbfile=nwbfile),
    name="PlaneSegmentationSubregions",
)
nwbfile.processing["ophys"].add(plane_segmentation_subregions)

# adda all subregions masks
for _ in range(10):
    plane_segmentation_subregions.add_roi(image_mask=np.zeros((3, 3)))

# add a column to hold references to the rows of the lower level for each higher level
# e.g., synapse 0 refers to subregions with row indices 0, 1, 2. 
# synapse 1 refers to subregions with row indices 3, 4, 5.
# synapse 2 refers to subregions with row indices 6, 7.
plane_segmentation_synapses.add_column(name="synapse_subregions", description="description", data=[[0,1,2], [3,4,5], [6,7]], table=plane_segmentation_subregions, index=True)

from pynwb import NWBHDF5IO

with NWBHDF5IO("test_pynwb_1921.nwb", "w") as io:
    io.write(nwbfile)

io = NWBHDF5IO("test_pynwb_1921.nwb", "r")
read_nwbfile = io.read()

# this returns the subregions of synapse with row index 2, which are rows 6 and 7 of the subregions table
read_nwbfile.processing["ophys"]["PlaneSegmentationSynapses"][2, "synapse_subregions"]

io.close()

I also moved the creation of the ophys module and addition of tables to the ophys module earlier in the script to avoid the warning when creating the DynamicTableRegion column that the linked table doesn't share an ancestor.

@ssapir to be clear, what we are proposing in this example code is to create two PlaneSegmentation tables that hold ROIs - one for synapses and one for synapse subregions. Then in the synapses table, add a custom column that contains references to rows of the subregions table for each synapse. This column is ragged (indexed) which means it can hold a different number of elements (subregions) for each row.

stephprince commented 4 months ago

thanks @rly and @alessandratrapani for the suggestions. I will close this issue for now, but please reopen if there are any further questions!