From 6c175f2bff60e0c2c2892ce372bc31b07282b8e5 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 7 Oct 2024 16:16:30 +0200 Subject: [PATCH 01/10] add minian extractor --- .../extractors/minian/__init__.py | 14 ++ .../minian/miniansegmentationextractor.py | 170 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 src/roiextractors/extractors/minian/__init__.py create mode 100644 src/roiextractors/extractors/minian/miniansegmentationextractor.py diff --git a/src/roiextractors/extractors/minian/__init__.py b/src/roiextractors/extractors/minian/__init__.py new file mode 100644 index 00000000..4839a679 --- /dev/null +++ b/src/roiextractors/extractors/minian/__init__.py @@ -0,0 +1,14 @@ +"""A Segmentation Extractor for Minian. + +Modules +------- +miniansegmentationextractor + A Segmentation Extractor for Minian. + +Classes +------- +MinianSegmentationExtractor + A class for extracting segmentation from Minian output. +""" + +from .miniansegmentationextractor import MinianSegmentationExtractor diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py new file mode 100644 index 00000000..454cf244 --- /dev/null +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -0,0 +1,170 @@ +"""A SegmentationExtractor for Minian. + +Classes +------- +MinianSegmentationExtractor + A class for extracting segmentation from Minian output. +""" + +from pathlib import Path + +import zarr +import warnings +import numpy as np + +from ...extraction_tools import PathType +from ...segmentationextractor import SegmentationExtractor + + +class MinianSegmentationExtractor(SegmentationExtractor): + """A SegmentationExtractor for Minian. + + This class inherits from the SegmentationExtractor class, having all + its functionality specifically applied to the dataset output from + the 'Minian' ROI segmentation method. + """ + + extractor_name = "MinianSegmentation" + is_writable = True + mode = "file" + + def __init__(self, folder_path: PathType): + """Initialize a MinianSegmentationExtractor instance. + + Parameters + ---------- + folder_path: str + The location of the folder containing minian .zarr output. + """ + SegmentationExtractor.__init__(self) + self.folder_path = folder_path + self._roi_response_denoised = self._trace_extractor_read(field = "C") + self._roi_response_baseline = self._trace_extractor_read(field = "b0") + self._roi_response_neuropil = self._trace_extractor_read(field = "f") + self._roi_response_deconvolved = self._trace_extractor_read(field = "S") + self._image_maximum_projection = self._file_extractor_read("/max_proj.zarr/max_proj") + self._image_masks = self._roi_image_mask_read() + self._background_image_masks = self._background_image_mask_read() + + + def _file_extractor_read(self, zarr_group = ""): + """Read the zarr. + + Returns + ------- + zarr.open + The zarr object specified by self.folder_path. + """ + if zarr_group not in zarr.open(self.folder_path, mode="r"): + warnings.warn(f"Group '{zarr_group}' not found in the Zarr store.", UserWarning) + return None + else: + return zarr.open(self.folder_path + f"/{zarr_group}", "r") + + def _roi_image_mask_read(self): + """Read the image masks from the zarr output. + + Returns + ------- + image_masks: numpy.ndarray + The image masks for each ROI. + """ + dataset = self._file_extractor_read("/A.zarr") + if dataset is None or "A" not in dataset: + return None + else: + return np.transpose(dataset["A"], (1,2,0)) + + def _background_image_mask_read(self): + """Read the image masks from the zarr output. + + Returns + ------- + image_masks: numpy.ndarray + The image masks for each background components. + """ + dataset = self._file_extractor_read("/b.zarr") + if dataset is None or "b" not in dataset: + return None + else: + return np.expand_dims(dataset["b"], axis=2) + + def _trace_extractor_read(self, field): + """Read the traces specified by the field from the zarr object. + + Parameters + ---------- + field: str + The field to read from the zarr object. + + Returns + ------- + trace: numpy.ndarray + The traces specified by the field. + """ + + dataset = self._file_extractor_read(f"/{field}.zarr") + + if dataset is None or field not in dataset: + return None + elif dataset[field].ndim == 2: + return np.transpose(dataset[field]) + elif dataset[field].ndim == 1: + return np.expand_dims(dataset[field], axis=1) + + def get_image_size(self): + dataset = self._file_extractor_read("/A.zarr") + height = dataset["height"].shape[0] + width = dataset["width"].shape[0] + return (height, width) + + def get_accepted_list(self) -> list: + """Get a list of accepted ROI ids. + + Returns + ------- + accepted_list: list + List of accepted ROI ids. + """ + return list(range(self.get_num_rois())) + + def get_rejected_list(self) -> list: + """Get a list of rejected ROI ids. + + Returns + ------- + rejected_list: list + List of rejected ROI ids. + """ + return list() + + def get_roi_ids(self) -> list: + dataset = self._file_extractor_read("/A.zarr") + return list(dataset["unit_id"]) + + def get_traces_dict(self) -> dict: + """Get traces as a dictionary with key as the name of the ROiResponseSeries. + + Returns + ------- + _roi_response_dict: dict + dictionary with key, values representing different types of RoiResponseSeries: + Raw Fluorescence, DeltaFOverF, Denoised, Neuropil, Deconvolved, Background, etc. + """ + return dict( + denoised=self._roi_response_denoised, + baseline=self._roi_response_baseline, + neuropil=self._roi_response_neuropil, + deconvolved=self._roi_response_deconvolved, + ) + + def get_images_dict(self) -> dict: + """Get images as a dictionary with key as the name of the ROIResponseSeries. + + Returns + ------- + _roi_image_dict: dict + dictionary with key, values representing different types of Images used in segmentation: + Mean, Correlation image + """ + return dict(mean=self._image_mean, correlation=self._image_correlation, maximum_projection=self._image_maximum_projection) \ No newline at end of file From 49d377aefd9db539e29baab2402f1058f096d84d Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 7 Oct 2024 16:16:44 +0200 Subject: [PATCH 02/10] add minian extractor to extractorlist --- src/roiextractors/extractorlist.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/roiextractors/extractorlist.py b/src/roiextractors/extractorlist.py index f93c6c29..c1a2e897 100644 --- a/src/roiextractors/extractorlist.py +++ b/src/roiextractors/extractorlist.py @@ -28,6 +28,7 @@ from .extractors.inscopixextractors import InscopixImagingExtractor from .extractors.memmapextractors import NumpyMemmapImagingExtractor from .extractors.memmapextractors import MemmapImagingExtractor +from .extractors.minian import MinianSegmentationExtractor from .extractors.miniscopeimagingextractor import MiniscopeImagingExtractor from .multisegmentationextractor import MultiSegmentationExtractor from .multiimagingextractor import MultiImagingExtractor @@ -62,6 +63,7 @@ ExtractSegmentationExtractor, SimaSegmentationExtractor, CaimanSegmentationExtractor, + MinianSegmentationExtractor, ] imaging_extractor_dict = {imaging_class.extractor_name: imaging_class for imaging_class in imaging_extractor_full_list} From 7e351908485bbf9f511a68dfa98edbfede95def7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 14:25:43 +0000 Subject: [PATCH 03/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../minian/miniansegmentationextractor.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index 454cf244..229473e4 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -38,16 +38,15 @@ def __init__(self, folder_path: PathType): """ SegmentationExtractor.__init__(self) self.folder_path = folder_path - self._roi_response_denoised = self._trace_extractor_read(field = "C") - self._roi_response_baseline = self._trace_extractor_read(field = "b0") - self._roi_response_neuropil = self._trace_extractor_read(field = "f") - self._roi_response_deconvolved = self._trace_extractor_read(field = "S") + self._roi_response_denoised = self._trace_extractor_read(field="C") + self._roi_response_baseline = self._trace_extractor_read(field="b0") + self._roi_response_neuropil = self._trace_extractor_read(field="f") + self._roi_response_deconvolved = self._trace_extractor_read(field="S") self._image_maximum_projection = self._file_extractor_read("/max_proj.zarr/max_proj") self._image_masks = self._roi_image_mask_read() self._background_image_masks = self._background_image_mask_read() - - def _file_extractor_read(self, zarr_group = ""): + def _file_extractor_read(self, zarr_group=""): """Read the zarr. Returns @@ -73,7 +72,7 @@ def _roi_image_mask_read(self): if dataset is None or "A" not in dataset: return None else: - return np.transpose(dataset["A"], (1,2,0)) + return np.transpose(dataset["A"], (1, 2, 0)) def _background_image_mask_read(self): """Read the image masks from the zarr output. @@ -167,4 +166,8 @@ def get_images_dict(self) -> dict: dictionary with key, values representing different types of Images used in segmentation: Mean, Correlation image """ - return dict(mean=self._image_mean, correlation=self._image_correlation, maximum_projection=self._image_maximum_projection) \ No newline at end of file + return dict( + mean=self._image_mean, + correlation=self._image_correlation, + maximum_projection=self._image_maximum_projection, + ) From a7d5a2e72074badedb4af61926f0c681d2a7b669 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Mon, 7 Oct 2024 16:52:54 +0200 Subject: [PATCH 04/10] UPDATE CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f4bfe33..ce044cd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features * Added a seed to dummy generators [#361](https://github.com/catalystneuro/roiextractors/pull/361) * Added depth_slice for VolumetricImagingExtractors [PR #363](https://github.com/catalystneuro/roiextractors/pull/363) +* Added MinianSegmentationExtractor: [PR #368](https://github.com/catalystneuro/roiextractors/pull/368) ### Fixes * Added specific error message for single-frame scanimage data [PR #360](https://github.com/catalystneuro/roiextractors/pull/360) From a96f0638ce564fe6c93c41151c8dc2b7524caca4 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Tue, 8 Oct 2024 10:51:32 +0200 Subject: [PATCH 05/10] add extraction of timestamps --- .../minian/miniansegmentationextractor.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index 229473e4..b7d2931a 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -11,6 +11,7 @@ import zarr import warnings import numpy as np +import pandas as pd from ...extraction_tools import PathType from ...segmentationextractor import SegmentationExtractor @@ -43,8 +44,10 @@ def __init__(self, folder_path: PathType): self._roi_response_neuropil = self._trace_extractor_read(field="f") self._roi_response_deconvolved = self._trace_extractor_read(field="S") self._image_maximum_projection = self._file_extractor_read("/max_proj.zarr/max_proj") + # TODO add extraction of motion correction self._image_masks = self._roi_image_mask_read() self._background_image_masks = self._background_image_mask_read() + self._times = self._timestamps_extractor_read() def _file_extractor_read(self, zarr_group=""): """Read the zarr. @@ -111,6 +114,22 @@ def _trace_extractor_read(self, field): elif dataset[field].ndim == 1: return np.expand_dims(dataset[field], axis=1) + def _timestamps_extractor_read(self): + """ Extract timestamps corresponding to frame numbers of the stored denoised trace + + Returns + ------- + list + The timestamps of the denoised trace. + """ + + csv_file = self.folder_path + "timeStamps.csv" + df = pd.read_csv(csv_file) + frame_numbers = self._file_extractor_read("/C.zarr/frame") + filtered_df = df[df['Frame Number'].isin(frame_numbers)] + + return filtered_df['Time Stamp (ms)'].tolist() + def get_image_size(self): dataset = self._file_extractor_read("/A.zarr") height = dataset["height"].shape[0] From a2010bd0cfae38b53d0aa46364102d47eb6a1e1c Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Wed, 9 Oct 2024 17:22:08 +0200 Subject: [PATCH 06/10] bug fixes --- .../minian/miniansegmentationextractor.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index b7d2931a..dd48edda 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -43,8 +43,7 @@ def __init__(self, folder_path: PathType): self._roi_response_baseline = self._trace_extractor_read(field="b0") self._roi_response_neuropil = self._trace_extractor_read(field="f") self._roi_response_deconvolved = self._trace_extractor_read(field="S") - self._image_maximum_projection = self._file_extractor_read("/max_proj.zarr/max_proj") - # TODO add extraction of motion correction + self._image_maximum_projection = np.array(self._file_extractor_read("/max_proj.zarr/max_proj")) self._image_masks = self._roi_image_mask_read() self._background_image_masks = self._background_image_mask_read() self._times = self._timestamps_extractor_read() @@ -61,7 +60,7 @@ def _file_extractor_read(self, zarr_group=""): warnings.warn(f"Group '{zarr_group}' not found in the Zarr store.", UserWarning) return None else: - return zarr.open(self.folder_path + f"/{zarr_group}", "r") + return zarr.open(str(self.folder_path) + f"/{zarr_group}", "r") def _roi_image_mask_read(self): """Read the image masks from the zarr output. @@ -104,7 +103,6 @@ def _trace_extractor_read(self, field): trace: numpy.ndarray The traces specified by the field. """ - dataset = self._file_extractor_read(f"/{field}.zarr") if dataset is None or field not in dataset: @@ -119,16 +117,15 @@ def _timestamps_extractor_read(self): Returns ------- - list + np.ndarray The timestamps of the denoised trace. """ - - csv_file = self.folder_path + "timeStamps.csv" + csv_file = self.folder_path / "timeStamps.csv" df = pd.read_csv(csv_file) frame_numbers = self._file_extractor_read("/C.zarr/frame") - filtered_df = df[df['Frame Number'].isin(frame_numbers)] + filtered_df = df[df['Frame Number'].isin(frame_numbers)]*1e-3 - return filtered_df['Time Stamp (ms)'].tolist() + return filtered_df['Time Stamp (ms)'].to_numpy() def get_image_size(self): dataset = self._file_extractor_read("/A.zarr") From 9a6321c530730ae98d7fc6341fcf8f0ceddcab91 Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Wed, 9 Oct 2024 17:22:22 +0200 Subject: [PATCH 07/10] add extractor test --- tests/test_miniansegmentationextractor.py | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/test_miniansegmentationextractor.py diff --git a/tests/test_miniansegmentationextractor.py b/tests/test_miniansegmentationextractor.py new file mode 100644 index 00000000..b818d561 --- /dev/null +++ b/tests/test_miniansegmentationextractor.py @@ -0,0 +1,108 @@ +import shutil +import tempfile +from pathlib import Path + +import numpy as np +import zarr +from hdmf.testing import TestCase +from numpy.testing import assert_array_equal + +from roiextractors import MinianSegmentationExtractor +from tests.setup_paths import OPHYS_DATA_PATH + + +class TestMinianSegmentationExtractor(TestCase): + @classmethod + def setUpClass(cls): + folder_path = str(OPHYS_DATA_PATH / "segmentation_datasets" / "minian") + + cls.folder_path = Path(folder_path) + extractor = MinianSegmentationExtractor(folder_path=cls.folder_path) + cls.extractor = extractor + + cls.test_dir = Path(tempfile.mkdtemp()) + + # denoised traces + dataset = zarr.open(folder_path + "/C.zarr") + cls.denoised_traces = np.transpose(dataset['C']) + cls.num_frames = len(dataset['frame'][:]) + # deconvolved traces + dataset = zarr.open(folder_path + "/S.zarr") + cls.deconvolved_traces = np.transpose(dataset['S']) + # baseline traces + dataset = zarr.open(folder_path + "/b0.zarr") + cls.baseline_traces = np.transpose(dataset['b0']) + # neuropil trace + dataset = zarr.open(folder_path + "/f.zarr") + cls.neuropil_trace = np.expand_dims(dataset["f"], axis=1) + + # ROIs masks + dataset = zarr.open(folder_path + "/A.zarr") + cls.image_masks = np.transpose(dataset['A'], (1,2,0)) + cls.image_size = (dataset["height"].shape[0], dataset["width"].shape[0]) + cls.num_rois = dataset["unit_id"].shape[0] + # background mask + dataset = zarr.open(folder_path + "/b.zarr") + cls.background_image_mask = np.expand_dims(dataset["b"], axis=2) + # summary image: maximum projection + cls.maximum_projection_image = np.array(zarr.open(folder_path + "/max_proj.zarr/max_proj")) + + + + @classmethod + def tearDownClass(cls): + # remove the temporary directory and its contents + shutil.rmtree(cls.test_dir) + + def test_incomplete_extractor_load(self): + """Check extractor can be initialized when not all traces are available.""" + # temporary directory for testing assertion when some of the files are missing + folders_to_copy = ["A.zarr","C.zarr","b0.zarr", "b.zarr", "f.zarr", "max_proj.zarr", ".zgroup", "timeStamps.csv"] + self.test_dir.mkdir(exist_ok=True) + + for folder in folders_to_copy: + src = Path(self.folder_path) / folder + dst = self.test_dir / folder + if src.is_dir(): + shutil.copytree(src, dst, dirs_exist_ok=True) + else: + shutil.copy(src, dst) + + extractor = MinianSegmentationExtractor(folder_path=self.test_dir) + traces_dict = extractor.get_traces_dict() + self.assertEqual(traces_dict["deconvolved"], None) + + def test_image_size(self): + self.assertEqual(self.extractor.get_image_size(), self.image_size) + + def test_num_frames(self): + self.assertEqual(self.extractor.get_num_frames(), self.num_frames) + + def test_frame_to_time(self): + self.assertEqual(self.extractor.frame_to_time(frames=[0]), 0.328) + + def test_num_channels(self): + self.assertEqual(self.extractor.get_num_channels(), 1) + + def test_num_rois(self): + self.assertEqual(self.extractor.get_num_rois(), self.num_rois) + + def test_extractor_denoised_traces(self): + assert_array_equal(self.extractor.get_traces(name="denoised"), self.denoised_traces) + + def test_extractor_neuropil_trace(self): + assert_array_equal(self.extractor.get_traces(name="neuropil"), self.neuropil_trace) + + def test_extractor_image_masks(self): + """Test that the image masks are correctly extracted.""" + assert_array_equal(self.extractor.get_roi_image_masks(), self.image_masks) + + def test_extractor_background_image_masks(self): + """Test that the image masks are correctly extracted.""" + assert_array_equal(self.extractor.get_background_image_masks(), self.background_image_mask) + + def test_maximum_projection_image(self): + """Test that the mean image is correctly loaded from the extractor.""" + images_dict = self.extractor.get_images_dict() + assert_array_equal(images_dict["maximum_projection"], self.maximum_projection_image) + From a6873d2ca0388356b9263e664f0b2ca2a6857b82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:22:36 +0000 Subject: [PATCH 08/10] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../minian/miniansegmentationextractor.py | 14 +++++------ tests/test_miniansegmentationextractor.py | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index dd48edda..678bd366 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -113,19 +113,19 @@ def _trace_extractor_read(self, field): return np.expand_dims(dataset[field], axis=1) def _timestamps_extractor_read(self): - """ Extract timestamps corresponding to frame numbers of the stored denoised trace + """Extract timestamps corresponding to frame numbers of the stored denoised trace - Returns - ------- - np.ndarray - The timestamps of the denoised trace. + Returns + ------- + np.ndarray + The timestamps of the denoised trace. """ csv_file = self.folder_path / "timeStamps.csv" df = pd.read_csv(csv_file) frame_numbers = self._file_extractor_read("/C.zarr/frame") - filtered_df = df[df['Frame Number'].isin(frame_numbers)]*1e-3 + filtered_df = df[df["Frame Number"].isin(frame_numbers)] * 1e-3 - return filtered_df['Time Stamp (ms)'].to_numpy() + return filtered_df["Time Stamp (ms)"].to_numpy() def get_image_size(self): dataset = self._file_extractor_read("/A.zarr") diff --git a/tests/test_miniansegmentationextractor.py b/tests/test_miniansegmentationextractor.py index b818d561..14e349c3 100644 --- a/tests/test_miniansegmentationextractor.py +++ b/tests/test_miniansegmentationextractor.py @@ -24,21 +24,21 @@ def setUpClass(cls): # denoised traces dataset = zarr.open(folder_path + "/C.zarr") - cls.denoised_traces = np.transpose(dataset['C']) - cls.num_frames = len(dataset['frame'][:]) + cls.denoised_traces = np.transpose(dataset["C"]) + cls.num_frames = len(dataset["frame"][:]) # deconvolved traces dataset = zarr.open(folder_path + "/S.zarr") - cls.deconvolved_traces = np.transpose(dataset['S']) + cls.deconvolved_traces = np.transpose(dataset["S"]) # baseline traces dataset = zarr.open(folder_path + "/b0.zarr") - cls.baseline_traces = np.transpose(dataset['b0']) + cls.baseline_traces = np.transpose(dataset["b0"]) # neuropil trace dataset = zarr.open(folder_path + "/f.zarr") cls.neuropil_trace = np.expand_dims(dataset["f"], axis=1) # ROIs masks dataset = zarr.open(folder_path + "/A.zarr") - cls.image_masks = np.transpose(dataset['A'], (1,2,0)) + cls.image_masks = np.transpose(dataset["A"], (1, 2, 0)) cls.image_size = (dataset["height"].shape[0], dataset["width"].shape[0]) cls.num_rois = dataset["unit_id"].shape[0] # background mask @@ -47,8 +47,6 @@ def setUpClass(cls): # summary image: maximum projection cls.maximum_projection_image = np.array(zarr.open(folder_path + "/max_proj.zarr/max_proj")) - - @classmethod def tearDownClass(cls): # remove the temporary directory and its contents @@ -57,7 +55,16 @@ def tearDownClass(cls): def test_incomplete_extractor_load(self): """Check extractor can be initialized when not all traces are available.""" # temporary directory for testing assertion when some of the files are missing - folders_to_copy = ["A.zarr","C.zarr","b0.zarr", "b.zarr", "f.zarr", "max_proj.zarr", ".zgroup", "timeStamps.csv"] + folders_to_copy = [ + "A.zarr", + "C.zarr", + "b0.zarr", + "b.zarr", + "f.zarr", + "max_proj.zarr", + ".zgroup", + "timeStamps.csv", + ] self.test_dir.mkdir(exist_ok=True) for folder in folders_to_copy: @@ -105,4 +112,3 @@ def test_maximum_projection_image(self): """Test that the mean image is correctly loaded from the extractor.""" images_dict = self.extractor.get_images_dict() assert_array_equal(images_dict["maximum_projection"], self.maximum_projection_image) - From f038aa0f9865311c8abf8836c71334f3b2d6ba6f Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Thu, 17 Oct 2024 14:48:11 +0200 Subject: [PATCH 09/10] add detailed doc string --- .../minian/miniansegmentationextractor.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index 678bd366..d54a3f3c 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -23,6 +23,21 @@ class MinianSegmentationExtractor(SegmentationExtractor): This class inherits from the SegmentationExtractor class, having all its functionality specifically applied to the dataset output from the 'Minian' ROI segmentation method. + + Users can extract key information such as ROI traces, image masks, + and timestamps from the output of the Minian pipeline. + + Key features: + - Extracts fluorescence traces (denoised, baseline, neuropil, deconvolved) for each ROI. + - Retrieves ROI masks and background components. + - Provides access to timestamps corresponding to calcium traces. + - Retrieves maximum projection image. + + Parameters + ---------- + folder_path: str + Path to the folder containing Minian .zarr output files. + """ extractor_name = "MinianSegmentation" From a56d088fc4a178e7072ba4f06908e52a70eb134b Mon Sep 17 00:00:00 2001 From: alessandratrapani Date: Thu, 17 Oct 2024 14:48:45 +0200 Subject: [PATCH 10/10] make functions more verb-like --- .../minian/miniansegmentationextractor.py | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/roiextractors/extractors/minian/miniansegmentationextractor.py b/src/roiextractors/extractors/minian/miniansegmentationextractor.py index d54a3f3c..6c1e59c0 100644 --- a/src/roiextractors/extractors/minian/miniansegmentationextractor.py +++ b/src/roiextractors/extractors/minian/miniansegmentationextractor.py @@ -54,16 +54,16 @@ def __init__(self, folder_path: PathType): """ SegmentationExtractor.__init__(self) self.folder_path = folder_path - self._roi_response_denoised = self._trace_extractor_read(field="C") - self._roi_response_baseline = self._trace_extractor_read(field="b0") - self._roi_response_neuropil = self._trace_extractor_read(field="f") - self._roi_response_deconvolved = self._trace_extractor_read(field="S") - self._image_maximum_projection = np.array(self._file_extractor_read("/max_proj.zarr/max_proj")) - self._image_masks = self._roi_image_mask_read() - self._background_image_masks = self._background_image_mask_read() - self._times = self._timestamps_extractor_read() - - def _file_extractor_read(self, zarr_group=""): + self._roi_response_denoised = self._read_trace_from_zarr_filed(field="C") + self._roi_response_baseline = self._read_trace_from_zarr_filed(field="b0") + self._roi_response_neuropil = self._read_trace_from_zarr_filed(field="f") + self._roi_response_deconvolved = self._read_trace_from_zarr_filed(field="S") + self._image_maximum_projection = np.array(self._read_zarr_group("/max_proj.zarr/max_proj")) + self._image_masks = self._read_roi_image_mask_from_zarr_filed() + self._background_image_masks = self._read_background_image_mask_from_zarr_filed() + self._times = self._read_timestamps_from_csv() + + def _read_zarr_group(self, zarr_group=""): """Read the zarr. Returns @@ -77,7 +77,7 @@ def _file_extractor_read(self, zarr_group=""): else: return zarr.open(str(self.folder_path) + f"/{zarr_group}", "r") - def _roi_image_mask_read(self): + def _read_roi_image_mask_from_zarr_filed(self): """Read the image masks from the zarr output. Returns @@ -85,13 +85,13 @@ def _roi_image_mask_read(self): image_masks: numpy.ndarray The image masks for each ROI. """ - dataset = self._file_extractor_read("/A.zarr") + dataset = self._read_zarr_group("/A.zarr") if dataset is None or "A" not in dataset: return None else: return np.transpose(dataset["A"], (1, 2, 0)) - def _background_image_mask_read(self): + def _read_background_image_mask_from_zarr_filed(self): """Read the image masks from the zarr output. Returns @@ -99,13 +99,13 @@ def _background_image_mask_read(self): image_masks: numpy.ndarray The image masks for each background components. """ - dataset = self._file_extractor_read("/b.zarr") + dataset = self._read_zarr_group("/b.zarr") if dataset is None or "b" not in dataset: return None else: return np.expand_dims(dataset["b"], axis=2) - def _trace_extractor_read(self, field): + def _read_trace_from_zarr_filed(self, field): """Read the traces specified by the field from the zarr object. Parameters @@ -118,7 +118,7 @@ def _trace_extractor_read(self, field): trace: numpy.ndarray The traces specified by the field. """ - dataset = self._file_extractor_read(f"/{field}.zarr") + dataset = self._read_zarr_group(f"/{field}.zarr") if dataset is None or field not in dataset: return None @@ -127,7 +127,7 @@ def _trace_extractor_read(self, field): elif dataset[field].ndim == 1: return np.expand_dims(dataset[field], axis=1) - def _timestamps_extractor_read(self): + def _read_timestamps_from_csv(self): """Extract timestamps corresponding to frame numbers of the stored denoised trace Returns @@ -137,13 +137,13 @@ def _timestamps_extractor_read(self): """ csv_file = self.folder_path / "timeStamps.csv" df = pd.read_csv(csv_file) - frame_numbers = self._file_extractor_read("/C.zarr/frame") + frame_numbers = self._read_zarr_group("/C.zarr/frame") filtered_df = df[df["Frame Number"].isin(frame_numbers)] * 1e-3 return filtered_df["Time Stamp (ms)"].to_numpy() def get_image_size(self): - dataset = self._file_extractor_read("/A.zarr") + dataset = self._read_zarr_group("/A.zarr") height = dataset["height"].shape[0] width = dataset["width"].shape[0] return (height, width) @@ -169,7 +169,7 @@ def get_rejected_list(self) -> list: return list() def get_roi_ids(self) -> list: - dataset = self._file_extractor_read("/A.zarr") + dataset = self._read_zarr_group("/A.zarr") return list(dataset["unit_id"]) def get_traces_dict(self) -> dict: