Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _roi_response_denoised to store denoised traces #291

Merged
merged 16 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
### Improvements
* Improved xml parsing with Bruker [PR #267](https://github.com/catalystneuro/roiextractors/pull/267)

### Features
* Add support to get background components: add `get_background_ids()`, `get_background_image_masks()`, `get_background_pixel_masks()` to `SegmentationExtractor`. [PR #291](https://github.com/catalystneuro/roiextractors/pull/291)

* Add distinction for raw roi response and denoised roi response in `CaimanSegmentationExtractor`: [PR #291](https://github.com/catalystneuro/roiextractors/pull/291)

* Bug fix for the `CaimanSegmentationExtractor`: correctly extract temporal and spatial background components [PR #291](https://github.com/catalystneuro/roiextractors/pull/291)

# v0.5.5

Expand Down
1 change: 1 addition & 0 deletions docs/source/build_re.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ To build a custom SegmentationExtractor that interfaces with the output of a cus
self._roi_response_raw = self._load_traces()# define a method to extract Flourescence traces
self._roi_response_dff = self._load_traces()# define a method to extract dF/F traces if any else None
self._roi_response_neuropil = self._load_traces()# define a method to extract neuropil info if any else None
self._roi_response_denoised = self._load_traces() # define a method to extract denoised traces if any else None
self._roi_response_deconvolved = self._load_traces() # define a method to extract deconvolved traces if any else None
self._image_correlation = self._load_summary_images()# define method to extract a correlation image else None
self._image_mean = self._load_summary_images() # define method to extract a mean image else None
Expand Down
33 changes: 31 additions & 2 deletions src/roiextractors/extractors/caiman/caimansegmentationextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,16 @@ def __init__(self, file_path: PathType):
SegmentationExtractor.__init__(self)
self.file_path = file_path
self._dataset_file = self._file_extractor_read()
self._roi_response_raw = self._raw_trace_extractor_read()
self._roi_response_dff = self._trace_extractor_read("F_dff")
self._roi_response_neuropil = self._trace_extractor_read("C")
self._roi_response_denoised = self._trace_extractor_read("C")
self._roi_response_neuropil = self._trace_extractor_read("f")
self._roi_response_deconvolved = self._trace_extractor_read("S")
self._image_correlation = self._correlation_image_read()
self._image_mean = self._summary_image_read()
self._sampling_frequency = self._dataset_file["params"]["data"]["fr"][()]
self._image_masks = self._image_mask_sparse_read()
self._background_image_masks = self._background_image_mask_read()

def __del__(self): # TODO: refactor segmentation extractors who use __del__ together into a base class
"""Close the h5py file when the object is deleted."""
Expand Down Expand Up @@ -95,6 +98,19 @@ def _image_mask_sparse_read(self):
image_masks = np.reshape(image_mask_in, (*self.get_image_size(), -1), order="F")
return image_masks

def _background_image_mask_read(self):
"""Read the image masks from the h5py file.

Returns
-------
image_masks: numpy.ndarray
The image masks for each background components.
"""
if self._dataset_file["estimates"].get("b"):
background_image_mask_in = self._dataset_file["estimates"]["b"]
background_image_masks = np.reshape(background_image_mask_in, (*self.get_image_size(), -1), order="F")
return background_image_masks

def _trace_extractor_read(self, field):
"""Read the traces specified by the field from the estimates dataset of the h5py file.

Expand All @@ -113,6 +129,17 @@ def _trace_extractor_read(self, field):
if field in self._dataset_file["estimates"]:
return lazy_ops.DatasetView(self._dataset_file["estimates"][field]).lazy_transpose()

def _raw_trace_extractor_read(self):
"""Read the denoised trace and the residual trace from the h5py file and sum them to obtain the raw roi response trace.

Returns
-------
roi_response_raw: numpy.ndarray
The raw roi response trace.
"""
roi_response_raw = self._dataset_file["estimates"]["C"][:] + self._dataset_file["estimates"]["YrA"][:]
return np.array(roi_response_raw.T)

def _correlation_image_read(self):
"""Read correlation image Cn."""
if self._dataset_file["estimates"].get("Cn"):
Expand Down Expand Up @@ -185,8 +212,10 @@ def write_segmentation(segmentation_object, save_path, overwrite=True):
estimates = f.create_group("estimates")
params = f.create_group("params")
# adding to estimates:
if segmentation_object.get_traces(name="denoised") is not None:
estimates.create_dataset("C", data=segmentation_object.get_traces(name="denoised"))
if segmentation_object.get_traces(name="neuropil") is not None:
estimates.create_dataset("C", data=segmentation_object.get_traces(name="neuropil"))
estimates.create_dataset("f", data=segmentation_object.get_traces(name="neuropil"))
if segmentation_object.get_traces(name="dff") is not None:
estimates.create_dataset("F_dff", data=segmentation_object.get_traces(name="dff"))
if segmentation_object.get_traces(name="deconvolved") is not None:
Expand Down
69 changes: 66 additions & 3 deletions src/roiextractors/segmentationextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SegmentationExtractor
Abstract class that contains all the meta-data and output data from the ROI segmentation operation when applied to
the pre-processed data. It also contains methods to read from and write to various data formats output from the
processing pipelines like SIMA, CaImAn, Suite2p, CNNM-E.
processing pipelines like SIMA, CaImAn, Suite2p, CNMF-E.
FrameSliceSegmentationExtractor
Class to get a lazy frame slice.
"""
Expand All @@ -26,7 +26,7 @@ class SegmentationExtractor(ABC):
An abstract class that contains all the meta-data and output data from
the ROI segmentation operation when applied to the pre-processed data.
It also contains methods to read from and write to various data formats
output from the processing pipelines like SIMA, CaImAn, Suite2p, CNNM-E.
output from the processing pipelines like SIMA, CaImAn, Suite2p, CNMF-E.
All the methods with @abstract decorator have to be defined by the
format specific classes that inherit from this.
"""
Expand All @@ -40,6 +40,7 @@ def __init__(self):
self._roi_response_raw = None
self._roi_response_dff = None
self._roi_response_neuropil = None
self._roi_response_denoised = None
self._roi_response_deconvolved = None
self._image_correlation = None
self._image_mean = None
Expand Down Expand Up @@ -153,6 +154,56 @@ def get_roi_pixel_masks(self, roi_ids=None) -> np.array:

return _pixel_mask_extractor(self.get_roi_image_masks(roi_ids=roi_ids), roi_ids)

def get_background_ids(self) -> list:
"""Get the list of background components ids.

Returns
-------
background_components_ids: list
List of background components ids.
"""
return list(range(self.get_num_background_components()))

def get_background_image_masks(self, background_ids=None) -> np.ndarray:
"""Get the background image masks extracted from segmentation algorithm.

Parameters
----------
background_ids: array_like
A list or 1D array of ids of the background components. Length is the number of background components requested.

Returns
-------
background_image_masks: numpy.ndarray
3-D array(val 0 or 1): image_height X image_width X length(background_ids)
"""
if background_ids is None:
background_ids_ = range(self.get_num_background_components())
else:
all_ids = self.get_background_ids()
background_ids_ = [all_ids.index(i) for i in background_ids]
return np.stack([self._background_image_masks[:, :, k] for k in background_ids_], 2)

def get_background_pixel_masks(self, background_ids=None) -> np.array:
"""Get the weights applied to each of the pixels of the mask.

Parameters
----------
roi_ids: array_like
A list or 1D array of ids of the ROIs. Length is the number of ROIs requested.

Returns
-------
pixel_masks: list
List of length number of rois, each element is a 2-D array with shape (number_of_non_zero_pixels, 3).
Columns 1 and 2 are the x and y coordinates of the pixel, while the third column represents the weight of
the pixel.
"""
if background_ids is None:
background_ids = range(self.get_num_background_components())

return _pixel_mask_extractor(self.get_background_image_masks(background_ids=background_ids), background_ids)

@abstractmethod
def get_image_size(self) -> ArrayType:
"""Get frame size of movie (height, width).
Expand Down Expand Up @@ -223,13 +274,14 @@ def get_traces_dict(self):
-------
_roi_response_dict: dict
dictionary with key, values representing different types of RoiResponseSeries:
Fluorescence, Neuropil, Deconvolved, Background, etc.
Raw Fluorescence, DeltaFOverF, Denoised, Neuropil, Deconvolved, Background, etc.
"""
return dict(
raw=self._roi_response_raw,
dff=self._roi_response_dff,
neuropil=self._roi_response_neuropil,
deconvolved=self._roi_response_deconvolved,
denoised=self._roi_response_denoised,
)

def get_images_dict(self):
Expand Down Expand Up @@ -284,6 +336,17 @@ def get_num_rois(self) -> int:
if trace is not None and len(trace.shape) > 0:
return trace.shape[1]

def get_num_background_components(self) -> int:
"""Get total number of background components in the acquired images.

Returns
-------
num_background_components: int
The number of background components extracted.
"""
if self._roi_response_neuropil is not None and len(self._roi_response_neuropil.shape) > 0:
return self._roi_response_neuropil.shape[1]

def get_channel_names(self) -> List[str]:
"""Get names of channels in the pipeline.

Expand Down
2 changes: 1 addition & 1 deletion src/roiextractors/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ def check_segmentation_return_types(seg: SegmentationExtractor):
)
assert isinstance(seg.get_traces_dict(), dict)
assert isinstance(seg.get_images_dict(), dict)
assert {"raw", "dff", "neuropil", "deconvolved"} == set(seg.get_traces_dict().keys())
assert {"raw", "dff", "neuropil", "deconvolved", "denoised"} == set(seg.get_traces_dict().keys())


def check_imaging_equal(
Expand Down
11 changes: 8 additions & 3 deletions tests/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,19 @@ def test_segmentation_extractors(self, extractor_class, extractor_kwargs):

num_frames = extractor.get_num_frames()
num_rois = extractor.get_num_rois()
num_background_components = extractor.get_num_background_components()
for trace_name, trace in extractor.get_traces_dict().items():
if trace is None:
continue
assert trace.shape[0] == num_frames
assert trace.shape[1] == num_rois

assert extractor.get_traces(name=trace_name).shape[0] == num_frames
assert extractor.get_traces(name=trace_name).shape[1] == num_rois

if trace_name == "neuropil":
assert trace.shape[1] == num_background_components
assert extractor.get_traces(name=trace_name).shape[1] == num_background_components
else:
assert trace.shape[1] == num_rois
assert extractor.get_traces(name=trace_name).shape[1] == num_rois

except NotImplementedError:
return
Expand Down
Loading