Skip to content
Snippets Groups Projects
Commit 2bd30b87 authored by Toumji Abdallah's avatar Toumji Abdallah
Browse files

Sidecar Creation from scratch implemented

parent ada597fe
No related branches found
No related tags found
No related merge requests found
......@@ -126,11 +126,11 @@ class Application(tk.Frame):
Tools.clean_frame(self.center_frame)
self.form_handler = FormHandler(self.center_frame, self.selected_forms)
self.form_handler = FormHandler(self.center_frame, self.selected_forms, 'res/Requirements.json')
self.form_handler.show_forms(0)
self.confirm_button.config(text='Create', command=self.form_finish, state='disabled')
self.center_frame.winfo_toplevel().bind('<<EntryStateChanged>>', lambda *_: self.check_mandatory(self.form_handler.form_frame_list[0]))
self.center_frame.winfo_toplevel().bind('<<EntryStateChanged>>', lambda *_: self.check_mandatory(self.form_handler.form_frame_list[0], self.confirm_button))
def form_finish(self):
''' This method is used to finish the creation of the dataset description of the new BIDS project.
......@@ -144,7 +144,7 @@ class Application(tk.Frame):
self.form_handler.show_forms(0)
self.confirm_button.config(text='Confirm', command=self.next_step, state='disabled')
self.form_handler.frame.winfo_toplevel().bind('<<EntryStateChanged>>', lambda *_: self.check_mandatory(self.form_handler.form_frame_list[0]))
self.form_handler.frame.winfo_toplevel().bind('<<EntryStateChanged>>', lambda *_: self.check_mandatory(self.form_handler.form_frame_list[0], self.confirm_button))
def next_step(self):
''' This method is used to go to the next step of the creation of the new BIDS project.
......@@ -168,15 +168,15 @@ class Application(tk.Frame):
self.actual_step += 1
self.integrate_sidecar()
def check_mandatory(self, form):
def check_mandatory(self, form, validation_button):
''' This method is used to check if the form is valid.
The confirm button is enabled if the form is valid.
'''
if form.is_valid_mandatory():
self.confirm_button.config(state='normal')
validation_button.config(state='normal')
else:
self.confirm_button.config(state='disabled')
validation_button.config(state='disabled')
def create_directory(self):
''' This method is used to create the directory of the new BIDS project.
......@@ -429,6 +429,9 @@ class Application(tk.Frame):
self.integrate_sidecar_button = tk.Button(self.integrate_sidecar_frame, text='Integrate sidecar', command=self.integrate_sidecar_file)
self.integrate_sidecar_button.pack()
self.create_sidecar_button = tk.Button(self.integrate_sidecar_frame, text='Create sidecar', command=self.create_sidecar)
self.create_sidecar_button.pack()
self.show_project_button = tk.Button(self.integrate_sidecar_frame, text='Show project state', command=self.actual_project_state)
self.show_project_button.pack()
......@@ -447,6 +450,87 @@ class Application(tk.Frame):
self.integrate_sidecar_button = tk.Button(self.integrate_sidecar_window, text='Select file', command=self.select_sidecar_file)
self.integrate_sidecar_button.grid(row=1, column=0)
def create_sidecar(self):
''' This method is used to create a sidecar of the new BIDS project.
The user is guided through the process of creating a sidecar of the new BIDS project.
He is first prompted to select the type of the sidecar (e.g. channels.tsv, events.tsv, etc.).
'''
self.integrate_sidecar_window = tk.Toplevel(self.root)
self.integrate_sidecar_window.title('Create sidecar')
self.integrate_sidecar_label = tk.Label(self.integrate_sidecar_window, text='Create sidecar')
self.integrate_sidecar_label.grid(row=0, column=0)
self.sidecar_type_label = tk.Label(self.integrate_sidecar_window, text='Select the type of the sidecar:')
self.sidecar_type_label.grid(row=1, column=0)
self.sidecar_type = tk.StringVar()
self.sidecar_type.set('channels.tsv')
self.sidecar_type_dropdown = ttk.Combobox(self.integrate_sidecar_window, textvariable=self.sidecar_type)
self.sidecar_type_dropdown['values'] = self.extension_to_possible_sidecar['edf'] # To change
self.sidecar_type_dropdown.grid(row=1, column=1)
self.sidecar_type_dropdown.config(state='readonly')
self.integrate_sidecar_button = tk.Button(self.integrate_sidecar_window, text='Create file', command=self.create_sidecar_second)
self.integrate_sidecar_button.grid(row=2, column=0)
def select_sidecar_type(self):
''' This method is used to select the type of the sidecar to create.
The user is guided through the process of selecting the type of the sidecar to create.
'''
self.integrate_sidecar_window.lift()
self.integrate_sidecar_label.config(text='Create sidecar\nType selected: ' + self.sidecar_type.get())
self.change_sidecar_button = tk.Button(self.integrate_sidecar_window, text='Change type', command=self.create_sidecar)
self.integrate_sidecar_button.config(text='Create', command=self.create_sidecar_second, state='normal')
def create_sidecar_second(self):
''' This method is used to finish the creation of the sidecar of the new BIDS project.
Following the type selected, the user is presented with a form to fill that will be saved as the sidecar.
For .json files, the form is a dictionary.
For .tsv files, the form is a table.
'''
self.integrate_sidecar_window.destroy()
self.integrate_sidecar_window = tk.Toplevel(self.root)
self.integrate_sidecar_window.title('Create sidecar')
self.integrate_sidecar_label = tk.Label(self.integrate_sidecar_window, text='Creating sidecar...')
self.integrate_sidecar_label.grid(row=0, column=0)
self.form_handler = FormHandler(self.integrate_sidecar_window, [self.sidecar_type.get()], 'res/Sidecars.json')
self.form_handler.show_forms(0)
self.integrate_sidecar_button = tk.Button(self.integrate_sidecar_window, text='Confirm', command=self.create_sidecar_finish, state='disabled')
self.integrate_sidecar_button.grid(row=1, column=0)
self.form_handler.frame.winfo_toplevel().bind('<<EntryStateChanged>>', lambda *_: self.check_mandatory(self.form_handler.form_frame_list[0], self.integrate_sidecar_button))
def create_sidecar_finish(self):
''' This method is used to finish the creation of the sidecar of the new BIDS project.
The sidecar is temporarily saved in the res folder of the application.
The user is then prompted to select the experiments to apply the sidecar to.
'''
self.sidecar_file = self.project_directory + '/' + 'res/' + self.sidecar_type.get()
os.mkdir(self.project_directory + '/' + 'res')
self.form_handler.save_forms(0, self.project_directory + '/' + 'res')
self.form_handler.form_frame_list[0].frame.grid_forget()
self.form_handler.form_frame_list[0].frame.destroy()
self.form_handler.form_frame_list.pop(0)
self.experiments_files = []
self.integrate_sidecar_label.config(text='Sidecar created')
self.integrate_sidecar_button.config(text='Select experiments', command=lambda: self.select_experiments(True), state='normal')
def select_sidecar_file(self):
''' This method is used to select the file to integrate to the new BIDS project.
......@@ -466,25 +550,30 @@ class Application(tk.Frame):
self.sidecar_type_dropdown = ttk.Combobox(self.integrate_sidecar_window, textvariable=self.sidecar_type)
self.sidecar_type_dropdown['values'] = self.extension_to_possible_sidecar['edf'] # To change
self.sidecar_type_dropdown.grid(row=2, column=1)
self.sidecar_type_dropdown.config(state='readonly')
self.integrate_sidecar_button.config(text='Select experiments', command=self.select_experiments)
self.experiments_files = []
def select_experiments(self):
def select_experiments(self, create=False):
''' This method is used to select the experiments to apply the sidecar to.
The user is guided through the process of selecting the experiments to apply the sidecar to.
'''
if not create:
self.sidecar_type_dropdown.grid_forget()
self.dropdown_label.grid_forget()
self.experiments_files.append(tk.filedialog.askopenfilename(initialdir=self.project_directory))
self.integrate_sidecar_window.lift()
self.integrate_sidecar_label.config(text='File selected: ' + self.sidecar_file + '\nType: ' + self.sidecar_type.get() + '\nExperiments selected: ' + str(self.experiments_files))
self.add_experiment_button = tk.Button(self.integrate_sidecar_window, text='Add experiment', command=self.select_experiments)
self.add_experiment_button = tk.Button(self.integrate_sidecar_window, text='Add experiment', command= lambda : self.select_experiments(create))
self.add_experiment_button.grid(row=2, column=0)
if not create:
self.integrate_sidecar_button.config(text='Integrate', command=self.integrate_sidecar_finish, state='normal')
else:
self.integrate_sidecar_button.config(text='Integrate', command=self.place_created_sidecar, state='normal')
def integrate_sidecar_finish(self):
''' This method is used to finish the integration of the sidecar to the new BIDS project.
......@@ -611,3 +700,30 @@ class Application(tk.Frame):
if value != 'not_shared':
res = res + key + '-' + value + '_'
return res
def place_created_sidecar(self):
''' This method is used to place the created sidecar to the new BIDS project.
The created sidecar is placed to the new BIDS project.
It is saved following the bids standard on inheritance.
It is placed at the optimal place in the hierarchy to be applied to the choosen experiments.
It is named following the BIDS standard.
'''
self.integrate_sidecar_window.destroy()
self.integrate_sidecar_window = tk.Toplevel(self.root)
self.integrate_sidecar_window.title('Integrate sidecar')
self.integrate_sidecar_label = tk.Label(self.integrate_sidecar_window, text='Placing sidecar...')
self.integrate_sidecar_label.grid(row=0, column=0)
self.integrate_sidecar_button = tk.Button(self.integrate_sidecar_window, text='Leave', command=self.integrate_sidecar_window.destroy)
self.integrate_sidecar_button.grid(row=1, column=0)
path = self.get_optimal_files(self.experiments_files, self.sidecar_type.get())
for p in path:
shutil.copy2(self.sidecar_file, p)
shutil.rmtree(self.project_directory + '/' + 'res')
self.integrate_sidecar_label.config(text='Sidecar placed')
......@@ -146,10 +146,10 @@ class Form:
class FormHandler:
def __init__(self, frame, necessary_forms):
def __init__(self, frame, necessary_forms, file_path):
self.frame = frame
self.form_frame_list = []
self.requirement = json.load(open(os.path.join(os.path.dirname(__file__), 'res/Requirements.json')))
self.file = json.load(open(os.path.join(os.path.dirname(__file__), file_path)))
with Image.open(os.path.join(os.path.dirname(__file__), 'res/help_icon.png')) as img_help:
img_help = img_help.resize((20, 20))
......@@ -161,7 +161,7 @@ class FormHandler:
img_valid = img_valid.resize((20, 20))
self.valid_icon = ImageTk.PhotoImage(img_valid)
for form in self.requirement.items():
for form in self.file.items():
if form[0] in necessary_forms:
self.form_frame_list.append(Form(form, self.frame, self.help_icon, self.invalid_icon, self.valid_icon))
......
{
"channels.tsv" : {
"name": {
"requirement level": "required",
"description": "Label of the channel, values must be unique",
"valid condition": "len(value) > 0"
},
"type": {
"requirement level": "required",
"description": "Type of the channel",
"valid condition": "len(value) > 0"
},
"units": {
"requirement level": "required",
"description": "Physical unit of the value represented in this channel, for example, V for Volt, or fT/cm for femto Tesla per centimeter",
"valid condition": "len(value) > 0"
},
"description": {
"requirement level": "optional",
"description": "Brief free-text description of the channel, or other information of interest.",
"valid condition": "len(value) > 0"
},
"sampling_frequency": {
"requirement level": "required",
"description": "Sampling frequency of the channel in Hz",
"valid condition": "float(value) > 0"
},
"reference": {
"requirement level": "optional",
"description": "Name of the reference electrode(s). This column is not needed when it is common to all channels. In that case the reference electrode(s) can be specified in *_eeg.json as EEGReference).",
"valid condition": "len(value) > 0"
},
"low_cutoff": {
"requirement level": "optional",
"description": "Frequencies used for the high-pass filter applied to the channel in Hz. If no high-pass filter applied, use n/a.",
"valid condition": "value == 'n/a' or float(value) > 0"
},
"high_cutoff": {
"requirement level": "optional",
"description": "Frequencies used for the low-pass filter applied to the channel in Hz. If no low-pass filter applied, use n/a. Note that hardware anti-aliasing in A/D conversion of all MEG/EEG electronics applies a low-pass filter; specify its frequency here if applicable.",
"valid condition": "value == 'n/a' or float(value) > 0"
},
"notch": {
"requirement level": "optional",
"description": "Frequencies used for the notch filter applied to the channel, in Hz. If notch filters are applied at multiple frequencies, these frequencies MAY be specified as a list, for example, [60, 120, 180]. If no notch filter was applied, use n/a.",
"valid condition": "value == 'n/a' or float(value) > 0",
"multiple": true
},
"status": {
"requirement level": "optional",
"description": "Frequencies used for the notch filter applied to the channel, in Hz. If notch filters are applied at multiple frequencies, these frequencies MAY be specified as a list, for example, [60, 120, 180]. If no notch filter was applied, use n/a.",
"valid condition": "value in ['good', 'bad', 'n/a']",
"multiple": false
},
"status_description": {
"requirement level": "optional",
"description": "Freeform text description of noise or artifact affecting data quality on the channel. It is meant to explain why the channel was declared bad in the status column.",
"valid condition": "len(value) > 0",
"multiple": false
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment