From 30f8d040c8decca45cae97e9aa764975757cd286 Mon Sep 17 00:00:00 2001 From: Ross Kruger Date: Fri, 21 Jun 2024 11:27:40 +0100 Subject: [PATCH] start threading --- anon_gui.py | 128 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 109 insertions(+), 19 deletions(-) diff --git a/anon_gui.py b/anon_gui.py index a242145..7ea4b96 100644 --- a/anon_gui.py +++ b/anon_gui.py @@ -77,11 +77,74 @@ import time import datetime import contextlib +import threading + # import watchdog.observers __VERSION__ = "0.1" __APP_NAME__ = "Uploader" +# Define notification event for thread completion +EVT_RESULT_ID = wx.NewId() + +def EVT_RESULT(win, func): + """Define Result Event.""" + win.Connect(-1, -1, EVT_RESULT_ID, func) + +class ResultEvent(wx.PyEvent): + """Simple event to carry arbitrary result data.""" + def __init__(self, data): + """Init Result Event.""" + wx.PyEvent.__init__(self) + self.SetEventType(EVT_RESULT_ID) + self.data = data + +# Thread class that executes processing +class WorkerThread(threading.Thread): + """Worker Thread Class.""" + def __init__(self, notify_window): + """Init Worker Thread Class.""" + threading.Thread.__init__(self) + self._notify_window = notify_window + self._want_abort = 0 + # This starts the thread running on creation, but you could + # also make the GUI thread responsible for calling this + self.start() + + def run(self): + """Run Worker Thread.""" + # This is the code executing in the new thread. Simulation of + # a long process (well, 10s here) as a simple loop - you will + # need to structure your processing so that you periodically + # peek at the abort variable + for i in range(10): + time.sleep(1) + if self._want_abort: + # Use a result of None to acknowledge the abort (of + # course you can use whatever you'd like or even + # a separate event type) + wx.PostEvent(self._notify_window, ResultEvent(None)) + return + # Here's where the result would be returned (this is an + # example fixed result of the number 10, but it could be + # any Python object) + wx.PostEvent(self._notify_window, ResultEvent(10)) + + def abort(self): + """abort worker thread.""" + # Method for use by main thread to signal an abort + self._want_abort = 1 + +def resource_path(relative_path): + """ Get absolute path to resource, works for dev and for PyInstaller """ + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, relative_path) + anonymizer = dicognito.anonymizer.Anonymizer() # determine if application is a script file or frozen exe @@ -194,6 +257,7 @@ def annonymise( i += 1 # new_filepath = Path(output_dir).joinpath(f"IMG_{i:03}.dcm") + logger.debug(f"anon: {n}") anonymize_file(file, new_filepath) # progress_dialog.Destroy() @@ -201,6 +265,7 @@ def annonymise( return new_filepath +@logger.catch() def anonymize_file(file: Path, new_filepath: Path, remove_original: bool = False): logger.debug(f"Annonymise file, in={file}, out={new_filepath}") # Ensure the output folder exists @@ -517,6 +582,8 @@ class AnonGui(wx.App): self.rqst = None + self.worker = None + wx.App.__init__(self, redirect, filename) def my_listener(self, message, arg2=None): @@ -571,7 +638,7 @@ class AnonGui(wx.App): size=(300, 400), ) - self.frame.SetIcon(wx.Icon("icon/icon1.ico")) + self.frame.SetIcon(wx.Icon(resource_path("icon\icon1.ico"))) self.panel = wx.Panel(self.frame, wx.ID_ANY) @@ -834,6 +901,16 @@ class AnonGui(wx.App): if input_dir.is_dir(): logger.debug("is dir") files = list(Path(input_dir).rglob("*.dcm")) + + if not files: + # TODO load from DICOMDIR + to_exclude = ("DICOMDIR") + files = [i for i in Path(input_dir).rglob("*") if i.is_file() and i.name not in to_exclude] + + + + + else: return @@ -859,20 +936,25 @@ class AnonGui(wx.App): with self.progress(len(files), "Annonymise files") as p: # p.SetRange(len(files)) + i: int = 0 for n, file in enumerate(files): # progress_dialog.Update(n) p.update(n) new_filepath: Path # Get next available filename - i: int = 0 while os.path.exists( new_filepath := output_dir.joinpath(f"IMG_{i:03}.dcm") ): i += 1 # new_filepath = Path(output_dir).joinpath(f"IMG_{i:03}.dcm") - dataset, filepath = anonymize_file(file, new_filepath, remove_original=remove_original) - self.add_datails_to_file_detail_map(dataset, filepath) + logger.debug(f"anon: {n}") + try: + dataset, filepath = anonymize_file(file, new_filepath, remove_original=remove_original) + except pydicom.errors.InvalidDicomError: + continue + self.add_datails_to_file_detail_map(dataset, filepath) + wx.GetApp().Yield() # progress_dialog.Destroy() self.LoadDir(self.dir) @@ -942,7 +1024,7 @@ base site url: {self.base_site_url} info.SetName(__APP_NAME__) info.SetVersion(__VERSION__) info.SetDescription(description) - icon_path = "icon/icon1.png" + icon_path = resource_path("icon\icon1.png") desired_width = 200 desired_height = 200 @@ -1136,7 +1218,12 @@ base site url: {self.base_site_url} files = [Path(self.dir, f) for f, file_hash in files] for f in files: - os.remove(f) + try: + os.remove(f) + except OSError: + logger.debug(f"File to delete not found: {f}") + pass + if load_dir: self.LoadDir(clear_file_detail_map=True) @@ -1252,20 +1339,22 @@ base site url: {self.base_site_url} hashes = [] for n, f in enumerate(self.files): + logger.debug(f"update {n}") # progress_dialog.Update(n) - p.update(n) + #p.update(n) # dataset = pydicom.read_file(f) if f in self.file_detail_map: d, hash, d2 = self.file_detail_map[f] - print(f"{n}: in map") + #print(f"{n}: in map") else: + logger.debug("read: {f}") try: dataset = pydicom.dcmread(f) except FileNotFoundError: return - print("recalc") + #print("recalc") d, hash, d2 = self.add_datails_to_file_detail_map(dataset, f) hashes.append(hash) @@ -1283,18 +1372,18 @@ base site url: {self.base_site_url} except: logger.debug(f"Unable to get Series Instance UID: {f.name}") - logger.debug(d) - DATA[n] = d - self.list_ctrl.Append(d) - self.list_ctrl.SetItemPyData(n, {"file": f, "hash": hash}) - self.list_ctrl.SetItemData(n, n) + #logger.debug(d) + #DATA[n] = d + #self.list_ctrl.Append(d) + #self.list_ctrl.SetItemPyData(n, {"file": f, "hash": hash}) + ##self.list_ctrl.SetItemData(n, n) if f in self.file_duplicate_check: if self.file_duplicate_check[f]["id"]: self.list_ctrl.SetItemBackgroundColour(n, wx.RED) n = n + 1 - self.list_ctrl.UpdateData(DATA) + #self.list_ctrl.UpdateData(DATA) self.populate_series_grid() self.frame.Refresh() @@ -1347,9 +1436,9 @@ base site url: {self.base_site_url} duplicate_files_not_uploaded = [] logger.debug("Upload files") for f in self.files: - if f in self.file_duplicate_check: - # duplicate the api return format so ClearFiles works - duplicate_files_not_uploaded.append((f.name, "...")) + if f in self.file_duplicate_check and self.file_duplicate_check[f]["id"]: + # duplicate the api return format so ClearFiles works + duplicate_files_not_uploaded.append((f.name, "...")) else: files_to_upload.append(("files", open(str(f), "rb"))) @@ -1420,7 +1509,7 @@ base site url: {self.base_site_url} if self.clear_files_on_upload: all_files = upload_file_list + duplicate_file_list + duplicate_files_not_uploaded print(all_files) - self.ClearFiles(event=None, load_dir=True, files=all_files) + wx.CallAfter(self.ClearFiles,event=None, load_dir=True, files=all_files) def upload_complete_message(self, upload_file_list, duplicate_file_list, failed, duplicate_files_not_uploaded): wx.MessageBox( @@ -1501,3 +1590,4 @@ def launchGUI( if __name__ == "__main__": app() +