diff --git a/src/cai_lab_to_nwb/zaki_2024/interfaces/__init__.py b/src/cai_lab_to_nwb/zaki_2024/interfaces/__init__.py index 9ea686e..4babfbc 100644 --- a/src/cai_lab_to_nwb/zaki_2024/interfaces/__init__.py +++ b/src/cai_lab_to_nwb/zaki_2024/interfaces/__init__.py @@ -4,3 +4,4 @@ from .zaki_2024_sleep_classification_interface import Zaki2024SleepClassificationInterface from .miniscope_imaging_interface import MiniscopeImagingInterface from .zaki_2024_shock_stimuli_interface import Zaki2024ShockStimuliInterface +from .zaki_2024_cell_registration_interface import Zaki2024CellRegistrationInterface diff --git a/src/cai_lab_to_nwb/zaki_2024/interfaces/zaki_2024_cell_registration_interface.py b/src/cai_lab_to_nwb/zaki_2024/interfaces/zaki_2024_cell_registration_interface.py new file mode 100644 index 0000000..f018ed1 --- /dev/null +++ b/src/cai_lab_to_nwb/zaki_2024/interfaces/zaki_2024_cell_registration_interface.py @@ -0,0 +1,72 @@ +"""Primary class for converting experiment-specific cell registration output.""" + +from neuroconv.basedatainterface import BaseDataInterface +from neuroconv.utils import DeepDict +from typing import Optional +from pathlib import Path +from pynwb import NWBFile +from hdmf.common.table import DynamicTable, VectorData + +import pandas as pd + + +class Zaki2024CellRegistrationInterface(BaseDataInterface): + """Adds a table to store the output of CellReg.""" + + keywords = ["cross sessions cell registration"] + + def __init__(self, file_paths: list[Path], verbose: bool = False): + + self.verbose = verbose + self.file_paths = file_paths + super().__init__(file_paths=file_paths) + + def get_metadata(self) -> DeepDict: + # Automatically retrieve as much metadata as possible from the source files available + metadata = super().get_metadata() + + return metadata + + def add_to_nwbfile( + self, + nwbfile: NWBFile, + subject_id: str, + stub_test: bool = False, + metadata: Optional[dict] = None, + ): + processing_module = nwbfile.create_processing_module( + name="cell_registration", + description="Processing module for cross session cell registration. " + "Cells recorded across sessions were cross-registered using a previously published open-source " + "cross-registration algorithm, CellReg, using the spatial correlations of nearby cells to " + "determine whether highly correlated footprints close in space are likely to be the same cell across sessions." + "Each offline recording was cross-registered with all the encoding and recall sessions, " + "but not with the other offline sessions because cross-registering between all sessions would lead to too many conflicts and, " + "therefore, to no cells cross-registered across all sessions." + "Each table represents the output of the cross-registration between one offline sessions and all the encoding and recall sessions. " + "A table maps the global ROI ids (row of the table) to the ROI ids in each of cross-registered session's plane segmentation.", + ) + + for file_path in self.file_paths: + offline_session_name = Path(file_path).stem.split(f"{subject_id}_")[-1] + name = offline_session_name + "vsConditioningSessions" + data = pd.read_csv(file_path) + + columns = [ + VectorData( + name=col, + description=f"ROI indexes of plane segmentation of session {col}", + data=data[col].tolist()[:100] if stub_test else data[col].tolist(), + ) + for col in data.columns + ] + + dynamic_table = DynamicTable( + name=name, + description="Table maps the global ROI ids (row of the table) to the ROI ids in each of cross-registered session's plane segmentation." + "The column names refer to the cross-registered session's ids" + "The values -9999 indicates no correspondence. ", + columns=columns, + ) + + processing_module.add(dynamic_table) diff --git a/src/cai_lab_to_nwb/zaki_2024/utils/cell_registration.py b/src/cai_lab_to_nwb/zaki_2024/utils/cell_registration.py deleted file mode 100644 index 76b1406..0000000 --- a/src/cai_lab_to_nwb/zaki_2024/utils/cell_registration.py +++ /dev/null @@ -1,44 +0,0 @@ -from neuroconv.tools import get_module -from pynwb import NWBFile -import pandas as pd -from roiextractors.extraction_tools import PathType - - -def add_cell_registration( - nwbfile: NWBFile, - global_roi_ids: list, - plane_segmentation_name: str, -) -> None: - """Add cell registration data to the NWBFile. - - The global roi ids for the segmentation data (identified by 'plane_segmentation_name' are added to the NWBFile as - an extra column of the PlaneSegmentation table). - - Parameters - ---------- - nwbfile: NWBFile - The NWBFile where the motion correction time series will be added to. - global_roi_ids: list - global roi ids for the segmentation data. - plane_segmentation_name: str - The name of the plane segmentation table in the NWBFile. - """ - ophys = get_module(nwbfile, "ophys") - assert ( - plane_segmentation_name in ophys["ImageSegmentation"].plane_segmentations.keys() - ), f"The plane segmentation '{plane_segmentation_name}' does not exist in the NWBFile." - - plane_segmentation = ophys["ImageSegmentation"][plane_segmentation_name] - plane_segmentation.add_column( - name="global_ids", - description="list of global ids of identified cells registered cross sessions", - data=global_roi_ids, - ) - - -def get_global_ids_from_csv( - file_path: PathType, - session_id: str, -): - df = pd.read_csv(file_path) - # TODO discuss with Joe how to identify global ids diff --git a/src/cai_lab_to_nwb/zaki_2024/zaki_2024_convert_week_session.py b/src/cai_lab_to_nwb/zaki_2024/zaki_2024_convert_week_session.py index 94c5e92..77a4f22 100644 --- a/src/cai_lab_to_nwb/zaki_2024/zaki_2024_convert_week_session.py +++ b/src/cai_lab_to_nwb/zaki_2024/zaki_2024_convert_week_session.py @@ -3,10 +3,9 @@ import time from natsort import natsorted from pathlib import Path +import warnings from typing import Union -from datetime import datetime, timedelta -import pandas as pd -import json +import re from neuroconv.utils import load_dict_from_file, dict_deep_update from zaki_2024_nwbconverter import Zaki2024NWBConverter @@ -49,6 +48,23 @@ def session_to_nwb( ) conversion_options.update(dict(MultiEDFSignals=dict(stub_test=stub_test))) + # Add Cross session cell registration + main_folder = data_dir_path / f"Ca_EEG_Calcium/{subject_id}/SpatialFootprints" + pattern = re.compile(r"^CellRegResults_OfflineDay(\d+)Session(\d+)$") + + file_paths = [] + for folder in main_folder.iterdir(): + match = pattern.match(folder.name) + if folder.is_dir() and match: + offline_day, session_number = match.groups() + filename = f"CellRegResults_{subject_id}_OfflineDay{offline_day}Session{session_number}.csv" + csv_file = folder / filename + assert csv_file.is_file(), f"Expected file not found: {csv_file}" + file_paths.append(csv_file) + + source_data.update(dict(CellRegistration=dict(file_paths=file_paths))) + conversion_options.update(dict(CellRegistration=dict(stub_test=stub_test, subject_id=subject_id))) + converter = Zaki2024NWBConverter(source_data=source_data) # Add datetime to conversion diff --git a/src/cai_lab_to_nwb/zaki_2024/zaki_2024_nwbconverter.py b/src/cai_lab_to_nwb/zaki_2024/zaki_2024_nwbconverter.py index 8ef2c16..90c5712 100644 --- a/src/cai_lab_to_nwb/zaki_2024/zaki_2024_nwbconverter.py +++ b/src/cai_lab_to_nwb/zaki_2024/zaki_2024_nwbconverter.py @@ -1,11 +1,7 @@ """Primary NWBConverter class for this dataset.""" -from copy import deepcopy - from neuroconv import NWBConverter -from neuroconv.utils import DeepDict from neuroconv.datainterfaces import VideoInterface -from typing import Dict from interfaces import ( MinianSegmentationInterface, @@ -16,6 +12,7 @@ MiniscopeImagingInterface, MinianMotionCorrectionInterface, Zaki2024ShockStimuliInterface, + Zaki2024CellRegistrationInterface, ) @@ -32,41 +29,5 @@ class Zaki2024NWBConverter(NWBConverter): FreezingBehavior=EzTrackFreezingBehaviorInterface, Video=VideoInterface, ShockStimuli=Zaki2024ShockStimuliInterface, + CellRegistration=Zaki2024CellRegistrationInterface, ) - - -""" - # TODO decide which datastream set the session start time - def get_metadata(self) -> DeepDict: - if "" not in self.data_interface_objects: - return super().get_metadata() - - # Explicitly set session_start_time to ... start time - metadata = super().get_metadata() - session_start_time = self.data_interface_objects[""] - metadata["NWBFile"]["session_start_time"] = session_start_time - - return metadata - - # TODO Add cell global_ids - def add_to_nwbfile(self, nwbfile: NWBFile, metadata, conversion_options: Optional[dict] = None) -> None: - super().add_to_nwbfile(nwbfile=nwbfile, metadata=metadata, conversion_options=conversion_options) - - if "MinianSegmentation" in self.data_interface_objects: - global_roi_ids = get_global_ids_from_csv() - add_cell_registration( - nwbfile=nwbfile, - global_roi_ids=global_roi_ids, - plane_segmentation_name="PlaneSegmentation", - ) - - # TODO discuss time alignment with author - def temporally_align_data_interfaces(self): - aligned_starting_time = 0 - if "MiniscopeImaging" in self.data_interface_classes: - miniscope_interface = self.data_interface_classes["MiniscopeImaging"] - miniscope_interface.set_aligned_starting_time(aligned_starting_time=aligned_starting_time) - if "MinianSegmentation" in self.data_interface_classes: - minian_interface = self.data_interface_classes["MinianSegmentation"] - minian_interface.set_aligned_starting_time(aligned_starting_time=aligned_starting_time) -"""