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

Multi-Plane and Multi-Channel Support for ScanImage #241

Closed
wants to merge 49 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
d3f25b8
added multi-plane support to scanimage (first draft)
pauladkisson Sep 14, 2023
a2e0caf
switched extract_extra_metadata to local implementation to avoid neur…
pauladkisson Sep 14, 2023
882cf4c
Refactored ScanImage to support multi-channel and multi-plane directl…
pauladkisson Sep 14, 2023
8dd316b
Removed informal multiplane scanimage test
pauladkisson Sep 14, 2023
53634a0
Check that ScanImageReader works in CI tests
pauladkisson Sep 15, 2023
ce69ecd
Check that ScanImageReader works in CI tests
pauladkisson Sep 15, 2023
5b8fe3c
fixed local tests and removed unnecessary io check
pauladkisson Sep 15, 2023
be21dd7
setup informal test for newer ScanImageData
pauladkisson Sep 15, 2023
4819be2
refactored init to manually accept args rather than automatically par…
pauladkisson Sep 15, 2023
e84b6ad
refactored to take plane and channel at __init__ rather than in get_v…
pauladkisson Sep 18, 2023
243c168
refactored to take plane and channel at __init__ rather than in get_v…
pauladkisson Sep 18, 2023
8e407e8
added multi-plane extractor
pauladkisson Sep 18, 2023
b04baab
refactored out generic MultiPlaneImagingExtractor into its own class
pauladkisson Sep 19, 2023
b1cb564
added extra info to docstrings
pauladkisson Sep 19, 2023
380dd63
fixed bugs with multi-imaging extractor
pauladkisson Sep 19, 2023
d921342
Added depth to get_image_size to allow compatibility with MultiImagin…
pauladkisson Sep 19, 2023
3b65bf8
Refactored new ScanImage --> ScanImageTiffSinglePlaneIE and keep lega…
pauladkisson Sep 19, 2023
84c1946
Added legacy message to old ScanImageTiff
pauladkisson Sep 19, 2023
c3fcfea
Added legacy metadata parsing function
pauladkisson Sep 19, 2023
80ec40c
Added deprecation warning to legacy scanimage extractor
pauladkisson Sep 19, 2023
bc4fafe
Added deprecation warning to legacy scanimage extractor
pauladkisson Sep 19, 2023
1965be8
removed redundant metadata/input validation in ScanImageTiffMultiPlan…
pauladkisson Sep 19, 2023
c160d05
changed multiple frames per slice to warning to allow use of existing…
pauladkisson Sep 19, 2023
d6fe0a5
inital test setup
pauladkisson Sep 19, 2023
76f8485
added tests for new ScanImage stuff
pauladkisson Sep 20, 2023
36f43f6
added note about version support for parse_metadata
pauladkisson Sep 20, 2023
67ab8cb
added note about version support for extract_extra_metadata
pauladkisson Sep 20, 2023
41bf93b
channel --> channel_name
pauladkisson Sep 20, 2023
370341b
remove Optional for int plane
pauladkisson Sep 20, 2023
9831e5d
plane --> plane_name
pauladkisson Sep 21, 2023
c410cac
remove temp test
pauladkisson Sep 21, 2023
ee4dc1a
Update tests/test_scanimagetiffimagingextractor.py
pauladkisson Sep 21, 2023
a36e753
added input validation tests to get_frames and get_video
pauladkisson Sep 21, 2023
bd1070d
removed redundant get_channel_names
pauladkisson Sep 22, 2023
cbb1e03
temp removed get_channel_names from consistency checks AND fixed bug …
pauladkisson Sep 22, 2023
3bc556d
updated parse_metadata docstring
pauladkisson Sep 22, 2023
36105b9
added support for multiple frames per slice
pauladkisson Sep 22, 2023
dc54a03
revamped tests with new data (get_video still has some problems)
pauladkisson Sep 23, 2023
72f21c4
fixed bug in get_video for multiple frames per slice
pauladkisson Sep 25, 2023
663b819
optimized indexing to only i/o the necessary slice
pauladkisson Sep 25, 2023
36f9394
clean up code for readability
pauladkisson Sep 25, 2023
ac6a7c0
fixed typo in get_video
pauladkisson Sep 25, 2023
3f7d0d3
updated test filename
pauladkisson Sep 26, 2023
849b20c
Merge branch 'main' into 3DScanImage
pauladkisson Sep 26, 2023
cabfa84
added _times to account for variable sampling rates (multiple frames …
pauladkisson Sep 26, 2023
e6f4496
Switched sampling_frequency to scanFrameRate instead of scanVolumeRate
pauladkisson Sep 26, 2023
22aa9ec
Refactored stand-alone functions to separate scanimage_utils module
pauladkisson Sep 26, 2023
5f3980e
Added tests for scanimage utils
pauladkisson Sep 26, 2023
1289b70
Merge branch 'main' into 3DScanImage
pauladkisson Sep 28, 2023
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
4 changes: 4 additions & 0 deletions src/roiextractors/extractorlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from .extractors.tiffimagingextractors import (
TiffImagingExtractor,
ScanImageTiffImagingExtractor,
ScanImageTiffSinglePlaneImagingExtractor,
ScanImageTiffMultiPlaneImagingExtractor,
BrukerTiffMultiPlaneImagingExtractor,
BrukerTiffSinglePlaneImagingExtractor,
MicroManagerTiffImagingExtractor,
Expand All @@ -31,6 +33,8 @@
Hdf5ImagingExtractor,
TiffImagingExtractor,
ScanImageTiffImagingExtractor,
ScanImageTiffSinglePlaneImagingExtractor,
ScanImageTiffMultiPlaneImagingExtractor,
BrukerTiffMultiPlaneImagingExtractor,
BrukerTiffSinglePlaneImagingExtractor,
MicroManagerTiffImagingExtractor,
Expand Down
12 changes: 10 additions & 2 deletions src/roiextractors/extractors/tiffimagingextractors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
TiffImagingExtractor
A ImagingExtractor for TIFF files.
ScanImageTiffImagingExtractor
Specialized extractor for reading TIFF files produced via ScanImage.
Legacy extractor for reading TIFF files produced via ScanImage v3.8.
ScanImageTiffSinglePlaneImagingExtractor
Specialized extractor for reading single-plane TIFF files produced via ScanImage.
ScanImageTiffMultiPlaneImagingExtractor
Specialized extractor for reading multi-plane TIFF files produced via ScanImage.
BrukerTiffMultiPlaneImagingExtractor
Specialized extractor for reading TIFF files produced via Bruker.
BrukerTiffSinglePlaneImagingExtractor
Expand All @@ -25,6 +29,10 @@
Specialized extractor for reading TIFF files produced via Micro-Manager.
"""
from .tiffimagingextractor import TiffImagingExtractor
from .scanimagetiffimagingextractor import ScanImageTiffImagingExtractor
from .scanimagetiffimagingextractor import (
ScanImageTiffImagingExtractor,
ScanImageTiffMultiPlaneImagingExtractor,
ScanImageTiffSinglePlaneImagingExtractor,
)
from .brukertiffimagingextractor import BrukerTiffMultiPlaneImagingExtractor, BrukerTiffSinglePlaneImagingExtractor
from .micromanagertiffimagingextractor import MicroManagerTiffImagingExtractor
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import numpy as np

from ...extraction_tools import PathType, get_package


def _get_scanimage_reader() -> type:
"""Import the scanimage-tiff-reader package and return the ScanImageTiffReader class."""
return get_package(
package_name="ScanImageTiffReader", installation_instructions="pip install scanimage-tiff-reader"
).ScanImageTiffReader


def extract_extra_metadata(
file_path: PathType,
) -> dict: # TODO: Refactor neuroconv to reference this implementation to avoid duplication
"""Extract metadata from a ScanImage TIFF file.

Parameters
----------
file_path : PathType
Path to the TIFF file.

Returns
-------
extra_metadata: dict
Dictionary of metadata extracted from the TIFF file.

Notes
-----
Known to work on SI versions v3.8.0, v2019bR0, v2022.0.0, and v2023.0.0
"""
ScanImageTiffReader = _get_scanimage_reader()
io = ScanImageTiffReader(str(file_path))
extra_metadata = {}
for metadata_string in (io.description(iframe=0), io.metadata()):
metadata_dict = {
x.split("=")[0].strip(): x.split("=")[1].strip()
for x in metadata_string.replace("\n", "\r").split("\r")
if "=" in x
}
extra_metadata = dict(**extra_metadata, **metadata_dict)
return extra_metadata


def parse_matlab_vector(matlab_vector: str) -> list:
"""Parse a MATLAB vector string into a list of integer values.

Parameters
----------
matlab_vector : str
MATLAB vector string.

Returns
-------
vector: list of int
List of integer values.

Raises
------
ValueError
If the MATLAB vector string cannot be parsed.

Notes
-----
MATLAB vector string is of the form "[1 2 3 ... N]" or "[1,2,3,...,N]" or "[1;2;3;...;N]".
There may or may not be whitespace between the values. Ex. "[1, 2, 3]" or "[1,2,3]".
"""
vector = matlab_vector.strip("[]")
if ";" in vector:
vector = vector.split(";")
elif "," in vector:
vector = vector.split(",")
elif " " in vector:
vector = vector.split(" ")
elif len(vector) == 1:
pass
else:
raise ValueError(f"Could not parse vector from {matlab_vector}.")
vector = [int(x.strip()) for x in vector if x != ""]
return vector


def parse_metadata(metadata: dict) -> dict:
"""Parse metadata dictionary to extract relevant information and store it standard keys for ImagingExtractors.

Currently supports
- sampling_frequency
- num_planes
- frames_per_slice
- channel_names
- num_channels

Parameters
----------
metadata : dict
Dictionary of metadata extracted from the TIFF file.

Returns
-------
metadata_parsed: dict
Dictionary of parsed metadata.

Notes
-----
Known to work on SI versions v2019bR0, v2022.0.0, and v2023.0.0. Fails on v3.8.0.
SI.hChannels.channelsActive = string of MATLAB-style vector with channel integers (see parse_matlab_vector).
SI.hChannels.channelName = "{'channel_name_1' 'channel_name_2' ... 'channel_name_M'}"
where M is the number of channels (active or not).
"""
sampling_frequency = float(metadata["SI.hRoiManager.scanFrameRate"])
num_planes = int(metadata["SI.hStackManager.numSlices"])
frames_per_slice = int(metadata["SI.hStackManager.framesPerSlice"])
active_channels = parse_matlab_vector(metadata["SI.hChannels.channelsActive"])
channel_indices = np.array(active_channels) - 1 # Account for MATLAB indexing
channel_names = np.array(metadata["SI.hChannels.channelName"].split("'")[1::2])
channel_names = channel_names[channel_indices].tolist()
num_channels = len(channel_names)
metadata_parsed = dict(
sampling_frequency=sampling_frequency,
num_channels=num_channels,
num_planes=num_planes,
frames_per_slice=frames_per_slice,
channel_names=channel_names,
)
return metadata_parsed


def parse_metadata_v3_8(metadata: dict) -> dict:
"""Parse metadata dictionary to extract relevant information and store it standard keys for ImagingExtractors.

Requires old version of metadata (v3.8).
Currently supports
- sampling frequency
- num_channels
- num_planes

Parameters
----------
metadata : dict
Dictionary of metadata extracted from the TIFF file.

Returns
-------
metadata_parsed: dict
Dictionary of parsed metadata.
"""
sampling_frequency = float(metadata["state.acq.frameRate"])
num_channels = int(metadata["state.acq.numberOfChannelsSave"])
num_planes = int(metadata["state.acq.numberOfZSlices"])
metadata_parsed = dict(
sampling_frequency=sampling_frequency,
num_channels=num_channels,
num_planes=num_planes,
)
return metadata_parsed


def extract_timestamps_from_file(file_path: PathType) -> np.ndarray:
"""Extract the frame timestamps from a ScanImage TIFF file.

Parameters
----------
file_path : PathType
Path to the TIFF file.

Returns
-------
timestamps : numpy.ndarray
Array of frame timestamps in seconds.

Raises
------
AssertionError
If the frame timestamps are not found in the TIFF file.

Notes
-----
Known to work on SI versions v2019bR0, v2022.0.0, and v2023.0.0. Fails on v3.8.0.
"""
ScanImageTiffReader = _get_scanimage_reader()
io = ScanImageTiffReader(str(file_path))
assert "frameTimestamps_sec" in io.description(iframe=0), "frameTimestamps_sec not found in TIFF file"
num_frames = io.shape()[0]
timestamps = np.zeros(num_frames)
for iframe in range(num_frames):
description = io.description(iframe=iframe)
description_lines = description.split("\n")
for line in description_lines:
if "frameTimestamps_sec" in line:
timestamps[iframe] = float(line.split("=")[1].strip())
break

return timestamps
Loading
Loading