diff --git a/src/lerner_lab_to_nwb/seiler_2024/seiler_2024_convert_dataset.py b/src/lerner_lab_to_nwb/seiler_2024/seiler_2024_convert_dataset.py index a38e1ca..21049a6 100644 --- a/src/lerner_lab_to_nwb/seiler_2024/seiler_2024_convert_dataset.py +++ b/src/lerner_lab_to_nwb/seiler_2024/seiler_2024_convert_dataset.py @@ -52,6 +52,7 @@ split_western_blot, ) from lerner_lab_to_nwb.seiler_2024.medpc_helpers import get_medpc_variables, read_medpc_file +from lerner_lab_to_nwb.seiler_2024.seiler_2024csvbehaviorinterface import Seiler2024CSVBehaviorInterface def dataset_to_nwb( @@ -252,6 +253,32 @@ def get_session_key_from_kwargs(session_to_nwb_kwargs: dict): return session_key +def get_subject_session_start(session_to_nwb_kwargs: dict) -> tuple[str, str, str]: + """Get the subject ID, start date, and start time of a session. + + For CSV behavior files, the start date and time are read from the CSV itself. + + Parameters + ---------- + session_to_nwb_kwargs : dict + The arguments for session_to_nwb. + + Returns + ------- + tuple[str, str, str] + The subject ID, start date (mm/dd/yy), and start time (HH:MM:SS) of the session. + """ + behavior_file_path = session_to_nwb_kwargs["behavior_file_path"] + if behavior_file_path.suffix in (".csv", ".CSV"): + behavior_metadata = Seiler2024CSVBehaviorInterface(file_path=str(behavior_file_path)).get_metadata()["Behavior"] + start_date = behavior_metadata["start_date"] + start_time = behavior_metadata["start_time"] + else: + start_date = session_to_nwb_kwargs["session_conditions"]["Start Date"] + start_time = session_to_nwb_kwargs["session_conditions"]["Start Time"] + return session_to_nwb_kwargs["subject_id"], start_date, start_time + + def safe_session_to_nwb(*, session_to_nwb_kwargs: dict, exception_file_path: Union[Path, str]): """Convert a session to NWB while handling any errors by recording error messages to the exception_file_path. @@ -731,6 +758,10 @@ def opto_to_nwb( continue unique_session_keys.add(session_key) session_to_nwb_args_per_session.append(session_to_nwb_args) + subject_session_starts = { + get_subject_session_start(session_to_nwb_args) for session_to_nwb_args in session_to_nwb_args_per_session + } + # DLS Excitatory raw files by date raw_files_by_date_path = data_dir_path / "Opto Experiments" / "DLS Excitatory" start_dates, start_times, msns, file_paths, subjects, box_numbers = [], [], [], [], [], [] @@ -765,6 +796,8 @@ def opto_to_nwb( } if subject in partial_subject_ids_to_subject_id: subject = partial_subject_ids_to_subject_id[subject] + if (subject, start_date, start_time) in subject_session_starts: + continue # already converted from the subject's own folder, which also gives the treatment session_to_nwb_args = dict( data_dir_path=data_dir_path, output_dir_path=output_dir_path, diff --git a/tests/test_opto_session_deduplication.py b/tests/test_opto_session_deduplication.py new file mode 100644 index 0000000..8e1d93b --- /dev/null +++ b/tests/test_opto_session_deduplication.py @@ -0,0 +1,70 @@ +from pathlib import Path + +import pytest + +from lerner_lab_to_nwb.seiler_2024.seiler_2024_convert_dataset import opto_to_nwb + +TREATMENT_FOLDERS = [ + "DLS Excitatory/ChR2", + "DLS Excitatory/EYFP", + "DLS Excitatory/Scrambled", + "DMS Excitatory/ChR2", + "DMS Excitatory/EYFP", + "DMS Excitatory/Scrambled", + "DMS Inhibitory/Group 1/Halo", + "DMS Inhibitory/Group 1/EYFP", + "DMS Inhibitory/Group 1/Scrambled", + "DMS Inhibitory/Group 2/NpHr", + "DMS Inhibitory/Group 2/EYFP", + "DMS Inhibitory/Group 2/Scrambled", +] + + +def medpc_run(subject: str, box: str) -> str: + return ( + "Start Date: 09/29/20\n" + "End Date: 09/29/20\n" + f"Subject: {subject}\n" + "Experiment: \n" + "Group: \n" + f"Box: {box}\n" + "Start Time: 11:36:53\n" + "End Time: 12:36:45\n" + "MSN: Footshock Degradation Left\n" + ) + + +@pytest.fixture +def data_dir_path(tmp_path) -> Path: + opto_path = tmp_path / "Opto Experiments" + for treatment_folder in TREATMENT_FOLDERS: + (opto_path / treatment_folder).mkdir(parents=True) + + # 300.405's run exported to its own folder as a CSV + subject_folder_path = opto_path / "DLS Excitatory" / "Scrambled" / "300_405" + subject_folder_path.mkdir() + (subject_folder_path / "300.405_09-29-20.csv").write_text( + ",Start Date,End Date,Subject,Experiment,Group,Box,Start Time,End Time,MSN\n" + "0,09/29/20,09/29/20,300.405,,,5,11:36:53,12:36:45,Footshock Degradation Left\n" + ) + + # The same run and another mouse's run in the date-organized MED-PC file + (opto_path / "DLS Excitatory" / "2020-09-29").write_text( + "\n" + medpc_run(subject="418.404", box="1") + "\n" + medpc_run(subject="300.405", box="5") + ) + return tmp_path + + +class TestOptoToNwb: + def test_by_date_run_already_in_subject_folder_is_skipped(self, data_dir_path, tmp_path): + session_to_nwb_args_per_session = opto_to_nwb( + data_dir_path=data_dir_path, + output_dir_path=tmp_path / "nwb", + start_variable="Start Date", + verbose=False, + ) + subject_and_treatment = sorted( + (session_to_nwb_args["subject_id"], session_to_nwb_args["optogenetic_treatment"]) + for session_to_nwb_args in session_to_nwb_args_per_session + ) + assert subject_and_treatment == [("300.405", "ChR2Scrambled"), ("418.404", "Unknown")]