commit fcf086bdee1655b1ae7b8db7395f86a3a2a5dcce Author: gideonstar Date: Thu Oct 12 14:23:35 2023 +0200 Erste Version 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 0000000..ed32cef Binary files /dev/null and b/Resources/icon.ico differ diff --git a/compile.bat b/compile.bat new file mode 100644 index 0000000..e521ec7 --- /dev/null +++ b/compile.bat @@ -0,0 +1,11 @@ +python -m nuitka^ + --onefile^ + --disable-console^ + --windows-icon-from-ico=Resources\icon.ico^ + --include-data-files=Resources\icon.ico=icon.ico^ + --plugin-enable=pyqt6^ + --include-module=pydicom.encoders.gdcm^ + --include-module=pydicom.encoders.pylibjpeg^ + --output-filename="Dicom Extractor 2023.exe"^ + --remove-output^ + main.py diff --git a/drives.py b/drives.py new file mode 100644 index 0000000..e070175 --- /dev/null +++ b/drives.py @@ -0,0 +1,19 @@ +from ctypes import windll +from string import ascii_uppercase + + +class Drives: + def __init__(self): + self.list = self.getDrives() + + @staticmethod + def getDrives(): + drives = [] + bitmask = windll.kernel32.GetLogicalDrives() + + for letter in ascii_uppercase: + if bitmask & 1: + drives.append(letter) + bitmask >>= 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()