From fcf086bdee1655b1ae7b8db7395f86a3a2a5dcce Mon Sep 17 00:00:00 2001 From: gideonstar Date: Thu, 12 Oct 2023 14:23:35 +0200 Subject: [PATCH] Erste Version --- .gitignore | 4 + LICENSE | 13 +++ Resources/icon.ico | Bin 0 -> 100163 bytes compile.bat | 11 +++ drives.py | 19 ++++ main.py | 10 +++ registry.py | 49 +++++++++++ requirements.txt | 3 + warchive.py | 213 +++++++++++++++++++++++++++++++++++++++++++++ wconfig.py | 80 +++++++++++++++++ wmain.py | 53 +++++++++++ 11 files changed, 455 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Resources/icon.ico create mode 100644 compile.bat create mode 100644 drives.py create mode 100644 main.py create mode 100644 registry.py create mode 100644 requirements.txt create mode 100644 warchive.py create mode 100644 wconfig.py create mode 100644 wmain.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55dae9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +__pycache__ +venv +Dicom Extractor 2023.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f3bbedb --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + 14 rue de Plaisance, 75014 Paris, France + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. \ No newline at end of file diff --git a/Resources/icon.ico b/Resources/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ed32cef434751d2995159ce4f94588e7425e544f GIT binary patch literal 100163 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 MqHV{fgVekK1A79&>= 1 + + return drives diff --git a/main.py b/main.py new file mode 100644 index 0000000..f95ae93 --- /dev/null +++ b/main.py @@ -0,0 +1,10 @@ +from PyQt6.QtWidgets import QApplication +from wmain import wMain + +version = "20231012" + +if __name__ == "__main__": + app = QApplication([]) + app.setStyle("Fusion") + win = wMain(version) + app.exec() diff --git a/registry.py b/registry.py new file mode 100644 index 0000000..cbf627b --- /dev/null +++ b/registry.py @@ -0,0 +1,49 @@ +import winreg + + +class Registry: + def __init__(self): + self.regInit() + self.ArchivePath, self.CDRomPath = self.regRead() + + # Initialize registry with values if there are none + @staticmethod + def regInit(): + reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + + # Key + try: + key = winreg.OpenKeyEx(reg, r"SOFTWARE\Dicom Extractor 2023", 0, winreg.KEY_ALL_ACCESS) + except FileNotFoundError: + winreg.CreateKey(reg, r"SOFTWARE\Dicom Extractor 2023") + key = winreg.OpenKeyEx(reg, r"SOFTWARE\Dicom Extractor 2023", 0, winreg.KEY_ALL_ACCESS) + + # Value ArchivePath + try: + _ = winreg.QueryValueEx(key, "ArchivePath") + except FileNotFoundError: + winreg.SetValueEx(key, "ArchivePath", 0, winreg.REG_SZ, "Init") + + # Value CDRomPath + try: + _ = winreg.QueryValueEx(key, "CDRomPath") + except FileNotFoundError: + winreg.SetValueEx(key, "CDRomPath", 0, winreg.REG_SZ, "C") + + # Read values + @staticmethod + def regRead(): + reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + key = winreg.OpenKeyEx(reg, r"SOFTWARE\Dicom Extractor 2023", 0, winreg.KEY_ALL_ACCESS) + archivePath = winreg.QueryValueEx(key, "ArchivePath") + cdromPath = winreg.QueryValueEx(key, "CDRomPath") + + return archivePath[0], cdromPath[0] + + # Write values + @staticmethod + def regWrite(ArchivePath, CDRomPath): + reg = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) + key = winreg.OpenKeyEx(reg, r"SOFTWARE\Dicom Extractor 2023", 0, winreg.KEY_ALL_ACCESS) + winreg.SetValueEx(key, "ArchivePath", 0, winreg.REG_SZ, ArchivePath) + winreg.SetValueEx(key, "CDRomPath", 0, winreg.REG_SZ, CDRomPath) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..022f466 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +PyQt6 +pydicom +nuitka diff --git a/warchive.py b/warchive.py new file mode 100644 index 0000000..f0b6cf9 --- /dev/null +++ b/warchive.py @@ -0,0 +1,213 @@ +import os +import glob +import threading +import time +import pydicom +import shutil +import random +from registry import Registry +from PyQt6.QtWidgets import QDialog +from PyQt6.QtWidgets import QLabel +from PyQt6.QtWidgets import QPushButton +from PyQt6.QtWidgets import QStyle + + +class wArchive(QDialog): + def __init__(self): + super().__init__() + + self.setFixedSize(400, 400) + self.setWindowTitle("Archiviere...") + self.setStyleSheet("background-color: white") + + pixmap = QStyle.StandardPixmap.SP_DriveNetIcon + icon = self.style().standardIcon(pixmap) + self.setWindowIcon(icon) + + column1w = 180 + column2s = column1w + 20 + column2w = 400 - column2s - 10 + + self.lLine1 = QLabel("Starte Archivierung:", self) + self.lLine1.setGeometry(10, 10, column1w, 20) + self.tLine1 = QLabel("", self) + self.tLine1.setGeometry(column2s, 10, column2w, 20) + + self.lLine2 = QLabel("Pfad zum Archiv:", self) + self.lLine2.setGeometry(10, 30, column1w, 20) + self.tLine2 = QLabel("", self) + self.tLine2.setGeometry(column2s, 30, column2w, 20) + + self.lLine3 = QLabel("CD-Rom-Laufwerk:", self) + self.lLine3.setGeometry(10, 50, column1w, 20) + self.tLine3 = QLabel("", self) + self.tLine3.setGeometry(column2s, 50, column2w, 20) + + self.lLine4 = QLabel("DICOMDIR-Datei gefunden:", self) + self.lLine4.setGeometry(10, 70, column1w, 20) + self.tLine4 = QLabel("", self) + self.tLine4.setGeometry(column2s, 70, column2w, 20) + + self.lLine5 = QLabel("DICOM-Verzeichnis gefunden:", self) + self.lLine5.setGeometry(10, 90, column1w, 20) + self.tLine5 = QLabel("", self) + self.tLine5.setGeometry(column2s, 90, column2w, 20) + + self.lLine6 = QLabel("Archiv beschreibbar:", self) + self.lLine6.setGeometry(10, 110, column1w, 20) + self.tLine6 = QLabel("", self) + self.tLine6.setGeometry(column2s, 110, column2w, 20) + + self.lLine7 = QLabel("DICOM-Dateien gefunden:", self) + self.lLine7.setGeometry(10, 130, column1w, 20) + self.tLine7 = QLabel("", self) + self.tLine7.setGeometry(column2s, 130, column2w, 20) + + self.lLine8 = QLabel("Name:", self) + self.lLine8.setGeometry(10, 150, column1w, 20) + self.tLine8 = QLabel("", self) + self.tLine8.setGeometry(column2s, 150, column2w, 20) + + self.lLine9 = QLabel("Geburtsdatum:", self) + self.lLine9.setGeometry(10, 170, column1w, 20) + self.tLine9 = QLabel("", self) + self.tLine9.setGeometry(column2s, 170, column2w, 20) + + self.lLine10 = QLabel("Dateien:", self) + self.lLine10.setGeometry(10, 190, column1w, 20) + self.tLine10 = QLabel("", self) + self.tLine10.setGeometry(column2s, 190, column2w, 20) + + self.bArchive = QPushButton("Archivieren", self) + self.bArchive.setGeometry(10, 350, 380, 40) + self.bArchive.setEnabled(False) + self.bArchive.clicked.connect(self.copyData) + + archive = threading.Thread(target=self.archive) + archive.start() + + time.sleep(0.1) + self.exec() + + def archive(self): + okay = "✓" + notokay = "✗" + + self.tLine1.setText(okay) + self.tLine1.setStyleSheet("color: green") + + reg = Registry() + self.ArchivePath, self.CDRomPath = reg.regRead() + self.tLine2.setText(self.ArchivePath) + self.tLine3.setText(self.CDRomPath + ":\\") + + # Logic + dicomfile = False + dicomdir = False + target_writable = False + has_dicom_files = False + has_pName = False + has_pBirth = False + + # Check if DICOMDIR file exists + if os.path.isfile(os.path.join(self.CDRomPath + ":", os.sep, "DICOMDIR")): + dicomfile = True + self.tLine4.setText(okay) + self.tLine4.setStyleSheet("color: green") + else: + self.tLine4.setText(notokay) + self.tLine4.setStyleSheet("color: red") + + # Check if DICOM directory exists + if os.path.isdir(os.path.join(self.CDRomPath + ":", os.sep, "DICOM")): + dicomdir = True + self.tLine5.setText(okay) + self.tLine5.setStyleSheet("color: green") + else: + self.tLine5.setText(notokay) + self.tLine5.setStyleSheet("color: red") + + # Check if ArchivePath is writable + if os.access(self.ArchivePath, os.W_OK) is True: + target_writable = True + self.tLine6.setText(okay) + self.tLine6.setStyleSheet("color: green") + else: + self.tLine6.setText(notokay) + self.tLine6.setStyleSheet("color: red") + + # List of all files under DICOM directory + path = os.path.join(self.CDRomPath + ":", os.sep, "DICOM") + self.files = [] + for x in os.walk(path): + for y in glob.glob(os.path.join(x[0], "*")): + if os.path.isfile(y): + self.files.append(y) + + # Get PatientsName from first file + self.pName = "" + self.pBirth = "" + + try: + dicom = pydicom.read_file(self.files[0]) + self.pName = str(dicom.PatientName) + self.pBirth = str(dicom.PatientBirthDate) + has_dicom_files = True + has_pName = True + has_pBirth = True + self.tLine7.setText(okay) + self.tLine7.setStyleSheet("color: green") + except: + self.tLine7.setText(notokay) + self.tLine7.setStyleSheet("color: red") + + # Output pName and pBirth + if self.pName and self.pBirth: + self.tLine8.setText(self.pName) + self.tLine8.setStyleSheet("color: green") + self.tLine9.setText(self.pBirth) + self.tLine9.setStyleSheet("color: green") + else: + self.tLine8.setText(notokay) + self.tLine8.setStyleSheet("color: red") + self.tLine9.setText(notokay) + self.tLine9.setStyleSheet("color: red") + + # Output file count + self.tLine10.setText(str(len(self.files))) + + # Activate bArchive + if dicomfile and dicomdir and target_writable and has_dicom_files and has_pBirth and has_pName: + self.bArchive.setEnabled(True) + + def copyData(self): + self.bArchive.setEnabled(False) + + source = self.files + + # LastName^FirstName^Birth + mainFolder = os.path.join(self.ArchivePath, self.pName + "^" + self.pBirth) + try: + os.mkdir(mainFolder) + except: + pass + + # LastName^FirstName^Birth\1234 + subFolder = self.randomString(4) + os.mkdir(os.path.join(mainFolder, subFolder)) + + for file in source: + shutil.copy(file, os.path.join(mainFolder, subFolder)) + + self.close() + + @staticmethod + def randomString(length): + string = "" + chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890" + + for _ in range(length): + char = random.choice(chars) + string = string + char + + return string diff --git a/wconfig.py b/wconfig.py new file mode 100644 index 0000000..499145b --- /dev/null +++ b/wconfig.py @@ -0,0 +1,80 @@ +from registry import Registry +from drives import Drives +from PyQt6.QtWidgets import QPushButton +from PyQt6.QtWidgets import QLabel +from PyQt6.QtWidgets import QLineEdit +from PyQt6.QtWidgets import QComboBox +from PyQt6.QtWidgets import QStyle +from PyQt6.QtWidgets import QDialog +from PyQt6 import QtCore + + +class wConfig(QDialog): + def __init__(self): + super().__init__() + + self.reg = Registry() + self.drives = Drives() + + self.setWindowTitle("Konfiguration") + self.setFixedSize(300, 120) + + pixmap = QStyle.StandardPixmap.SP_DriveNetIcon + icon = self.style().standardIcon(pixmap) + self.setWindowIcon(icon) + + # Window should stay on top and only close button is visible + self.setWindowFlags(QtCore.Qt.WindowType.WindowStaysOnTopHint | QtCore.Qt.WindowType.WindowCloseButtonHint) + + # ArchivePath label and input field + self.lArchivePath = QLabel("Pfad zum Archiv:", self) + self.lArchivePath.setGeometry(10, 10, 100, 20) + + self.leArchivePath = QLineEdit(self) + self.leArchivePath.setText(self.reg.ArchivePath) + self.leArchivePath.setGeometry(155, 10, 135, 20) + + # CDRomPath label and drop down menu + self.lCDRomPath = QLabel("CD-Rom-Laufwerk:", self) + self.lCDRomPath.setGeometry(10, 40, 100, 20) + + self.ddCDRomPath = QComboBox(self) + self.ddCDRomPath.addItems(self.drives.list) + self.ddCDRomPath.setCurrentText(self.reg.CDRomPath) + self.ddCDRomPath.setGeometry(155, 40, 135, 20) + + # Apply + self.bApply = QPushButton("Übernehmen", self) + self.bApply.setGeometry(10, 70, 135, 40) + self.bApply.clicked.connect(self.bApplyFunction) + + # Cancel / Close + self.bClose = QPushButton("Schließen", self) + self.bClose.setGeometry(155, 70, 135, 40) + self.bClose.clicked.connect(self.bCloseFunction) + + self.exec() + + def bApplyFunction(self): + # Get text from ArchivePath input field + ArchivePath = self.leArchivePath.text() + + # Remove all backslashes from the end of the string + while ArchivePath[-1] == "\\": + ArchivePath = ArchivePath[:-1] + + # Get selected item from CDRomPath drop down menu + CDRomPath = self.ddCDRomPath.currentText() + + # Set both variables in the running app + self.reg.ArchivePath = ArchivePath + self.reg.CDRomPath = CDRomPath + + # Write both variables to the registry + self.reg.regWrite(ArchivePath, CDRomPath) + + def bCloseFunction(self): + self.close() + + def closeEvent(self, event): + self.close() diff --git a/wmain.py b/wmain.py new file mode 100644 index 0000000..02ce56d --- /dev/null +++ b/wmain.py @@ -0,0 +1,53 @@ +from warchive import wArchive +from wconfig import wConfig +from PyQt6.QtWidgets import QMainWindow +from PyQt6.QtWidgets import QPushButton +from PyQt6.QtWidgets import QApplication +from PyQt6.QtWidgets import QLabel +from PyQt6.QtGui import QIcon +from PyQt6.QtCore import Qt + + +class wMain(QMainWindow): + def __init__(self, version): + super().__init__() + + self.setWindowTitle("DICOM Extractor 2023") + self.setFixedSize(400, 180) + self.setWindowIcon(QIcon("Resources\\icon.ico")) + + # Archive button + self.bArchive = QPushButton("Archivieren", self) + self.bArchive.setGeometry(10, 10, 380, 100) + self.bArchive.clicked.connect(self.bArchiveFunction) + + # Config button + self.bConfig = QPushButton("Konfiguration", self) + self.bConfig.setGeometry(10, 120, 185, 40) + self.bConfig.clicked.connect(self.bConfigFunction) + + # Quit button + self.bQuit = QPushButton("Beenden", self) + self.bQuit.setGeometry(205, 120, 185, 40) + self.bQuit.clicked.connect(self.bQuitFunction) + + # Version + self.lVersion = QLabel("Version " + version, self) + self.lVersion.setGeometry(10, 162, 380, 20) + self.lVersion.setAlignment(Qt.AlignmentFlag.AlignHCenter) + self.setStyleSheet("QLabel{ color: grey; font-size: 10px; }") + + self.show() + + def bArchiveFunction(self): + self.wArchive = wArchive() + + def bConfigFunction(self): + self.wConfig = wConfig() + + @staticmethod + def bQuitFunction(): + QApplication.quit() + + def closeEvent(self, event): + QApplication.quit()