1010from pathlib import Path
1111from typing import Union
1212
13- import npc_ephys
1413import npc_mvr
1514import numpy as np
1615import pandas as pd
2524 VisualStimulation ,
2625)
2726from aind_data_schema_models .modalities import Modality
27+ from npc_ephys import (
28+ get_ephys_timing_on_sync ,
29+ get_newscale_coordinates ,
30+ get_single_oebin_path ,
31+ )
2832
33+ import aind_metadata_mapper .open_ephys .utils .pkl_utils as pkl
2934import aind_metadata_mapper .open_ephys .utils .sync_utils as sync
3035import aind_metadata_mapper .stimulus .camstim
3136from aind_metadata_mapper .core import GenericEtl
@@ -46,9 +51,7 @@ class CamstimEphysSessionEtl(
4651 session_path : Path
4752 recording_dir : Path
4853
49- def __init__ (
50- self , session_id : str , job_settings : Union [JobSettings , str , dict ]
51- ) -> None :
54+ def __init__ (self , job_settings : Union [JobSettings , str , dict ]) -> None :
5255 """
5356 Determine needed input filepaths from np-exp and lims, get session
5457 start and end times from sync file, write stim tables and extract
@@ -67,48 +70,69 @@ def __init__(
6770 job_settings_model = job_settings
6871 GenericEtl .__init__ (self , job_settings = job_settings_model )
6972
70- sessions_root = Path (self .job_settings .sessions_root )
71- self .folder = self .get_folder (session_id , sessions_root )
72- self .session_path = self .get_session_path (session_id , sessions_root )
73- self .recording_dir = npc_ephys .get_single_oebin_path (
74- self .session_path
75- ).parent
73+ # sessions_root = Path(self.job_settings.sessions_root)
74+ # self.folder_name = self.get_folder(session_id, sessions_root)
75+ # self.session_path = self.get_session_path(session_id, sessions_root)
76+ self .session_path = job_settings .input_source
77+ self .folder_name = self .session_path .name
78+ self .output_dir = job_settings .output_directory
79+ # sometimes data files are deleted on npexp so try files on lims
80+ # try:
81+ # self.recording_dir = get_single_oebin_path(
82+ # session_inst.lims_path
83+ # ).parent
84+ # except:
85+ self .recording_dir = get_single_oebin_path (self .session_path ).parent
7686
7787 self .motor_locs_path = (
78- self .session_path / f"{ self .folder } .motor-locs.csv"
88+ self .session_path / f"{ self .folder_name } .motor-locs.csv"
7989 )
80- self .pkl_path = self .session_path / f"{ self .folder } .stim.pkl"
81- self .opto_pkl_path = self .session_path / f"{ self .folder } .opto.pkl"
90+ self .pkl_path = self .session_path / f"{ self .folder_name } .stim.pkl"
91+ if not self .pkl_path .exists ():
92+ self .pkl_path = (
93+ self .session_path / f"{ self .folder_name } .behavior.pkl"
94+ )
95+ logger .debug ("Using pickle:" , self .pkl_path )
96+ self .pkl_data = pkl .load_pkl (self .pkl_path )
97+ self .fps = pkl .get_fps (self .pkl_data )
98+
99+ self .opto_pkl_path = self .session_path / f"{ self .folder_name } .opto.pkl"
82100 self .opto_table_path = (
83- self .session_path / f"{ self .folder } _opto_epochs.csv"
101+ self .session_path / f"{ self .folder_name } _opto_epochs.csv"
84102 )
103+ self .opto_conditions_map = job_settings .opto_conditions_map
85104 self .stim_table_path = (
86- self .session_path / f"{ self .folder } _stim_epochs.csv"
105+ self .session_path / f"{ self .folder_name } _stim_epochs.csv"
87106 )
88- self .sync_path = self .session_path / f"{ self .folder } .sync"
107+ self .sync_path = self .session_path / f"{ self .folder_name } .sync"
89108
90109 platform_path = next (
91- self .session_path .glob (f"{ self .folder } _platform*.json" )
110+ self .session_path .glob (f"{ self .folder_name } _platform*.json" )
92111 )
93112 self .platform_json = json .loads (platform_path .read_text ())
94113 self .project_name = self .platform_json ["project" ]
95114
96- sync_data = sync .load_sync (self .sync_path )
97- self .session_start = sync .get_start_time (sync_data )
98- self .session_end = sync .get_stop_time (sync_data )
115+ self . sync_data = sync .load_sync (self .sync_path )
116+ self .session_start = sync .get_start_time (self . sync_data )
117+ self .session_end = sync .get_stop_time (self . sync_data )
99118 logger .debug (
100119 f"session start: { self .session_start } \n "
101120 f" session end: { self .session_end } "
102121 )
103122
104123 self .session_uuid = self .get_session_uuid ()
105- self .mtrain_regimen = self .get_mtrain ()
124+ self .mtrain_server = job_settings .mtrain_server
125+ self .stage_name = pkl .get_stage (self .pkl_data )
126+ self .behavior = self ._is_behavior ()
106127
107128 if not self .stim_table_path .exists () or (
108129 self .job_settings .overwrite_tables
109130 ):
110131 logger .debug ("building stim table" )
111- self .build_stimulus_table ()
132+ if self .behavior :
133+ self .build_behavior_table ()
134+ else :
135+ self .build_stimulus_table ()
112136 if self .opto_pkl_path .exists () and (
113137 not self .opto_table_path .exists ()
114138 or self .job_settings .overwrite_tables
@@ -127,7 +151,7 @@ def run_job(self):
127151 """Transforms all metadata for the session into relevant files"""
128152 self ._extract ()
129153 self ._transform ()
130- return self ._load (self .session_json , self .session_path )
154+ return self ._load (self .session_json , self .output_dir )
131155
132156 def _extract (self ):
133157 """TODO: refactor a lot of the __init__ code here"""
@@ -146,7 +170,7 @@ def _transform(self) -> Session:
146170 session_type = self .job_settings .session_type ,
147171 iacuc_protocol = self .job_settings .iacuc_protocol ,
148172 rig_id = self .platform_json ["rig_id" ],
149- subject_id = self .folder .split ("_" )[1 ],
173+ subject_id = self .folder_name .split ("_" )[1 ],
150174 data_streams = self .data_streams (),
151175 stimulus_epochs = self .stim_epochs ,
152176 mouse_platform_name = self .job_settings .mouse_platform_name ,
@@ -235,9 +259,7 @@ def ephys_modules(self) -> list:
235259 """
236260 Return list of schema ephys modules for each available probe.
237261 """
238- newscale_coords = npc_ephys .get_newscale_coordinates (
239- self .motor_locs_path
240- )
262+ newscale_coords = get_newscale_coordinates (self .motor_locs_path )
241263
242264 ephys_modules = []
243265 for probe_letter in self .available_probes :
@@ -251,7 +273,7 @@ def ephys_modules(self) -> list:
251273 arc_angle = 0.0 ,
252274 module_angle = 0.0 ,
253275 rotation_angle = 0.0 ,
254- primary_targeted_structure = "none " ,
276+ primary_targeted_structure = "root " ,
255277 manipulator_coordinates = manipulator_coordinates ,
256278 notes = notes ,
257279 )
@@ -265,7 +287,7 @@ def ephys_stream(self) -> Stream:
265287 """
266288 probe_exp = r"(?<=[pP{1}]robe)[-_\s]*(?P<letter>[A-F]{1})(?![a-zA-Z])"
267289
268- times = npc_ephys . get_ephys_timing_on_sync (
290+ times = get_ephys_timing_on_sync (
269291 sync = self .sync_path , recording_dirs = [self .recording_dir ]
270292 )
271293
@@ -336,10 +358,62 @@ def data_streams(self) -> tuple[Stream, ...]:
336358 """
337359 data_streams = []
338360 data_streams .append (self .ephys_stream ())
339- data_streams .append (self .sync_stream ())
340- data_streams .append (self .video_stream ())
361+ # data_streams.append(self.sync_stream())
362+ # data_streams.append(self.video_stream())
341363 return tuple (data_streams )
342364
365+ def build_optogenetics_table (self ):
366+ """
367+ Builds an optogenetics table from the opto pickle file and sync file.
368+ Writes the table to a csv file.
369+ Parameters
370+ ----------
371+ output_opto_table_path : str
372+ Path to write the optogenetics table to.
373+ returns
374+ -------
375+ dict
376+ Dictionary containing the path to the output opto table
377+ """
378+ opto_file = pkl .load_pkl (self .opto_pkl_path )
379+ sync_file = sync .load_sync (self .sync_path )
380+ start_times = sync .extract_led_times (
381+ sync_file , self .opto_conditions_map
382+ )
383+ condition_nums = [str (item ) for item in opto_file ["opto_conditions" ]]
384+ levels = opto_file ["opto_levels" ]
385+ assert len (condition_nums ) == len (levels )
386+ if len (start_times ) > len (condition_nums ):
387+ raise ValueError (
388+ f"there are { len (start_times ) - len (condition_nums )} extra "
389+ f"optotagging sync times!"
390+ )
391+ optotagging_table = pd .DataFrame (
392+ {
393+ "start_time" : start_times ,
394+ "condition_num" : condition_nums ,
395+ "level" : levels ,
396+ }
397+ )
398+ optotagging_table = optotagging_table .sort_values (
399+ by = "start_time" , axis = 0
400+ )
401+ stop_times = []
402+ conditions = []
403+ names = []
404+ for _ , row in optotagging_table .iterrows ():
405+ condition = self .opto_conditions_map [row ["condition_num" ]]
406+ stop_times .append (row ["start_time" ] + condition ["duration" ])
407+ conditions .append (condition ["condition" ])
408+ names .append (condition ["name" ])
409+ optotagging_table ["stop_time" ] = stop_times
410+ optotagging_table ["condition" ] = conditions
411+ optotagging_table ["name" ] = names
412+ optotagging_table ["duration" ] = (
413+ optotagging_table ["stop_time" ] - optotagging_table ["start_time" ]
414+ )
415+ optotagging_table .to_csv (self .opto_table_path , index = False )
416+
343417 def epoch_from_opto_table (self ) -> StimulusEpoch :
344418 """
345419 From the optogenetic stimulation table, returns a single schema
@@ -348,11 +422,9 @@ def epoch_from_opto_table(self) -> StimulusEpoch:
348422 parameters, and include the set of all of that column's values as the
349423 parameter values.
350424 """
351-
352425 script_obj = Software (
353- name = self .mtrain_regimen [ "name" ] ,
426+ name = self .stage_name ,
354427 version = "1.0" ,
355- url = self .mtrain_regimen ,
356428 )
357429
358430 opto_table = pd .read_csv (self .opto_table_path )
0 commit comments