Source code for neuroconv.datainterfaces.ophys.inscopix.inscopiximagingdatainterface

from typing import Optional

from pydantic import FilePath, validate_call

from ..baseimagingextractorinterface import BaseImagingExtractorInterface
from ....utils import DeepDict


[docs] def is_file_multiplane(file_path): """ Hacky check for 'multiplane' keyword in the file. Reads line by line to avoid memory issues with large files. If found, raises NotImplementedError. This is NOT a proper ISX API method—just a string search. """ with open(file_path, "r", encoding="utf-8", errors="ignore") as f: for line in f: if "multiplane" in line: raise NotImplementedError( f"Multiplane ISXD file detected (found 'multiplane' in file).\n" f"This is a hacky check (not an official ISX API method) and may not be robust.\n" f"Proper separation logic is not yet implemented in roiextractors.\n" f"Loading as 2D would result in incorrect data interpretation.\n\n" f"Please open an issue at:\n" f"https://github.com/catalystneuro/roiextractors/issues\n\n" f"Reference: https://github.com/inscopix/pyisx/issues/36" )
[docs] class InscopixImagingInterface(BaseImagingExtractorInterface): """Data Interface for Inscopix Imaging Extractor.""" display_name = "Inscopix Imaging" associated_suffixes = (".isxd",) info = "Interface for handling Inscopix imaging data."
[docs] @classmethod def get_extractor_class(cls): from roiextractors import InscopixImagingExtractor return InscopixImagingExtractor
@validate_call def __init__( self, file_path: FilePath, verbose: bool = False, **kwargs, ): """ Parameters ---------- file_path : FilePath Path to the .isxd Inscopix file. verbose : bool, optional If True, outputs additional information during processing. **kwargs : dict, optional Additional keyword arguments passed to the parent class. Raises ------ NotImplementedError If the file contains multiplane configuration that is not yet supported. """ # Check for multiplane configuration before proceeding is_file_multiplane(file_path) kwargs.setdefault("photon_series_type", "OnePhotonSeries") super().__init__( file_path=file_path, verbose=verbose, **kwargs, )
[docs] def get_metadata(self) -> DeepDict: """ Retrieve the metadata for the Inscopix imaging data. Returns ------- DeepDict Dictionary containing metadata including device information, imaging plane details, photon series configuration, and Inscopix-specific acquisition parameters. """ # Get metadata from parent (already configured for OnePhotonSeries) metadata = super().get_metadata() extractor = self.imaging_extractor extractor_metadata = extractor._get_metadata() # Extract individual components session_info = extractor_metadata.get("session", {}) device_info = extractor_metadata.get("device", {}) subject_info = extractor_metadata.get("subject", {}) session_start_time = extractor_metadata.get("session_start_time") # Session start time if session_start_time: metadata["NWBFile"]["session_start_time"] = session_start_time # Session name and experimenter if session_info.get("session_name"): metadata["NWBFile"]["session_id"] = session_info["session_name"] if session_info.get("experimenter_name"): metadata["NWBFile"]["experimenter"] = [session_info["experimenter_name"]] # Device information if device_info: device_metadata = metadata["Ophys"]["Device"][0] # Update the actual device name if device_info.get("device_name"): device_metadata["name"] = device_info["device_name"] # Build device description microscope_info = [] if device_info.get("device_serial_number"): microscope_info.append(f"Serial: {device_info['device_serial_number']}") if device_info.get("acquisition_software_version"): microscope_info.append(f"Software: {device_info['acquisition_software_version']}") if microscope_info: device_metadata["description"] = f"Inscopix Microscope ({', '.join(microscope_info)})" # Update imaging plane metadata with acquisition details imaging_plane_metadata = metadata["Ophys"]["ImagingPlane"][0] # Update imaging plane device reference to match the actual device name if device_info.get("device_name"): imaging_plane_metadata["device"] = device_info["device_name"] acquisition_details = [] if device_info.get("exposure_time_ms"): acquisition_details.append(f"Exposure Time (ms): {device_info['exposure_time_ms']}") if device_info.get("microscope_gain"): acquisition_details.append(f"Gain: {device_info['microscope_gain']}") if device_info.get("microscope_focus"): acquisition_details.append(f"Focus: {device_info['microscope_focus']}") if device_info.get("efocus"): acquisition_details.append(f"eFocus: {device_info['efocus']}") if device_info.get("led_power_ex_mw_per_mm2"): acquisition_details.append(f"EX LED Power (mw/mm^2): {device_info['led_power_ex_mw_per_mm2']}") if device_info.get("led_power_og_mw_per_mm2"): acquisition_details.append(f"OG LED Power (mw/mm^2): {device_info['led_power_og_mw_per_mm2']}") if acquisition_details: current_description = imaging_plane_metadata.get( "description", "The plane or volume being imaged by the microscope." ) imaging_plane_metadata["description"] = f"{current_description} ({'; '.join(acquisition_details)})" # Subject information subject_metadata = {} has_any_subject_data = False # Subject ID if subject_info and subject_info.get("animal_id"): subject_metadata["subject_id"] = subject_info["animal_id"] has_any_subject_data = True species_value = None strain_value = None if subject_info and subject_info.get("species"): species_raw = subject_info["species"] # If it contains genotype info or matches NWB format, put it in species; otherwise strain # e.g., "CaMKIICre" if " " in species_raw and species_raw[0].isupper() and species_raw.split()[1][0].islower(): species_value = species_raw else: strain_value = species_raw if species_value: subject_metadata["species"] = species_value has_any_subject_data = True if strain_value: subject_metadata["strain"] = strain_value has_any_subject_data = True # Sex mapping sex_mapping = {"m": "M", "male": "M", "f": "F", "female": "F", "u": "U", "unknown": "U"} if subject_info and subject_info.get("sex"): mapped_sex = sex_mapping.get(subject_info["sex"].lower(), "U") subject_metadata["sex"] = mapped_sex has_any_subject_data = True # Additional subject fields if subject_info: if subject_info.get("description"): subject_metadata["description"] = subject_info["description"] has_any_subject_data = True if subject_info.get("date_of_birth"): subject_metadata["date_of_birth"] = subject_info["date_of_birth"] has_any_subject_data = True if subject_info.get("weight") and subject_info["weight"] > 0: subject_metadata["weight"] = str(subject_info["weight"]) has_any_subject_data = True # Add Subject if we have ANY subject information, filling required fields with defaults if has_any_subject_data: if "subject_id" not in subject_metadata: subject_metadata["subject_id"] = "Unknown" if "species" not in subject_metadata: subject_metadata["species"] = "Unknown species" if "sex" not in subject_metadata: subject_metadata["sex"] = "U" if "Subject" in metadata: metadata["Subject"].update(subject_metadata) else: metadata["Subject"] = subject_metadata return metadata
[docs] def add_to_nwbfile( self, nwbfile, metadata: Optional[dict] = None, **kwargs, ): """ Add the Inscopix data to the NWB file. Parameters ---------- nwbfile : NWBFile NWB file to add the data to. metadata : dict, optional Metadata dictionary. If None, will be generated dynamically with OnePhotonSeries. **kwargs Additional keyword arguments passed to the parent add_to_nwbfile method. # TODO: add logic for determining whether the microscope is `nVista 2P` and change photon_series_type to TwoPhotonSeries accordingly. """ kwargs["photon_series_type"] = "OnePhotonSeries" # TODO: add logic for determining whether the microscope is `nVista 2P` and change photon_series_type to TwoPhotonSeries accordingly. super().add_to_nwbfile(nwbfile=nwbfile, metadata=metadata, **kwargs)