Source code for roiextractors.extractors.inscopixextractors.inscopixsegmentationextractor
"""Inscopix Segmentation Extractor."""
import platform
import sys
import warnings
from datetime import datetime
from typing import Any
import numpy as np
from ...extraction_tools import PathType
from ...segmentationextractor import SegmentationExtractor, _ROIMasks
[docs]
class InscopixSegmentationExtractor(SegmentationExtractor):
"""A segmentation extractor for Inscopix."""
extractor_name = "InscopixSegmentationExtractor"
def __init__(self, file_path: PathType):
"""Initialize a InscopixSegmentationExtractor instance.
Main class for extracting segmentation data from Inscopix format.
Parameters
----------
file_path: str or Path
The location of the folder containing Inscopix *.mat output file.
"""
python_version = sys.version_info
if python_version >= (3, 13):
raise ImportError(
"The isx package only supports Python versions 3.9 to 3.13. "
f"Your Python version is {python_version.major}.{python_version.minor}. "
"See https://github.com/inscopix/pyisx for details."
)
if platform.system() == "Darwin" and platform.machine() == "arm64":
raise ImportError(
"The isx package is currently not natively supported on macOS with Apple Silicon. "
"Installation instructions can be found at: "
"https://github.com/inscopix/pyisx?tab=readme-ov-file#install"
)
import isx
SegmentationExtractor.__init__(self)
self._file_path = file_path
file_path_str = str(file_path)
self.cell_set = isx.CellSet.read(file_path_str, read_only=True)
# Get original IDs directly from CellSet
self._roi_ids = [self.cell_set.get_cell_name(x) for x in range(self.cell_set.num_cells)]
# Cache for metadata to avoid repeated extraction
self._metadata_cache = None
# Set sampling frequency
self._sampling_frequency = 1 / self.cell_set.timing.period.secs_float
# Create ROI representations
# Load all masks upfront and store in standard nwb-image_mask format
if self.cell_set.num_cells > 0:
frame_shape = self.get_frame_shape()
# Stack all image masks into (H, W, N) array
all_masks = np.stack([self.cell_set.get_cell_image_data(i) for i in range(self.cell_set.num_cells)], axis=2)
roi_id_map = {roi_id: index for index, roi_id in enumerate(self._roi_ids)}
self._roi_masks = _ROIMasks(
data=all_masks,
mask_tpe="nwb-image_mask",
field_of_view_shape=frame_shape,
roi_id_map=roi_id_map,
)
# Set cell_status as a property for ROI classification
if self.cell_set.num_cells > 0:
cell_status = np.array([self.cell_set.get_cell_status(i) for i in range(self.cell_set.num_cells)])
self.set_property(
"cell_status",
cell_status,
self._roi_ids,
description="Cell status classification from Inscopix",
)
[docs]
def get_roi_ids(self) -> list:
"""Get ROI IDs as original string IDs from the CellSet."""
return self._roi_ids.copy()
[docs]
def get_frame_shape(self) -> tuple[int, int]:
if hasattr(self.cell_set, "spacing"):
# Swap dimensions to return (width, height)
pixels = self.cell_set.spacing.num_pixels
return (pixels[1], pixels[0])
else:
if self.get_num_rois() > 0:
shape = self.cell_set.get_cell_image_data(0).shape
# Swap dimensions to return (width, height)
return (shape[1], shape[0])
raise ValueError("No ROIs found in the segmentation. Unable to determine image size.")
[docs]
def get_accepted_list(self) -> list:
"""Get a list of accepted ROI ids.
Returns
-------
accepted_list: list
List of accepted ROI ids.
"""
warnings.warn(
"get_accepted_list is deprecated and will be removed in May 2026. "
"Use get_property('cell_status', ids) instead to access Inscopix's native classification.",
DeprecationWarning,
stacklevel=2,
)
if self.cell_set.num_cells == 0:
return []
cell_status = self.get_property("cell_status", self.get_roi_ids())
return [roi_id for roi_id, status in zip(self.get_roi_ids(), cell_status) if status == "accepted"]
[docs]
def get_rejected_list(self) -> list:
"""Get a list of rejected ROI ids.
Returns
-------
rejected_list: list
List of rejected ROI ids.
"""
warnings.warn(
"get_rejected_list is deprecated and will be removed in May 2026. "
"Use get_property('cell_status', ids) instead to access Inscopix's native classification.",
DeprecationWarning,
stacklevel=2,
)
if self.cell_set.num_cells == 0:
return []
cell_status = self.get_property("cell_status", self.get_roi_ids())
return [roi_id for roi_id, status in zip(self.get_roi_ids(), cell_status) if status == "rejected"]
[docs]
def get_traces(self, roi_ids=None, start_frame=None, end_frame=None, name="raw") -> np.ndarray:
"""Get traces for the specified ROIs.
Parameters
----------
roi_ids : list or None
List of ROI IDs (can be integers or string IDs)
start_frame : int or None
Start frame index
end_frame : int or None
End frame index
name : str
Name of the trace type
Returns
-------
np.ndarray
Traces for the specified ROIs
"""
if roi_ids is None:
roi_indices = list(range(self.get_num_rois()))
else:
all_roi_ids = self.get_roi_ids()
roi_indices = [all_roi_ids.index(roi_id) for roi_id in roi_ids]
return np.vstack([self.cell_set.get_cell_trace_data(roi_idx)[start_frame:end_frame] for roi_idx in roi_indices])
[docs]
def get_num_samples(self) -> int:
"""Get the number of samples in the recording (duration of recording).
Returns
-------
num_samples: int
Number of samples in the recording.
"""
try:
return self.cell_set.timing.num_samples
except AttributeError:
if self.get_num_rois() > 0:
return len(self.cell_set.get_cell_trace_data(0))
return 0
def _get_session_start_time(self) -> datetime | None:
"""
Get the session start time as a datetime object.
Returns
-------
datetime | None
The session start time if available, otherwise None.
"""
timing = getattr(self.cell_set, "timing", None)
start_time = getattr(timing, "start", None) if timing else None
if not start_time:
return None
return datetime.fromisoformat(str(start_time))
def _get_device_info(self) -> dict:
"""
Get device-specific information including hardware settings and imaging parameters.
Returns
-------
dict
Dictionary containing device information such as microscope type, serial number,
acquisition software version, field of view, exposure time, focus, gain, channel,
efocus, and LED power settings.
"""
acq_info = self.cell_set.get_acquisition_info()
device_info = {}
# Handle case where acquisition info is None (empty cell sets)
if acq_info is None:
return device_info
# Basic device identification
if acq_info.get("Microscope Type"):
device_info["device_name"] = acq_info.get("Microscope Type")
if acq_info.get("Microscope Serial Number"):
device_info["device_serial_number"] = acq_info.get("Microscope Serial Number")
if acq_info.get("Acquisition SW Version"):
device_info["acquisition_software_version"] = acq_info.get("Acquisition SW Version")
# Imaging/acquisition parameters
if hasattr(self.cell_set, "spacing") and self.cell_set.spacing:
device_info["field_of_view_pixels"] = self.cell_set.spacing.num_pixels
# Hardware/optical settings
if acq_info.get("Exposure Time (ms)"):
device_info["exposure_time_ms"] = acq_info.get("Exposure Time (ms)")
if acq_info.get("Microscope Focus"):
device_info["microscope_focus"] = acq_info.get("Microscope Focus")
if acq_info.get("Microscope Gain"):
device_info["microscope_gain"] = acq_info.get("Microscope Gain")
if acq_info.get("channel"):
device_info["channel"] = acq_info.get("channel")
if acq_info.get("efocus"):
device_info["efocus"] = acq_info.get("efocus")
if acq_info.get("Microscope EX LED 1 Power (mw/mm^2)"):
device_info["led_power_1_mw_per_mm2"] = acq_info.get("Microscope EX LED 1 Power (mw/mm^2)")
if acq_info.get("Microscope EX LED 2 Power (mw/mm^2)"):
device_info["led_power_2_mw_per_mm2"] = acq_info.get("Microscope EX LED 2 Power (mw/mm^2)")
return device_info
def _get_subject_info(self) -> dict:
"""
Get subject/animal information from the acquisition metadata.
Returns
-------
dict
Dictionary containing subject information such as animal ID, species, sex, weight,
date of birth, and description.
"""
acq_info = self.cell_set.get_acquisition_info()
subject_info = {}
# Handle case where acquisition info is None (empty cell sets)
if acq_info is None:
return subject_info
if acq_info.get("Animal ID"):
subject_info["animal_id"] = acq_info.get("Animal ID")
if acq_info.get("Animal Species"):
subject_info["species"] = acq_info.get("Animal Species")
if acq_info.get("Animal Sex"):
subject_info["sex"] = acq_info.get("Animal Sex")
if acq_info.get("Animal Weight"):
subject_info["weight"] = acq_info.get("Animal Weight")
if acq_info.get("Animal Date of Birth"):
subject_info["date_of_birth"] = acq_info.get("Animal Date of Birth")
if acq_info.get("Animal Description"):
subject_info["description"] = acq_info.get("Animal Description")
return subject_info
def _get_analysis_info(self) -> dict:
"""
Get analysis method information specific to Inscopix Segmentation.
Returns
-------
dict
Dictionary containing analysis information such as cell identification method and trace units.
"""
acq_info = self.cell_set.get_acquisition_info()
analysis_info = {}
# Handle case where acquisition info is None (empty cell sets)
if acq_info is None:
return analysis_info
if acq_info.get("Cell Identification Method"):
analysis_info["cell_identification_method"] = acq_info.get("Cell Identification Method")
if acq_info.get("Trace Units"):
analysis_info["trace_units"] = acq_info.get("Trace Units")
return analysis_info
def _get_session_info(self) -> dict:
"""
Get session information from the acquisition metadata.
Returns
-------
dict
Dictionary containing session information such as session name, and experimenter name.
"""
info = {}
acq_info = self.cell_set.get_acquisition_info()
# Handle case where acquisition info is None (empty cell sets)
if acq_info is None:
return info
if acq_info.get("Session Name"):
info["session_name"] = acq_info.get("Session Name")
if acq_info.get("Experimenter Name"):
info["experimenter_name"] = acq_info.get("Experimenter Name")
return info
def _get_probe_info(self) -> dict:
"""
Get probe information from the acquisition metadata.
Returns
-------
dict
Dictionary containing probe information such as diameter, flip, length, pitch, rotation, and type.
Only includes fields with non-empty, non-zero, and non-'none' values.
"""
probe_info = {}
acq_info = self.cell_set.get_acquisition_info()
# Handle case where acquisition info is None (empty cell sets)
if acq_info is None:
return probe_info
probe_fields = [
"Probe Diameter (mm)",
"Probe Flip",
"Probe Length (mm)",
"Probe Pitch",
"Probe Rotation (degrees)",
"Probe Type",
]
for field in probe_fields:
value = acq_info.get(field)
# Include value if it's not None, empty string, 0, or string variations of "none"
if value is not None and value != "" and value != 0 and str(value).lower() != "none":
probe_info[field] = value
return probe_info
def _get_metadata(self) -> dict[str, Any]:
"""
Get all available metadata in a single dictionary.
This method consolidates all metadata extraction methods into one call
for convenience when extracting metadata in neuroconv.
Returns
-------
Dict[str, Any]
Dictionary containing all extractable metadata including:
- device: Device/hardware information
- subject: Subject/animal information
- analysis: Analysis method information
- session: Session information
- probe: Probe information
- session_start_time: Measurement start time as datetime
"""
metadata = {}
# Device info
try:
metadata["device"] = self._get_device_info()
except Exception:
metadata["device"] = {}
# Subject info
try:
metadata["subject"] = self._get_subject_info()
except Exception:
metadata["subject"] = {}
# Analysis info
try:
metadata["analysis"] = self._get_analysis_info()
except Exception:
metadata["analysis"] = {}
# Session info
try:
metadata["session"] = self._get_session_info()
except Exception:
metadata["session"] = {}
# Probe info
try:
metadata["probe"] = self._get_probe_info()
except Exception:
metadata["probe"] = {}
# Session start time
try:
metadata["session_start_time"] = self._get_session_start_time()
except Exception:
metadata["session_start_time"] = None
return metadata
[docs]
def get_native_timestamps(
self, start_sample: int | None = None, end_sample: int | None = None
) -> np.ndarray | None:
# Inscopix segmentation data does not have native timestamps
return None