diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b810c03..7e62a546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,12 +6,16 @@ ### Features +* Add InscopixImagingExtractor [#276](https://github.com/catalystneuro/roiextractors/pull/276) * Updated testing workflows to include python 3.12, m1/intel macos, and dev tests to check neuroconv: [PR #317](https://github.com/catalystneuro/roiextractors/pull/317) ### Fixes * Remove unecessary scipy import error handling [#315](https://github.com/catalystneuro/roiextractors/pull/315) +### Testing + +* Updated testing workflows to include python 3.12, m1/intel macos, and dev tests to check neuroconv: [PR #317](https://github.com/catalystneuro/roiextractors/pull/317) * Added daily testing workflow and fixed bug with python 3.12 by upgrading scanimage-tiff-reader version: [PR #321](https://github.com/catalystneuro/roiextractors/pull/321) # v0.5.8 diff --git a/requirements-full.txt b/requirements-full.txt index 5e1ec834..2586b930 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -2,3 +2,4 @@ tifffile>=2018.10.18 scanimage-tiff-reader==1.4.1.4 neuroconv[video]>=0.4.6 # Uses the VideoCaptureContext class natsort>=8.3.1 +isx>=1.0.4 diff --git a/src/roiextractors/extractorlist.py b/src/roiextractors/extractorlist.py index 07ca8239..f93c6c29 100644 --- a/src/roiextractors/extractorlist.py +++ b/src/roiextractors/extractorlist.py @@ -25,6 +25,7 @@ MicroManagerTiffImagingExtractor, ) from .extractors.sbximagingextractor import SbxImagingExtractor +from .extractors.inscopixextractors import InscopixImagingExtractor from .extractors.memmapextractors import NumpyMemmapImagingExtractor from .extractors.memmapextractors import MemmapImagingExtractor from .extractors.miniscopeimagingextractor import MiniscopeImagingExtractor @@ -50,6 +51,7 @@ NumpyMemmapImagingExtractor, MemmapImagingExtractor, VolumetricImagingExtractor, + InscopixImagingExtractor, ] segmentation_extractor_full_list = [ diff --git a/src/roiextractors/extractors/inscopixextractors/__init__.py b/src/roiextractors/extractors/inscopixextractors/__init__.py new file mode 100644 index 00000000..60c67ef7 --- /dev/null +++ b/src/roiextractors/extractors/inscopixextractors/__init__.py @@ -0,0 +1,3 @@ +"""Defines extactors for Inscopix data.""" + +from .inscopiximagingextractor import InscopixImagingExtractor diff --git a/src/roiextractors/extractors/inscopixextractors/inscopiximagingextractor.py b/src/roiextractors/extractors/inscopixextractors/inscopiximagingextractor.py new file mode 100644 index 00000000..28fef2ca --- /dev/null +++ b/src/roiextractors/extractors/inscopixextractors/inscopiximagingextractor.py @@ -0,0 +1,57 @@ +"""Inscopix Imaging Extractor.""" + +import warnings +from typing import Optional, Tuple + +import numpy as np + +from ...imagingextractor import ImagingExtractor +from ...extraction_tools import PathType + + +class InscopixImagingExtractor(ImagingExtractor): + """Extracts imaging data from Inscopix recordings.""" + + extractor_name = "InscopixImaging" + + def __init__(self, file_path: PathType): + """ + Create an InscopixImagingExtractor instance from a single .isx file. + + Parameters + ---------- + file_path : PathType + Path to the Inscopix file. + """ + import isx + + super().__init__(file_path=file_path) + self.movie = isx.Movie.read(str(file_path)) + + def get_image_size(self) -> Tuple[int, int]: + num_pixels = self.movie.spacing.num_pixels + return num_pixels + + def get_num_frames(self) -> int: + return self.movie.timing.num_samples + + def get_sampling_frequency(self) -> float: + return 1 / self.movie.timing.period.secs_float + + def get_channel_names(self) -> list[str]: + warnings.warn("isx only supports single channel videos.") + return ["channel_0"] + + def get_num_channels(self) -> int: + warnings.warn("isx only supports single channel videos.") + return 1 + + def get_video( + self, start_frame: Optional[int] = None, end_frame: Optional[int] = None, channel: int = 0 + ) -> np.ndarray: + start_frame = start_frame or 0 + end_frame = end_frame or self.get_num_frames() + return np.array([self.movie.get_frame_data(i) for i in range(start_frame, end_frame)]) + + def get_dtype(self): + return self.movie.data_type diff --git a/tests/test_inscopiximagingextractor.py b/tests/test_inscopiximagingextractor.py new file mode 100644 index 00000000..fd2064b5 --- /dev/null +++ b/tests/test_inscopiximagingextractor.py @@ -0,0 +1,49 @@ +import numpy as np +from numpy import dtype +from numpy.testing import assert_array_equal + +from roiextractors import InscopixImagingExtractor + +from tests.setup_paths import OPHYS_DATA_PATH + + +def test_inscopiximagingextractor_movie_128x128x100_part1(): + file_path = OPHYS_DATA_PATH / "imaging_datasets" / "inscopix" / "movie_128x128x100_part1.isxd" + extractor = InscopixImagingExtractor(file_path=file_path) + + assert extractor.get_num_frames() == 100 + assert extractor.get_image_size() == (128, 128) + assert extractor.get_dtype() == dtype("float32") + assert extractor.get_sampling_frequency() == 10.0 + assert extractor.get_channel_names() == ["channel_0"] + assert extractor.get_num_channels() == 1 + assert extractor.get_video().shape == (100, 128, 128) + assert extractor.get_frames(frame_idxs=[0], channel=0).dtype == extractor.get_dtype() + + +def test_inscopiximagingextractor_movie_longer_than_3_min(): + file_path = OPHYS_DATA_PATH / "imaging_datasets" / "inscopix" / "movie_longer_than_3_min.isxd" + extractor = InscopixImagingExtractor(file_path=file_path) + + assert extractor.get_num_frames() == 1248 + assert extractor.get_image_size() == (33, 29) + assert extractor.get_dtype() == dtype("uint16") + np.testing.assert_almost_equal(extractor.get_sampling_frequency(), 5.5563890139076415) + assert extractor.get_channel_names() == ["channel_0"] + assert extractor.get_num_channels() == 1 + assert extractor.get_video().shape == (1248, 33, 29) + assert extractor.get_frames(frame_idxs=[0], channel=0).dtype == extractor.get_dtype() + + +def test_inscopiximagingextractor_movie_u8(): + file_path = OPHYS_DATA_PATH / "imaging_datasets" / "inscopix" / "movie_u8.isxd" + extractor = InscopixImagingExtractor(file_path=file_path) + + assert extractor.get_num_frames() == 5 + assert extractor.get_image_size() == (3, 4) + assert extractor.get_dtype() == dtype("uint8") + np.testing.assert_almost_equal(extractor.get_sampling_frequency(), 20.0) + assert extractor.get_channel_names() == ["channel_0"] + assert extractor.get_num_channels() == 1 + assert extractor.get_video().shape == (5, 3, 4) + assert extractor.get_frames(frame_idxs=[0], channel=0).dtype == extractor.get_dtype()