From e5eaccb14e23aea8a306ea6ab05d8d88500f1002 Mon Sep 17 00:00:00 2001 From: "Verbeek, J.M. (Janneke)" <j.verbeek@student.ru.nl> Date: Mon, 2 Dec 2019 14:22:32 +0100 Subject: [PATCH] Can now read multiple files, solved some GUI bugs, can also write multiple files --- umbra/controller.py | 73 ++++++++++++++++++++++++++++++++------------- umbra/filereader.py | 18 +++++------ umbra/model.py | 10 ++++--- umbra/utils.py | 31 +++++++++++++++++-- umbra/view.py | 64 ++++++++++++++++++++++----------------- 5 files changed, 131 insertions(+), 65 deletions(-) diff --git a/umbra/controller.py b/umbra/controller.py index 942c83da..b2108fee 100644 --- a/umbra/controller.py +++ b/umbra/controller.py @@ -32,7 +32,6 @@ class Controller: key(str): Identifier for view functionality """ action, *type = key.split(' ') # type is a (possibly empty) list - if action == 'select': self._select_files(type[0]) elif action == 'delete': @@ -47,21 +46,17 @@ class Controller: self._filewriter = TxtWriter() elif action == 'select_folder': self._select_folder() + elif action == 'rm_all': + self._delete_files(type[0], True) - def _select_folder(self): + def _select_folder(self, type="shadow"): folder_path = fd.askdirectory() file_names = os.listdir(folder_path) file_paths = [folder_path + "/" + x for x in file_names] - self._shadow_files = file_paths # For the record - for file_path in file_paths: - if "AO" not in file_path: - pass # TODO: raise error - else: - dict = self._model._multi_data_shadow - key = ut.shadow_regex(file_path) - self._filereader.path(file_path) - data = self._filereader.read() - self._model.add_to_dict(key, data, dict) + self._shadow_files = file_paths + self._view.update_files(file_paths, type) + self._view._button_status("select {}".format(type), "disabled") + def _select_files(self, type): """Select files and add them to files list corresponding to type. @@ -74,20 +69,29 @@ class Controller: # TODO: Check if not already present files.extend(selection) self._view.update_files(files, type) + if type == "shadow": + self._view._button_status("select_folder", "disable") - def _delete_files(self, type): # TODO: Not yet implemented fully + def _delete_files(self, type, all=False): # TODO: Not yet implemented fully """Remove selected file from files list corresponding to type. Args: type (str): Role of files ('source' or 'shadow') + all (boolean): whether to remove all files, by default false. """ selection = self._view.selected(type) files = getattr(self, '_{}_files'.format(type)) - if selection is not None: # TODO: This is supposed to make sure deletion of 'No file selected' is impossible, but does not work. - selection = [file for file in files if selection.lower() in file.lower()][0] - files.remove(selection) - self._view.update_files(files, type) # TODO: File remains in drop down options + if all: + selection = [file for file in files] + for s in selection: + files.remove(s) + self._view._button_status("select {}".format(type), "normal") + else: + selection = [file for file in files if selection.lower() in file.lower()][0] + files.remove(selection) + self._view._button_status("select_folder".format(type), "normal") + self._view.update_files(files, type) def _compare_files(self): """Check source and shadow data availability, and compare.""" @@ -97,8 +101,13 @@ class Controller: self._view.update_message('no shadow') else: self._view.update_message('files ok') + + if len(self._shadow_files) > 1: + self._read_folder("shadow") + else: + self._read_files("shadow") + self._read_files("source") - self._read_files("shadow") self._model.compare() self._view.update_message('comparison complete') @@ -107,9 +116,14 @@ class Controller: if not self._model.analysis_complete(): self._view.update_message('no comparison') else: - path = self._view.ask_save_location() - results = self._model._analysis_results - self._filewriter.write(path, results) + if self._model._analysis_results is not None: + path = self._view.ask_save_location() + results = self._model._analysis_results + self._filewriter.write(path, results) + else: + path = fd.askdirectory() + results = self._model._multi_results + self._filewriter.write_multiple(path, results) # results are of form sc, sh, info self._view.update_message('saved') @@ -129,3 +143,20 @@ class Controller: self._model._id = ut.id_regex(path) else: self._model._data_shadow = data + + def _read_folder(self, type): + """ + Read in multiple source files. + Extensible to consider "type". Now assume shadow + files. + """ + for file_path in self._shadow_files: + if "AO" not in file_path or ".csv" not in file_path: + # Very dirty + pass # TODO: raise error + else: + dict = self._model._multi_data_shadow + key = ut.shadow_regex(file_path) + self._filereader.path(file_path) + data = self._filereader.read() + ut.add_to_dict(key, data, dict) diff --git a/umbra/filereader.py b/umbra/filereader.py index 784b491f..dd5ee449 100644 --- a/umbra/filereader.py +++ b/umbra/filereader.py @@ -60,10 +60,6 @@ class FileReader (ABC): self._path = pth - - - - class CSVReader(FileReader): def __init__(self, path, type): @@ -143,7 +139,6 @@ class TxtWriter(FileWriter): result: the results, type unknown """ # For now assume source, shadow, stat format - source_results, shadow_results, info = results final = np.empty(0) for i in range(len(source_results)): @@ -193,15 +188,18 @@ class CSVWriter(FileWriter): writer.writerows(sc) writer.writerow(info) + def write_multiple(self, dir, dict): + for key, results in dict.items(): + path = self._participant_path(dir, key) + self.write(path, results) + # TODO: Test per-participant writing - def _participant_path(part_number): + def _participant_path(self, dir, key): """ Create a path for the appropriate participant. Args: int part_number, participant number. Returns: participath: string, a path towards a directory for participant part_number """ - pth = self.path+"/"+part_number - os.mkdir(pth) - participath = pth+"/"+part_number - return participath + pth = dir+"/"+key + return pth diff --git a/umbra/model.py b/umbra/model.py index ab1705a3..69027142 100644 --- a/umbra/model.py +++ b/umbra/model.py @@ -1,5 +1,5 @@ from statistics import Statistics - +from utils import Utils as ut class Model: """Internal data representation and processing.""" @@ -60,21 +60,23 @@ class Model: def analysis_complete(self): """Check whether self._analysis_results has a value.""" - return self._analysis_results is not None + return self._analysis_results is not None \ + or self._multi_results is not None def compare(self): """"Run the analyses and saves the results.""" if self.has_source() and self.has_shadow(): - print(self._multi_data_shadow, self.has_shadow()) if self._multi_data_shadow: for key, data in self._multi_data_shadow.items(): - if self._id in key: + print(key,self._id, ut.filter_key(key)) + if self._id is ut.filter_key(key): result = self._stats.analyze(self._data_source, data) self.add_to_dict(key+"_res", result, self._multi_results) else: print("passed") pass else: + print("Single analysis") self._analysis_results = \ self._stats.analyze(self._data_source, self._data_shadow) else: diff --git a/umbra/utils.py b/umbra/utils.py index 4d169ab1..6e03cf40 100644 --- a/umbra/utils.py +++ b/umbra/utils.py @@ -37,13 +37,38 @@ class Utils: return "%02i:%02i:%02i.%03i" % (h, m, s, ms) def shadow_regex(path): - match = re.search("AO\d", path) + match = re.search("\d_AO[0-9]+", path) if match: - return match.group() + key = match.group() + if ".csv" in path: + key = key+"csv" + return key + + def filter_key(string): + match = re.search("AO[0-9]+", string) + if match: + if len(match.group()[2:]) > 2: + return match.group()[2:4] + return match.group()[2:] def id_regex(path): match = re.search("\d\.TextGrid", path) # Make sure that not different number accidentally if match: return match.group()[0] - + + + def add_to_dict(key, value, dict): + """Checks if key already exists in a dictionary + before adding new key-value pair + + Args: + key: Key to add to dictionary + value: Corresponding value to add to dictionary + dict: Dictionary to add key-value pair to + """ + if key not in dict: + dict[key] = value + else: + raise ValueError("Key '{}' already exists in {}." + .format(key, dict)) diff --git a/umbra/view.py b/umbra/view.py index c1fada4a..5878ecca 100644 --- a/umbra/view.py +++ b/umbra/view.py @@ -1,6 +1,7 @@ import tkinter as tk from tkinter import ttk, W, filedialog # tk.ttk yields an AttributeError from functools import partial +from utils import Utils as ut class View: """Handle GUI and I/O elements of the program.""" @@ -12,7 +13,8 @@ class View: self._elements = {} # Dict to avoid large number of attributes self._selected_source = "No file selected" self._selected_shadow = "No file selected" - self._options = Options(self) + # self._options = Options(self) + # Options are not redundant quite yet self._actionlistener = None # Initialisation of visual and iteractive elements is listed top-down @@ -73,6 +75,7 @@ class View: if paths == []: filebox.set("No file selected") + filebox['values'] = [] else: filenames = [] for path in paths: @@ -123,7 +126,9 @@ class View: 'delete shadow', 'compare', 'save', - 'select_folder' + 'select_folder', + 'rm_all source', + 'rm_all shadow' ] if key in controllerkeys: @@ -178,7 +183,17 @@ class View: self._create_button('delete {}'.format(type), frame, 'Delete {}'.format(type), - 6, column) + 7, column) + self._create_button('rm_all {}'.format(type), + frame, + 'Delete all {} files'.format(type), + 8, column) + + if type == 'shadow': + self._create_button('select_folder', frame, + 'Select {} folder'.format(type), + 6, column) + def _create_button(self, key, frame, text, row, column): @@ -200,7 +215,7 @@ class View: action_function = partial(self._perform_action, key=key) button.bind('<Button>', action_function) - self._add_to_dict(key, button, self._elements) + ut.add_to_dict(key, button, self._elements) def _create_combobox(self, key, frame, type, row, column): """Create combobox, a drop-down file selection widget. @@ -214,19 +229,17 @@ class View: Raises: ValueError: Key already exists """ - - selection = tk.StringVar() - selection.set("No file selected") # TODO: Does not show up initially filebox = ttk.Combobox(frame, - textvariable=selection, state="readonly", width=20) + filebox.set("No file selected") + filebox.grid(column=column, row=row, sticky=W, padx=10, pady=10) action_function = partial(self._perform_action, key=key) filebox.bind('<Button>', action_function) - self._add_to_dict(key, filebox, self._elements) + ut.add_to_dict(key, filebox, self._elements) def _create_label(self, key, frame, text, row, column): """Create textual label and place it in the given frame. @@ -242,23 +255,11 @@ class View: """ label = tk.Label(frame, text=text) label.grid(column=column, row=row, columnspan=2) - self._add_to_dict(key, label, self._elements) + ut.add_to_dict(key, label, self._elements) - @staticmethod - def _add_to_dict(key, value, dict): # TODO: Definitely better in utils - """Checks if key already exists in a dictionary - before adding new key-value pair - - Args: - key: Key to add to dictionary - value: Corresponding value to add to dictionary - dict: Dictionary to add key-value pair to - """ - if key not in dict: - dict[key] = value - else: - raise ValueError("Key '{}' already exists in {}." - .format(key, dict)) + def _button_status(self, key, mode): + button = self._elements[key] + button['state'] = mode @property def actionlistener(self): @@ -270,7 +271,7 @@ class View: def selected(self, type): selected = self._elements['file {}'.format(type)].get() - if selected == "No file selected": + if selected == "No file selected" or not selected: return None else: return selected @@ -288,19 +289,28 @@ class Options(View): self._build_menu() def _build_menu(self): + """ + Build the menu. + """ self._add_menu_tab("File", self._menu_tabs) self._add_menu_tab("Options", self._menu_tabs) self._add_command("File", "Select folder", "select_folder") for tab in self._menu_tabs: self._menu_bar.add_cascade(label=tab, menu=self._menu_tabs[tab]) self._view._window.config(menu=self._menu_bar) - self._view._add_to_dict("menu", self._menu_bar, self._view._elements) + ut.add_to_dict("menu", self._menu_bar, self._view._elements) def _add_menu_tab(self, key, dict): + """ + Add a tab to the menu. + """ tab = tk.Menu(self._menu_bar, tearoff=0) dict[key] = tab def _add_command(self, tab, label, command): + """ + Add a command to the menu. + """ tab = self._menu_tabs[tab] action_function = partial(self._view._perform_action, key=command) tab.add_command(label=label) -- GitLab