Skip to content

Commit 99fbe57

Browse files
authored
Merge pull request INT-NIT#163 from sifaoufatai/microscopy-confocal
Microscopy confoncal and eyetracking customization parts
2 parents b26f6b7 + 7011052 commit 99fbe57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+197977
-39
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ Thumbs.db
5858
# Fichiers temporaires à ignorer
5959
BIDSTools/dev_tests/fff.json
6060
BIDSTools/dev_tests/ffffff.json
61-
BIDSTools/dev_tests/fff.CSV
61+
bep032tools/fff.CSV
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
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()

BIDSTools/BIDS_PROJECT_CONFIG/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)