1+ """
2+ bids_modality_custom.py
3+ this file contain contain the common modlity custom part of bids project
4+ and the customieation part of each modlity
5+ it is a factory for creating custom modality classes which will process the data , with the good format and
6+ the good path ( here is the place for the conversion part)
7+ the current directory is the directory where the BIDS output will be written
8+ example:
9+
10+ /sub-1/session-1/beh/ # as current directory
11+ sub_1_session_1_beh_task-1-events.tsv # as data file writin in the current directory
12+ sub_1_session_1_beh_task-1-eyetracking.tsv # as data file writin in the current directory
13+
14+
15+
16+
17+ """
18+ from BIDSTools .ProjectConfig import ProjectConfig
19+ from BIDSTools .Experiment import Experiment
20+ import os
21+ import shutil
22+ from BIDSTools .convertfileformat import ConvertedfSData
23+ from BIDSTools .log import log
24+ class BIDSCommonModality :
25+ def __init__ (self , project_config , experiment , current_dir ,converter = None ):
26+ """
27+ Initialize the BIDSCommonModality class with project configuration, experiment details, and directory path.
28+
29+ Parameters
30+ ----------
31+ project_config : ProjectConfig
32+ An instance of the ProjectConfig class containing project-specific configurations.
33+ experiment : Experiment
34+ An instance of the Experiment class containing details of the current experiment.
35+ current_dir : str
36+ Path to the current working directory where files will be processed and stored.
37+ converter : optional
38+ An optional converter object for handling data format conversions.
39+
40+ Attributes
41+ ----------
42+ segment_type : str
43+ The type of segment (e.g., 'run', 'chunk') as defined in the project configuration.
44+ segment_id_attr : str
45+ The attribute name for segment IDs, formatted based on segment type.
46+ data_type : str
47+ The data type as specified in the project configuration.
48+ segment_list : list
49+ A list of segments obtained from the project configuration.
50+ data_file_format : str
51+ The file format pattern for data files as defined in the project configuration.
52+ custom_pattern : str
53+ The custom configuration pattern for segments.
54+ segment_dict : dict
55+ A dictionary to store segment details.
56+ converter : optional
57+ An optional converter object for handling data format conversions.
58+ """
59+ self .project_config = project_config
60+ self .experiment = experiment
61+ self .current_dir = current_dir
62+ self .segment_type = self .project_config .get_segement_value () # e.g., 'run', 'chunk', etc.
63+ self .segment_id_attr = self .project_config .config .get ('segment_id' , f"{ self .segment_type } _id" ).format (segment = self .segment_type )
64+ self .data_type = self .project_config .get_data_type ()
65+ self .segment_list = self .project_config .get_segments_list ()
66+ self .data_file_format = self .project_config .get_data_file_format ()
67+ self .custom_pattern = self .project_config .get_custom_config ()
68+ self .segment_dict = {}
69+ self .converter = converter
70+
71+ def get_segment_details (self ):
72+ segment_dict = {}
73+ experiment_dict = self .experiment .to_dict ()
74+
75+ for segment_key in self .segment_list :
76+ segment_data = {}
77+
78+
79+ # Find all fields in the experiment that start with this segment_key
80+ segment_fields = [
81+ field_name .replace (f"{ segment_key } _" , "" )
82+ for field_name in experiment_dict
83+ if field_name .startswith (f"{ segment_key } _" )
84+ ]
85+ print (segment_fields )
86+ for field in segment_fields :
87+
88+ pattern = self .custom_pattern .format (
89+ ** {f"{ self .segment_type } _key" : segment_key , "field" : field , "segment_key" : segment_key }
90+ )
91+ value = getattr (self .experiment , pattern , None )
92+ segment_data [field ] = value
93+
94+ segment_data [self .segment_type ] = segment_key
95+
96+ raw_data_path_pattern = self .project_config .get_raw_data_path ()
97+
98+ print ("format dict:" , {f"{ self .segment_type } _key" : segment_key ,
99+ "segment_key" : segment_key })
100+ segment_data ['raw_data_path' ] = getattr (
101+ self .experiment ,
102+ raw_data_path_pattern .format (** {f"{ self .segment_type } _key" : segment_key , "segment_key" : segment_key }),
103+ None
104+ )
105+ print (segment_data )
106+ # Add segment_id if available
107+ if 'id' in segment_data :
108+ segment_data [self .segment_id_attr ] = segment_data ['id' ]
109+ segment_data ['segment_id' ] = segment_data ['id' ]
110+ else :
111+ raise ValueError (f"Segment ID not found for { segment_key } " )
112+
113+
114+
115+ segment_dict [segment_key ] = segment_data
116+ self .segment_dict = segment_dict
117+ return segment_dict
118+
119+ def get_segment_data_path (self , segment ):
120+ # Add any extra info from experiment or segment
121+
122+
123+ """
124+ Formats the data path for a given segment based on the BIDS configuration.
125+
126+ Parameters
127+ ----------
128+ segment : dict
129+ A dictionary containing information about the segment.
130+
131+ Returns
132+ -------
133+ segment : dict
134+ The input dictionary with an additional 'final_path' key containing the formatted data path.
135+ """
136+
137+ segment_info = self .experiment .to_dict ()
138+ segment_info .update (segment )
139+
140+ segment_info ['modality' ] = self .project_config .global_config .get ('modality' , 'unknown' )
141+
142+ segment ['final_path' ] = self .data_file_format .format (** segment_info )
143+
144+ return segment
145+
146+ def write_segment_info (self ):
147+ """
148+ Write BIDS segment information files for the experiment.
149+
150+ Copies raw data for each segment to the correct location if available.
151+ Creates a converter for each segment if available and calls its convert_bids_data method.
152+ """
153+ details = self .get_segment_details ()
154+ for segment_key , segment_info in details .items ():
155+ segment_info = self .get_segment_data_path (segment_info )
156+ # create empty file with final path
157+ os .makedirs (self .current_dir , exist_ok = True )
158+ # with open(os.path.join(self.current_dir, segment_info['final_path']), 'w') as f:
159+ # f.write('')
160+ # copy raw data if available to destination path with the correct name
161+
162+ if segment_info .get ('raw_data_path' ):
163+ # this will copy the raw data to the current directory
164+ shutil .copyfile (
165+ segment_info ['raw_data_path' ],
166+ os .path .join (self .current_dir , os .path .basename (segment_info ['final_path' ])),
167+ )
168+ final_raw_data_path = os .path .join (self .current_dir , os .path .basename (segment_info ['final_path' ]))
169+ segment_info ['final_raw_data_path' ] = final_raw_data_path
170+ # creat the specific converter with segment info
171+ self .create_converter (segment_info )
172+ if self .converter is None :
173+
174+ log .info (f"Modality { self .project_config .global_config .get ('modality' )} has no converter" )
175+
176+
177+ else :
178+
179+
180+
181+ # make the conversion if the converter is available
182+
183+ self .converter .convert_bids_data ()
184+
185+
186+
187+
188+
189+
190+ def create_converter (self , segment_info ):
191+ """
192+ Creates a converter for the given segment_info.
193+
194+ Parameters
195+ ----------
196+ segment_info : dict
197+ A dictionary containing information about the segment.
198+
199+ Notes
200+ -----
201+ The converter is determined by the file extension of the raw data path in the segment info.
202+ Currently, only .edf files are supported and use the `ConvertedfSData` converter.
203+ """
204+ raw_data_path = segment_info ['raw_data_path' ]
205+ all_info = self .experiment .to_dict ()
206+ all_info .update (segment_info )
207+ temps_exp = Experiment (** all_info )
208+ ext = os .path .splitext (raw_data_path )[1 ]
209+ final_raw_data_path = segment_info ['final_raw_data_path' ]
210+
211+
212+
213+
214+
215+ if ext == '.edf' :
216+ self .converter = ConvertedfSData (final_raw_data_path , None , self .current_dir , temps_exp )
217+
218+
219+ else :
220+ log .info (f"Unknown file format: { ext } " )
221+
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+ class MicroscopyCustom (BIDSCommonModality ):
232+ def __init__ (self , project_config , experiment , current_dir ):
233+ """
234+ Initialize the MicroscopyCustom class.
235+
236+ Parameters
237+ ----------
238+ project_config : ProjectConfig
239+ The project configuration object.
240+ experiment : Experiment
241+ The experiment object.
242+ current_dir : str
243+ The directory where the BIDS output will be written.
244+
245+ Notes
246+ -----
247+ This class is a customization of the BIDSCommonModality class for Microscopy projects.
248+ It sets the microscope_type attribute of the experiment to the value in the project config.
249+ """
250+ super ().__init__ (project_config , experiment , current_dir )
251+ self .microscopy_type = self .project_config .global_config .get ('microscope_type' , 'CONF' )
252+ experiment .microscope_type = self .microscopy_type
253+
254+
255+ class EyetrackingCustom (BIDSCommonModality ):
256+ def __init__ (self , project_config , experiment , current_dir ):
257+ """
258+ Initialize the EyetrackingCustom class.
259+
260+ Parameters
261+ ----------
262+ project_config : ProjectConfig
263+ The project configuration object.
264+ experiment : Experiment
265+ The experiment object.
266+ current_dir : str
267+ The directory where the BIDS output will be written.
268+
269+ Notes
270+ -----
271+ This class is a customization of the BIDSCommonModality class for Eyetracking projects.
272+ """
273+ super ().__init__ (project_config , experiment , current_dir )
274+ super ().__init__ (project_config , experiment , current_dir )
275+
276+
277+
278+
279+ class ModalityCustomBuilder :
280+ def __init__ (self , project_config , experiment , current_dir ):
281+ """
282+ Initialize the ModalityCustomBuilder class.
283+
284+ Parameters
285+ ----------
286+ project_config : ProjectConfig
287+ The project configuration object.
288+ experiment : Experiment
289+ The experiment object.
290+ current_dir : str
291+ The directory where the BIDS output will be written.
292+
293+ Notes
294+ -----
295+ This class is a factory for creating custom modality classes based on the project configuration.
296+ It creates an instance of either the MicroscopyCustom or EyetrackingCustom class depending on the project name.
297+ """
298+ if project_config .get_project_name () == 'microscopy_confocal' :
299+ self .custom = MicroscopyCustom (project_config , experiment , current_dir )
300+ elif project_config .get_project_name () == 'eyetracking' :
301+ self .custom = EyetrackingCustom (project_config , experiment , current_dir )
302+ else :
303+ raise ValueError (f"Unknown project config: { project_config .get_project_name ()} perhaps check your config file?" )
304+
305+ def build_customizations (self ):
306+ self .custom .write_segment_info ()
307+ return self .custom
308+
309+
310+
311+ if __name__ == "__main__" :
312+ from BIDSTools .ProjectConfig import ProjectConfig
313+ from BIDSTools .Experiment import Experiment
314+
315+ # Example: Load config and create a mock experiment
316+ config_file = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/BIDS_PROJECT_CONFIG/microscopy_confocal.yml"
317+ config_file2 = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/BIDS_PROJECT_CONFIG/eyetracking.yml"
318+ project_config = ProjectConfig (config_file )
319+ project_config2 = ProjectConfig (config_file2 )
320+
321+ # Mock experiment with fields for 2 segments
322+ experiment = Experiment (
323+ participant_id = "001" ,
324+ session_id = "01" ,
325+ modality = "eyetracking" ,
326+ task = "test" ,
327+ sample_id = "01" ,
328+ imageo01_datafile_path = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/data/im0.czi" ,
329+ image01_id = "01" ,
330+ image01_datafile_path = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/data/im0.czi" ,
331+ image02_id = "02"
332+
333+ )
334+ experiment2 = Experiment (
335+ participant_id = "01" ,
336+ session_id = "01" ,
337+ task = "test" ,
338+ modality = "eyetracking" ,
339+ run01_datafile_path = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/data/im0.czi" ,
340+ run01_id = "01" ,
341+ run02_datafile_path = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/data/im0.czi" ,
342+ run02_id = "02" ,
343+ run03_datafile_path = "/home/INT/idrissou.f/PycharmProjects/BEP032tools/BIDSTools/data/im0.czi" ,
344+ run03_id = "03"
345+ )
346+ output_dir = "./output"
347+
348+
349+ # use common parent class
350+ #handler = BIDSCommonModality(project_config, experiment, output_dir)
351+ #handler = BIDSCommonModality(project_config2, experiment2, output_dir)
352+ # use custom classes
353+ handler = MicroscopyCustom (project_config , experiment , output_dir )
354+ #handler= EyetrackingCustom(project_config2, experiment2, output_dir)
355+
356+ handler .write_segment_info ()
0 commit comments