diff --git a/.gitignore b/.gitignore index f98052d..3e97adc 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,9 @@ logs/* .pyc -__pycache__/* \ No newline at end of file +__pycache__/* + +build +dist + +*.build \ No newline at end of file diff --git a/anon_gui.cmd b/anon_gui.cmd new file mode 100644 index 0000000..df82bf3 --- /dev/null +++ b/anon_gui.cmd @@ -0,0 +1,6 @@ + +@echo off +rem This script was created by Nuitka to execute 'anon_gui.exe' with Python DLL being found. +set PATH=C:\PROGRA~1\PYTHON~1;%PATH% +set PYTHONHOME=C:\Program Files\Python311 +"%~dp0anon_gui.exe" %* diff --git a/anon_gui.exe b/anon_gui.exe new file mode 100644 index 0000000..dcddef7 Binary files /dev/null and b/anon_gui.exe differ diff --git a/anon_gui.py b/anon_gui.py index 1e8cda3..65f3e12 100644 --- a/anon_gui.py +++ b/anon_gui.py @@ -55,8 +55,11 @@ import hashlib try: from agw import hyperlink as hl + from agw import pygauge as PG except ImportError: # if it's not there locally, try the wxPython lib. import wx.lib.agw.hyperlink as hl + import wx.lib.agw.pygauge as PG + import wx.lib.agw.thumbnailctrl as TC @@ -565,7 +568,7 @@ class AnonGui(wx.App): self.series_grid = SeriesGrid(self.panel) if self.clear_files_on_start: - self.ClearFiles() + self.ClearFiles(load_dir=False) self.panel.sizer = wx.BoxSizer(wx.VERTICAL) @@ -627,7 +630,6 @@ class AnonGui(wx.App): self.list_ctrl.InsertColumn(6, "Hash") self.list_ctrl.InsertColumn(7, "Series Instance UID") - self.AnnonymiseFilesInDir(self.input_dir, self.dir) # self.UpdateListCtrl() @@ -673,12 +675,23 @@ class AnonGui(wx.App): self.panel.sizer.Add(self.drag_button, 2, wx.EXPAND) self.panel.sizer.Add(self.upload_button, 2, wx.EXPAND) + self.progress_sizer = wx.BoxSizer(wx.VERTICAL) + self.progress_bar = wx.Gauge(self.panel, style=wx.GA_HORIZONTAL|wx.GA_PROGRESS) + #self.progress_bar = PG.PyGauge(self.panel, style=wx.GA_HORIZONTAL|wx.GA_PROGRESS) + #self.progress_bar.SetDrawValue(draw=True, drawPercent=True, font=None, colour=wx.RED, formatString=None) + self.progress_text = wx.StaticText(self.panel, label="Text") + self.progress_sizer.Add(self.progress_bar, 2, wx.EXPAND) + self.progress_sizer.Add(self.progress_text, 2, wx.EXPAND) + self.panel.sizer.Add(self.progress_sizer, 2, wx.EXPAND) + self.progress_sizer.ShowItems(show=False) + # Layout sizers self.panel.SetSizer(self.panel.sizer) self.panel.SetAutoLayout(1) self.panel.sizer.Fit(self.panel) self.frame.Show() # self.spinner.Hide() + self.AnnonymiseFilesInDir(self.input_dir, self.dir) self.Bind(wx.EVT_KEY_DOWN, self.onKeyPress) @@ -717,29 +730,57 @@ class AnonGui(wx.App): #files = order_files_by_dicom_attribute(files) - progress_dialog = wx.ProgressDialog( - "Annonymise files", - "Removing data...", - maximum=len(files), - style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, - ) + #progress_dialog = wx.ProgressDialog( + # "Annonymise files", + # "Removing data...", + # maximum=len(files), + # style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, + #) + with self.progress(len(files), "Annonymise files") as p: + #p.SetRange(len(files)) - for n, file in enumerate(files): - progress_dialog.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") + 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) - self.add_datails_to_file_detail_map(dataset, filepath) - progress_dialog.Destroy() + dataset, filepath = anonymize_file(file, new_filepath) + self.add_datails_to_file_detail_map(dataset, filepath) + #progress_dialog.Destroy() self.LoadDir(self.dir) + + @contextlib.contextmanager + def progress(self, total: int, text: str="Working"): + #self.progress_sizer.Show() + self.progress_text.SetLabelText(text) + self.progress_bar.SetRange(total) + self.progress_sizer.ShowItems(show=True) + self.panel.Layout() + #self.panel.sizer.Fit(self.panel) + try: + class P(): + def __init__(self, gui, total, text): + self.gui = gui + self.text = text + self.total = total + def update(self, n: int): + self.gui.progress_bar.SetValue(n) + self.gui.progress_text.SetLabelText(f"{self.text} : {n}") + yield P(self, total, text) + finally: + #self.progress_sizer.Hide() + self.progress_sizer.ShowItems(show=False) + self.panel.Layout() + #self.panel.SetAutoLayout(1) + #self.panel.sizer.Fit(self.panel) def onKeyPress(self, event): keycode = event.GetKeyCode() @@ -774,7 +815,7 @@ class AnonGui(wx.App): def OnExit(self): if self.clear_files_on_close: - self.ClearFiles(close=True) + self.ClearFiles(load_dir=False) # self.zmq_thread.join() @@ -915,12 +956,12 @@ class AnonGui(wx.App): dlg.ShowModal() - def ClearFiles(self, event=None, close: bool=False): + def ClearFiles(self, event=None, load_dir: bool=True): files = Path(self.dir).glob("*") for f in files: os.remove(f) - if not close: + if load_dir: self.LoadDir(clear_file_detail_map=True) def add_datails_to_file_detail_map(self, dataset, file): @@ -1020,66 +1061,69 @@ class AnonGui(wx.App): n = 0 DATA = {} - progress_dialog = wx.ProgressDialog( - "Loading files", - "Extracting data from files", - maximum=len(self.files), - style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, - ) + #progress_dialog = wx.ProgressDialog( + # "Loading files", + # "Extracting data from files", + # maximum=len(self.files), + # style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, + #) + with self.progress((len(self.files)), "Loading files") as p: + #p.SetRange(len(self.files)) - self.series_map = defaultdict(list) - self.series_map_details = defaultdict(dict) + self.series_map = defaultdict(list) + self.series_map_details = defaultdict(dict) - hashes = [] + hashes = [] - for n, f in enumerate(self.files): - progress_dialog.Update(n) - # dataset = pydicom.read_file(f) + for n, f in enumerate(self.files): + #progress_dialog.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("in map") - else: + if f in self.file_detail_map: + d, hash, d2 = self.file_detail_map[f] + print("in map") + else: + try: + dataset = pydicom.dcmread(f) + except FileNotFoundError: + return + + print("recalc") + d, hash, d2 = self.add_datails_to_file_detail_map(dataset, f) + + hashes.append(hash) + + + series_instances_uid = d2["series_instances_uid"] try: - dataset = pydicom.dcmread(f) - except FileNotFoundError: - return + self.series_map[series_instances_uid].append(f) + self.series_map_details[series_instances_uid][ + "series_description" + ] = d2["series_description"] + self.series_map_details[series_instances_uid]["study_description"] = d2[ + "study_description" + ] + self.series_map_details[series_instances_uid]["name"] = d2["name"] + except: + logger.debug(f"Unable to get Series Instance UID: {f.name}") - print("recalc") - d, hash, d2 = self.add_datails_to_file_detail_map(dataset, f) + 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) - hashes.append(hash) + if f in self.file_duplicate_check: + if self.file_duplicate_check[f]["id"]: + self.list_ctrl.SetItemBackgroundColour(n, wx.RED) - - series_instances_uid = d2["series_instances_uid"] - try: - self.series_map[series_instances_uid].append(f) - self.series_map_details[series_instances_uid][ - "series_description" - ] = d2["series_description"] - self.series_map_details[series_instances_uid]["study_description"] = d2[ - "study_description" - ] - self.series_map_details[series_instances_uid]["name"] = d2["name"] - 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) - - if f in self.file_duplicate_check: - if self.file_duplicate_check[f]["id"]: - self.list_ctrl.SetItemBackgroundColour(n, wx.RED) - - n = n + 1 + n = n + 1 self.list_ctrl.UpdateData(DATA) self.populate_series_grid() - progress_dialog.Destroy() + #progress_dialog.Destroy() #if hashes: # self.check_image_hashes(hashes) @@ -1112,8 +1156,8 @@ class AnonGui(wx.App): for hash, dup in resp.json().items(): self.file_duplicate_check[hash_file_map[hash]] = dup - print(self.file_duplicate_check) - wx.CallAfter(self.UpdateListCtrl) + if resp.json().items(): + wx.CallAfter(self.UpdateListCtrl) self.image_hash_check_in_progress = False def OnColRightClick(self, event): @@ -1136,52 +1180,55 @@ class AnonGui(wx.App): files_to_upload[i : i + n] for i in range(0, len(files_to_upload), n) ] - progress_dialog = wx.ProgressDialog( - "Uploading files", - "Uploading files. This is done in batches...", - maximum=len(chunked_files), - style=wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, - ) + #progress_dialog = wx.ProgressDialog( + # "Uploading files", + # "Uploading files. This is done in batches...", + # maximum=len(chunked_files), + # style=wx.PD_ELAPSED_TIME | wx.PD_ESTIMATED_TIME, + #) + with self.progress(len(chunked_files), "Uploading files (in batches)") as p: + #p.SetRange(len(chunked_files)) - upload_file_list = [] - duplicate_file_list = [] + upload_file_list = [] + duplicate_file_list = [] - for n, files in enumerate(chunked_files): + for n, files in enumerate(chunked_files): - def upload_files(files): - self.rqst.headers["X-CSRFToken"] = self.rqst.cookies["csrftoken"] - # print(self.rqst.headers) - # print(self.rqst.cookies) - resp = self.rqst.post( - f"{self.base_site_url}/api/atlas/upload_dicom", - # data=data, - files=files, - ) + def upload_files(files): + self.rqst.headers["X-CSRFToken"] = self.rqst.cookies["csrftoken"] + # print(self.rqst.headers) + # print(self.rqst.cookies) + resp = self.rqst.post( + f"{self.base_site_url}/api/atlas/upload_dicom", + # data=data, + files=files, + ) - logger.debug(f"Resp: {resp}") - logger.debug(f"{resp.content}") - return resp + logger.debug(f"Resp: {resp}") + logger.debug(f"{resp.content}") + return resp - progress_dialog.Update(n, f"Uploading batch {n}/{len(chunked_files)}") + p.update(n) + #progress_dialog.Update(n, f"Uploading batch {n}/{len(chunked_files)}") - logger.debug(f"n: {n}") - # try to upload the files - resp = upload_files(files) - if resp.status_code == 200: - upload_file_list.extend(resp.json()["uploaded"]) - duplicate_file_list.extend(resp.json()["duplicates"]) + logger.debug(f"n: {n}") + # try to upload the files + resp = upload_files(files) + if resp.status_code == 200: + upload_file_list.extend(resp.json()["uploaded"]) + duplicate_file_list.extend(resp.json()["duplicates"]) - continue + continue - logger.debug(f"n: {n} fail") - # try again if we fail - resp = upload_files(files) - if resp.status_code == 200: - upload_file_list.extend(resp.json()["uploaded"]) - duplicate_file_list.extend(resp.json()["duplicates"]) - continue + logger.debug(f"n: {n} fail") + # try again if we fail + resp = upload_files(files) + if resp.status_code == 200: + upload_file_list.extend(resp.json()["uploaded"]) + duplicate_file_list.extend(resp.json()["duplicates"]) + continue - progress_dialog.Destroy() + #progress_dialog.Destroy() print(upload_file_list) print(duplicate_file_list) @@ -1215,10 +1262,10 @@ class AnonGui(wx.App): @app.callback(invoke_without_command=True) def runGUI( ctx: typer.Context, - #input_dir: Path = "C:\\Temp\\", - input_dir: Path = "/home/ross/temp/in/", - #output_dir: Path = "C:\\temp_upload\\", - output_dir: Path = "/home/ross/temp/out/", + input_dir: Path = "C:\\Temp\\", + #input_dir: Path = "/home/ross/temp/in/", + output_dir: Path = "C:\\temp_upload\\", + #output_dir: Path = "/home/ross/temp/out/", silent_fail: bool = False, clear_files: bool = True, clear_files_on_close: bool = True, diff --git a/anon_gui.spec b/anon_gui.spec index 4891402..d0eda3f 100644 --- a/anon_gui.spec +++ b/anon_gui.spec @@ -1,33 +1,24 @@ # -*- mode: python ; coding: utf-8 -*- -block_cipher = None - - a = Analysis( ['anon_gui.py'], pathex=[], - #binaries=[(r"C:\Users\krugerro\Desktop\rad_tools\.venv\Lib\site-packages\pywin32_system32\pythoncom311.dll", "."), - #(r"C:\Users\krugerro\Desktop\rad_tools\.venv\Lib\site-packages\pywin32_system32\pywintypes311.dll", "."), - #], + binaries=[], datas=[], hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, noarchive=False, ) -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) +pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, a.binaries, - a.zipfiles, a.datas, [], name='anon_gui', @@ -37,7 +28,7 @@ exe = EXE( upx=True, upx_exclude=[], runtime_tmpdir=None, - console=True, + console=False, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, diff --git a/requirements.txt b/requirements.txt index 0c95a60..cab43c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,4 +8,5 @@ requests zmq blake3 loguru -dicom2jp: boolg \ No newline at end of file +dicom2jpg +pyinstaller \ No newline at end of file