-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #92 from catalystneuro/adjust_raw_only
Adjust raw file structure
- Loading branch information
Showing
6 changed files
with
203 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
from pathlib import Path | ||
|
||
from neuroconv.datainterfaces import SpikeGLXRecordingInterface | ||
from one.api import ONE | ||
|
||
from ibl_to_nwb.converters import BrainwideMapConverter | ||
from ibl_to_nwb.datainterfaces import RawVideoInterface | ||
|
||
session_id = "d32876dd-8303-4720-8e7e-20678dc2fd71" | ||
|
||
# Specify the revision of the pose estimation data | ||
# Setting to 'None' will use whatever the latest released revision is | ||
revision = None | ||
|
||
base_path = Path("E:/IBL") | ||
base_path.mkdir(exist_ok=True) | ||
nwbfiles_folder_path = base_path / "nwbfiles" | ||
nwbfiles_folder_path.mkdir(exist_ok=True) | ||
|
||
# Initialize IBL (ONE) client to download processed data for this session | ||
one_cache_folder_path = base_path / "cache" | ||
ibl_client = ONE( | ||
base_url="https://openalyx.internationalbrainlab.org", | ||
password="international", | ||
silent=True, | ||
cache_dir=one_cache_folder_path, | ||
) | ||
|
||
# Specify the path to the SpikeGLX files on the server | ||
probe_1_source_folder_path = Path("D:/example_data/ephy_testing_data/spikeglx/Noise4Sam_g0") | ||
probe_2_source_folder_path = Path( | ||
"D:/example_data/ephy_testing_data/spikeglx/multi_trigger_multi_gate/SpikeGLX/5-19-2022-CI0/5-19-2022-CI0_g0/" | ||
) | ||
|
||
ap_1_file_path = probe_1_source_folder_path / "Noise4Sam_g0_imec0/Noise4Sam_g0_t0.imec0.ap.bin" | ||
ap_2_file_path = probe_2_source_folder_path / "5-19-2022-CI0_g0_imec0/5-19-2022-CI0_g0_t0.imec0.ap.bin" | ||
|
||
lf_1_file_path = probe_1_source_folder_path / "Noise4Sam_g0_imec0/Noise4Sam_g0_t0.imec0.lf.bin" | ||
lf_2_file_path = probe_2_source_folder_path / "5-19-2022-CI0_g0_imec0/5-19-2022-CI0_g0_t0.imec0.lf.bin" | ||
|
||
# Initialize interfaces | ||
data_interfaces = list() | ||
data_interfaces.append(SpikeGLXRecordingInterface(file_path=ap_1_file_path)) | ||
data_interfaces.append(SpikeGLXRecordingInterface(file_path=ap_2_file_path)) | ||
data_interfaces.append(SpikeGLXRecordingInterface(file_path=lf_1_file_path)) | ||
data_interfaces.append(SpikeGLXRecordingInterface(file_path=lf_2_file_path)) | ||
|
||
# Raw video take some special handling | ||
metadata_retrieval = BrainwideMapConverter(one=ibl_client, session=session_id, data_interfaces=[], verbose=False) | ||
subject_id = metadata_retrieval.get_metadata()["Subject"]["subject_id"] | ||
|
||
pose_estimation_files = ibl_client.list_datasets(eid=session_id, filename="*.dlc*") | ||
for pose_estimation_file in pose_estimation_files: | ||
camera_name = pose_estimation_file.replace("alf/_ibl_", "").replace(".dlc.pqt", "") | ||
|
||
video_interface = RawVideoInterface( | ||
nwbfiles_folder_path=nwbfiles_folder_path, | ||
subject_id=subject_id, | ||
one=ibl_client, | ||
session=session_id, | ||
camera_name=camera_name, | ||
) | ||
data_interfaces.append(video_interface) | ||
|
||
# Run conversion | ||
session_converter = BrainwideMapConverter( | ||
one=ibl_client, session=session_id, data_interfaces=data_interfaces, verbose=False | ||
) | ||
|
||
metadata = session_converter.get_metadata() | ||
subject_id = metadata["Subject"]["subject_id"] | ||
|
||
subject_folder_path = nwbfiles_folder_path / f"sub-{subject_id}" | ||
subject_folder_path.mkdir(exist_ok=True) | ||
nwbfile_path = subject_folder_path / f"sub-{subject_id}_ses-{session_id}_desc-raw_ecephys+image.nwb" | ||
|
||
session_converter.run_conversion( | ||
nwbfile_path=nwbfile_path, | ||
metadata=metadata, | ||
overwrite=True, | ||
) | ||
|
||
# TODO: add some kind of raw-specific check | ||
# check_written_nwbfile_for_consistency(one=ibl_client, nwbfile_path=nwbfile_path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
from shutil import copyfile | ||
from typing import Literal | ||
|
||
from neuroconv.basedatainterface import BaseDataInterface | ||
from one.api import ONE | ||
from pydantic import DirectoryPath | ||
from pynwb import NWBFile | ||
from pynwb.image import ImageSeries | ||
|
||
|
||
class RawVideoInterface(BaseDataInterface): | ||
def __init__( | ||
self, | ||
nwbfiles_folder_path: DirectoryPath, | ||
subject_id: str, | ||
one: ONE, | ||
session: str, | ||
camera_name: Literal["left", "right", "body"], | ||
) -> None: | ||
""" | ||
Interface for the raw video data from the IBL Brainwide Map release. | ||
Parameters | ||
---------- | ||
nwbfiles_folder_path : DirectoryPath | ||
The folder path where the NWB file will be written in DANDI organization structure. | ||
This is an unusual value to pass to __init__, but in this case it is necessary to simplify the DANDI | ||
organization of the externally stored raw video data. | ||
subject_id : str | ||
The subject ID to use for the DANDI organization. This is also an unusual value to pass to __init__, but | ||
the custom handling of Subject extensions requires removing it from the main metadata at runtime. | ||
one : one.ONE | ||
The ONE API client. | ||
session : str | ||
The session ID (EID in ONE). | ||
camera_name : "left", "right", or "body" | ||
The name of the camera to load the raw video data for. | ||
revision : str, optional | ||
The revision of the pose estimation data to use. If not provided, the latest revision will be used. | ||
""" | ||
self.nwbfiles_folder_path = nwbfiles_folder_path | ||
self.subject_id = subject_id | ||
self.one = one | ||
self.session = session | ||
self.camera_name = camera_name | ||
|
||
def add_to_nwbfile(self, nwbfile: NWBFile, metadata: dict) -> None: | ||
camera_data = self.one.load_object(id=self.session, obj=self.camera_name, collection="alf") | ||
timestamps = camera_data["times"] | ||
|
||
left_right_or_body = self.camera_name[:5].removesuffix("C") | ||
if self.one.list_datasets(eid=self.session, filename=f"raw_video_data/*{self.camera_name}*"): | ||
original_video_file_path = self.one.load_dataset( | ||
id=self.session, dataset=f"raw_video_data/*{self.camera_name}*", download_only=True | ||
) | ||
|
||
# Rename to DANDI format and relative organization | ||
dandi_sub_stem = f"sub-{self.subject_id}" | ||
dandi_subject_folder = self.nwbfiles_folder_path / dandi_sub_stem | ||
|
||
dandi_sub_ses_stem = f"{dandi_sub_stem}_ses-{self.session}" | ||
dandi_video_folder_path = dandi_subject_folder / f"{dandi_sub_ses_stem}_ecephys+image" | ||
dandi_video_folder_path.mkdir(exist_ok=True) | ||
|
||
nwb_video_name = f"OriginalVideo{left_right_or_body.capitalize()}Camera" | ||
dandi_video_file_path = dandi_video_folder_path / f"{dandi_sub_ses_stem}_{nwb_video_name}.mp4" | ||
|
||
# A little bit of data duplication to copy, but easier for re-running since original file stays in cache | ||
copyfile(src=original_video_file_path, dst=dandi_video_file_path) | ||
|
||
image_series = ImageSeries( | ||
name=nwb_video_name, | ||
description="The original video each pose was estimated from.", | ||
unit="n.a.", | ||
external_file=["./" + str(dandi_video_file_path.relative_to(dandi_subject_folder))], | ||
format="external", | ||
timestamps=timestamps, | ||
) | ||
nwbfile.add_acquisition(image_series) |