diff --git a/umbra/controller.py b/umbra/controller.py index 9716d29d1a03886a8ca3d2d2b3d204923bbf56ac..b4f63999b52aa9c198b7b4ade7c06348e2547784 100644 --- a/umbra/controller.py +++ b/umbra/controller.py @@ -1,5 +1,5 @@ -from filereader import FileReader, CSVReader, TxtReader # , FileWriter -# Disabled the FileWriter import since it does not exist yet. +from filereader import FileReader, CSVReader, TxtReader +from filereader import FileWriter, CSVWriter, TxtWriter class Controller: @@ -11,8 +11,9 @@ class Controller: self._model = model self._view = view self._view.actionlistener = self.actionlistener - self._filereader = CSVReader(None, None) # TODO: Deal with csv OR txt - # self._filewriter = FileWriter() + + self._filereader = CSVReader("","") # TODO: Deal with csv OR txt + self._filewriter = CSVWriter() # Lists of paths, represented as string self._source_files = [] @@ -38,6 +39,11 @@ class Controller: self._compare_files() elif action == 'save': self._save_results() + elif action == 'save_csv': + self._filewriter = CSVWriter() + elif action == 'save_txt': + self._filewriter = TxtWriter() + def _select_files(self, type): """Select files and add them to files list corresponding to type. @@ -73,7 +79,8 @@ class Controller: self._view.update_message('no shadow') else: self._view.update_message('files ok') - self._read_files(None) # TODO: Adapt to future implementation + self._read_files("source") + self._read_files("shadow") self._model.compare() self._view.update_message('comparison complete') @@ -83,8 +90,9 @@ class Controller: self._view.update_message('no comparison') else: path = self._view.ask_save_location() - results = self._model.results # TODO: Check exact syntax when implemented + results = self._model._analysis_results self._filewriter.write(path, results) + # results are of form sc, sh, info self._view.update_message('saved') def _read_files(self, type): @@ -94,12 +102,12 @@ class Controller: type (str): Role of file ('source' or 'shadow') """ # TODO - # data = filereader.read(path, type) - # data_property = getattr(self._model, 'data_{}'.format(type)) - # data_property(data) - # Temporary 'solution' for testing below - self._filereader = CSVReader(self._source_files[0], - self._shadow_files[0]) - self._model.data_source, self._model.data_shadow =\ - self._filereader.read() + path = self._source_files[0] + print(path) + self._filereader = CSVReader(path, type) + data = self._filereader.read() + if type == "source": # Fix is dirtier than I can talk + self._model._data_source = data + else: + self._model._data_shadow = data diff --git a/umbra/filereader.py b/umbra/filereader.py index fff52920865d7870b53e86ca4257e075208c184f..a74e0c063349a6f6804c2c1bc815ae16d78a7178 100644 --- a/umbra/filereader.py +++ b/umbra/filereader.py @@ -1,6 +1,8 @@ import pandas as pd +import numpy as np from abc import ABC, abstractmethod from words import SourceWord, ShadowWord, Sentence +import csv class FileReader (ABC): @@ -36,65 +38,154 @@ class FileReader (ABC): kind of word it should be converted. Returns: - words: list of the created Word instances. + words: type Sentence, object containing list of words. """ - words = [] + ws = [] for row in df.itertuples(name="Words"): if word_type == "source": word = SourceWord(row.Word.lower(), row.Onset, row.Offset) else: word = ShadowWord(row.Word.lower(), row.Onset, row.Offset) - words.append(word) + ws.append(word) + words = Sentence(ws) return words class CSVReader(FileReader): - def __init__(self, path_source, path_shadow): - """Constructor - + def __init__(self, path, type): + """ Args: - path_source: the internal path to the source file - path_shadow: the internal path to the shadow file + path: string, the internal path to the source file """ - self._path_source = path_source - self._path_shadow = path_shadow + self._path = path + self._type = type # Very dirty fix def read(self): """Method that is used to read the data into a workable format""" - source_df = pd.read_csv(self._path_source, header=None, sep='\n') - source_df = source_df[0].str.split('\t', expand=True) - shadow_df = pd.read_csv(self._path_shadow, header=None, sep='\n') - shadow_df = shadow_df[0].str.split('\t', expand=True) - source_df = self.extract_task_data(source_df) - shadow_df = self.extract_task_data(shadow_df) - source_words = Sentence(self.df_to_words(source_df, "source")) - shadow_words = Sentence(self.df_to_words(shadow_df, "shadow")) - return source_words, shadow_words + df = pd.read_csv(self._path, header=None, sep='\n') + df = df[0].str.split('\t', expand=True) + data = self.extract_task_data(df) + words = self.df_to_words(data, self._type) + return words class TxtReader(FileReader): - def __init__(self, path_source, path_shadow): + def __init__(self, path): """Constructor Args: - path_source: the internal path to the source file - path_shadow: the internal path to the shadow file + path: path to file """ - self._path_source = path_source - self._path_shadow = path_shadow + self._path = path + self._words = None def read(self): - """Method that is used to read the data into a workable format""" - with open(self._path_source, 'r') as source,\ - open(self._path_shadow, 'r') as shadow: - # Does this even work? See warning: - source_df = self.extract_task_data(source) - shadow_df = self.extract_task_data(shadow) - source_words = Sentence(self.df_to_words(source_df, - "source")) - shadow_words = Sentence(self.df_to_words(shadow_df, - "shadow")) - return source_words, shadow_words + """ + Read data into Sentences. + Returns: + words: Sentence containing Words. + """ + with open(self._path, 'r') as data: + self._words = self.extract_task_data(data) + self._words = self.df_to_words(self._words, "header") + return self._words + +class FileWriter(ABC): + def __init__(self): + pass + + @abstractmethod + def write(self): + pass + + def write_per_part(self): + """ + Write result per participant. + """ + for pn, pr in self.data: + results, stats = _format_results(pn, pr) + self.path = _participant_path(pn) + self.write() + + @abstractmethod + def _participant_path(self, number): + pass + + +class TxtWriter(FileWriter): + def __init__(self): + super().__init__() + + def write(self, path, results): + """ + Write a .txt file of analysis results. + Args: + path: string of paths + 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)): + source_word = source_results.words[i] + res = (str(source_word), str(source_word.shadowed)+"\n") + final = np.append(final, res) + final = np.append(final, "Total: "+str(info[0])) + final = np.append(final, "Correct: "+str(info[1])) + final = np.append(final, "Skips: "+str(info[2])) + np.savetxt(path, final, fmt="%s", header="Word|Shadowed") + +# Untested + def _participant_path(part_number): + """ + Create a path for the appropriate participant. + Args: part_number: int of 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+".txt" + return participath + + +class CSVWriter(FileWriter): + def __init__(self): + super().__init__() + + def write(self, path, results): + """ + Write a CSV file of the results. + Args: path: string of path + results: results, type unknown + + """ + source, shadow, info = results + info = "Total " + str(info[0]), "Shadowed " + str(info[1]), "Skipped"\ + " " + str(info[2]) + sc = [] + for entry in source.words: + sc.append([entry.word, entry.onset, entry.offset, entry.shadowed]) + with open(path+'.csv', 'w') as f: + writer = csv.writer(f) + writer.writerow(["Word", "Onset", "Offset", "Shadowed"]) + writer.writerows(sc) + writer.writerow(info) + + # TODO: Test per-participant writing + def _participant_path(part_number): + """ + 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 diff --git a/umbra/model.py b/umbra/model.py index 47124412ec91c8bf086a65f36ed05fb4b4065436..1b1a75990a0e68237f13d479a9d321d0fd0f729c 100644 --- a/umbra/model.py +++ b/umbra/model.py @@ -52,5 +52,8 @@ class Model: def compare(self): """"Run the analyses and saves the results.""" - self._analysis_results = self._stats.analyze(self._data_source, + if self.has_source() and self.has_shadow(): + self._analysis_results = self._stats.analyze(self._data_source, self._data_shadow) + else: + print("This needs fixing") diff --git a/umbra/saa_Romeo.py b/umbra/saa_Romeo.py index 94207ed77b262a736e8be6ffd9e5c7c2f1e8b04d..6b7d87478cfe7ed0e0f037793946a4ef57900fc4 100644 --- a/umbra/saa_Romeo.py +++ b/umbra/saa_Romeo.py @@ -138,12 +138,9 @@ class SaaRomeo(AlignmentStrategy): alignment_source.reverse() alignment_shadow.reverse() - for source_word, shadow_word in zip(alignment_source,alignment_shadow): + for source_word, shadow_word in zip(alignment_source, alignment_shadow): if type(source_word) is not Gap and type(shadow_word) is not Gap: if source_word == shadow_word: source_word.shadowed = True return Sentence(alignment_source), Sentence(alignment_shadow) - - - diff --git a/umbra/view.py b/umbra/view.py index b74c527cde3871c900b0e358991e6dc3496b9209..17d91526146e3f16b92ed1df0f25f2647a502133 100644 --- a/umbra/view.py +++ b/umbra/view.py @@ -56,9 +56,9 @@ class View: # TODO: Deal with multiple files at once for path in paths: - if path == "": # Do not assign an empty file path - # TODO: Error message and close - select_files(type) #prompt again + if path == "": # Do not assign an empty file path + # TODO: Error message and close + select_files(type) # prompt again return paths def update_files(self, paths, type): @@ -123,12 +123,12 @@ class View: 'delete shadow', 'compare', 'save', + ] if key in controllerkeys: self._actionlistener(key) - @staticmethod - def ask_save_location(): + def ask_save_location(self): """Ask user for location to save file.""" path = filedialog.asksaveasfilename(title="Save file", parent=self._window, @@ -136,7 +136,7 @@ class View: ("all files", "*.*"))) if ".txt" not in path: path += ".txt" - if save_path == ".txt": + if path == ".txt": # TODO: Error message & close self.ask_save_location() # Prompt again return path @@ -271,6 +271,7 @@ class View: else: return selected + class Options(View): """GUI control of program options.""" diff --git a/umbra/words.py b/umbra/words.py index 4260b88cf8079214f710df1af0f1fdd7feda6433..2a9c2feb3e38c2fb2a8034f3168e9653760ede73 100644 --- a/umbra/words.py +++ b/umbra/words.py @@ -14,6 +14,7 @@ class Word: def __eq__(self, word): return self._word == word.word + @property def word(self): """Getter for the word. @@ -67,6 +68,14 @@ class Word: """ self._anchor = anchor + def get_difference(self, other): + """Get the difference between the onset of this word and the other. + + Args: + other: the other Word instance + """ + return other.onset - self._onset + class ShadowWord(Word): def __init__(self, word, onset, offset): @@ -165,6 +174,13 @@ class SourceWord(Word): class Sentence(list): def __init__(self, words): list.__init__(self, words) + self.words = words # I am not sure if this is the 'proper' fix def __str__(self): - return " ".join([str(w) for w in self]) + return " ".join([str(w) for w in self.words]) + + def __len__(self): + return len(self.words) + + def __iter__(self): + return iter(self.words)