# -*- coding: utf-8 -*- """ GEPARD - Gepard-Enabled PARticle Detection Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für Polymerforschung Dresden e. V. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program, see COPYING. If not, see . """ import logging import logging.handlers import traceback import os from io import StringIO from PyQt5 import QtCore, QtWidgets, QtGui from .sampleview import SampleView from .scalebar import ScaleBar from .ramancom.ramancontrol import defaultPath from .ramancom.ramanSwitch import RamanSwitch from .analysis.colorlegend import ColorLegend from .gepardlogging import setDefaultLoggingConfig class GEPARDMainWindow(QtWidgets.QMainWindow): def __init__(self, logger): super(GEPARDMainWindow, self).__init__() self.setWindowTitle("GEPARD") self.resize(900, 700) self.view = SampleView(logger) self.view.imparent = self self.view.ScalingChanged.connect(self.scalingChanged) self.scalebar = ScaleBar(self) self.legend = ColorLegend(self) self.ramanSwitch = RamanSwitch(self) self.view.ScalingChanged.connect(self.scalebar.updateScale) mdiarea = QtWidgets.QMdiArea(self) mdiarea.addSubWindow(self.scalebar) mdiarea.addSubWindow(self.legend) mdiarea.addSubWindow(self.ramanSwitch) self.legend.hide() self.ramanSwitch.hide() subview = mdiarea.addSubWindow(self.view) subview.showMaximized() subview.setWindowFlags(QtCore.Qt.FramelessWindowHint) mdiarea.setOption(QtWidgets.QMdiArea.DontMaximizeSubWindowOnActivation) self.setCentralWidget(mdiarea) self.createActions() self.createMenus() self.createToolBar() self.updateModes() def resizeEvent(self, event): self.scalebar.move(0,self.height()-self.scalebar.height()-30) self.legend.move(self.width()-self.legend.width()-130, 10) self.ramanSwitch.move(5, 5) def closeEvent(self, event): self.view.closeEvent(event) closeAll() @QtCore.pyqtSlot(float) def scalingChanged(self, scale: float): self.zoomInAct.setEnabled(self.view.scaleFactor < 20.0) self.zoomOutAct.setEnabled(self.view.scaleFactor > .01) self.normalSizeAct.setEnabled(self.view.scaleFactor != 1.) @QtCore.pyqtSlot() def open(self, fileName=False): if fileName is False: fileName = QtWidgets.QFileDialog.getOpenFileName(self, "Open Project", defaultPath, "*.pkl")[0] if fileName: self.fname = str(fileName) self.view.open(self.fname) self.scalingChanged(1.) @QtCore.pyqtSlot() def importProject(self, fileName=False): if fileName is False: fileName = QtWidgets.QFileDialog.getOpenFileName(self, "Import Zeiss Zen Project", defaultPath, "*.xml")[0] if fileName: self.fname = str(fileName) self.view.importProject(self.fname) self.scalingChanged(1.) @QtCore.pyqtSlot() def new(self, fileName=False): if fileName is False: fileName = QtWidgets.QFileDialog.getSaveFileName(self, "Create New Project", defaultPath, "*.pkl")[0] if fileName: isValid, msg = self.testFilename(fileName) if isValid: self.fname = str(fileName) self.view.new(self.fname) self.scalingChanged(1.) else: QtWidgets.QMessageBox.critical(self, "Error", msg) @QtCore.pyqtSlot() def testFilename(self, fileName): if self.view.ramanctrl.name == 'RenishawCOM': #the renishawCom does not allow Spaces within filePath if fileName.find(' ') == 0: return False, "File path must not contain spaces." else: return True, "" else: return True, "" @QtCore.pyqtSlot() def about(self): QtWidgets.QMessageBox.about(self, 'GEPARD', "Developed by Complex Fiber Structures GmbH on behalf of Leibniz-IPF Dresden") def createActions(self): fname = os.path.join(os.path.split(__file__)[0], os.path.join('data', 'brand.png')) self.aboutAct = QtWidgets.QAction(QtGui.QIcon(fname), "About Particle Measurment", self) self.aboutAct.triggered.connect(self.about) self.openAct = QtWidgets.QAction("&Open Project...", self) self.openAct.setShortcut("Ctrl+O") self.openAct.triggered.connect(self.open) self.importAct = QtWidgets.QAction("&Import Zeiss Project...", self) self.importAct.setShortcut("Ctrl+I") self.importAct.triggered.connect(self.importProject) self.newAct = QtWidgets.QAction("&New Measurement...", self) self.newAct.setShortcut("Ctrl+N") self.newAct.triggered.connect(self.new) self.exitAct = QtWidgets.QAction("E&xit", self) self.exitAct.setShortcut("Ctrl+Q") self.exitAct.triggered.connect(self.close) self.zoomInAct = QtWidgets.QAction("Zoom &In (25%)", self) self.zoomInAct.setShortcut("Ctrl++") self.zoomInAct.setEnabled(False) self.zoomInAct.triggered.connect(self.view.zoomIn) self.zoomOutAct = QtWidgets.QAction("Zoom &Out (25%)", self) self.zoomOutAct.setShortcut("Ctrl+-") self.zoomOutAct.setEnabled(False) self.zoomOutAct.triggered.connect(self.view.zoomOut) self.normalSizeAct = QtWidgets.QAction("&Normal Size", self) self.normalSizeAct.setShortcut("Ctrl+S") self.normalSizeAct.setEnabled(False) self.normalSizeAct.triggered.connect(self.view.normalSize) self.fitToWindowAct = QtWidgets.QAction("&Fit to Window", self) self.fitToWindowAct.setShortcut("Ctrl+E") self.fitToWindowAct.setEnabled(True) self.fitToWindowAct.triggered.connect(self.view.fitToWindow) self.connectRamanAct = QtWidgets.QAction("Connect to Microscope", self) self.connectRamanAct.setEnabled(True) self.connectRamanAct.triggered.connect(self.view.connectRaman) self.disconnectRamanAct = QtWidgets.QAction("Release Microscope", self) self.disconnectRamanAct.setEnabled(False) self.disconnectRamanAct.triggered.connect(self.view.disconnectRaman) self.opticalScanAct = QtWidgets.QAction("Optical Scan", self) self.opticalScanAct.setEnabled(False) self.opticalScanAct.setCheckable(True) self.opticalScanAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.view.switchMode("OpticalScan"))) self.detectParticleAct = QtWidgets.QAction("Detect Particles", self) self.detectParticleAct.setEnabled(False) self.detectParticleAct.setCheckable(True) self.detectParticleAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.view.switchMode("ParticleDetection"))) self.ramanScanAct = QtWidgets.QAction("Raman Scan", self) self.ramanScanAct.setEnabled(False) self.ramanScanAct.setCheckable(True) self.ramanScanAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.view.switchMode("RamanScan"))) self.snapshotAct = QtWidgets.QAction("Save Screenshot", self) self.snapshotAct.triggered.connect(self.view.takeScreenshot) self.snapshotAct.setDisabled(True) self.configRamanCtrlAct = QtWidgets.QAction("Configure Raman Control", self) self.configRamanCtrlAct.triggered.connect(self.view.configureRamanControl) if self.view.simulatedRaman: self.configRamanCtrlAct.setDisabled(True) self.recalculateCoordAct = QtWidgets.QAction("&Recalculate Coordinate System") self.recalculateCoordAct.setDisabled(True) self.recalculateCoordAct.triggered.connect(self.view.recalculateCoordinateSystem) self.noOverlayAct = QtWidgets.QAction("&No Overlay", self) self.noOverlayAct.setShortcut("1") self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self) self.selOverlayAct.setShortcut("2") self.fullOverlayAct = QtWidgets.QAction("&Full Overlay", self) self.fullOverlayAct.setShortcut("3") self.transpAct = QtWidgets.QAction("&Transparent Overlay", self) self.transpAct.setShortcut("T") self.hideLabelAct = QtWidgets.QAction('&Hide Spectra Numbers', self) self.hideLabelAct.setShortcut("H") self.darkenAct = QtWidgets.QAction("&Darken Image", self) self.darkenAct.setShortcut("D") self.seedAct = QtWidgets.QAction("&Set Color Seed", self) for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct, self.seedAct]: act.setDisabled(True) def updateModes(self, active=None, maxenabled=None): ose, osc, pde, pdc, rse, rsc = [False]*6 if maxenabled == "OpticalScan": ose = True elif maxenabled == "ParticleDetection": ose, pde = True, True elif maxenabled == "RamanScan": ose, pde, rse = True, True, True if active == "OpticalScan" and ose: osc = True elif active == "ParticleDetection" and pde: pdc = True elif active == "RamanScan" and rse: rsc = True self.opticalScanAct.setEnabled(ose) self.opticalScanAct.setChecked(osc) self.detectParticleAct.setEnabled(pde) self.detectParticleAct.setChecked(pdc) self.ramanScanAct.setEnabled(rse) self.ramanScanAct.setChecked(rsc) def unblockUI(self, connected): self.openAct.setEnabled(True) self.importAct.setEnabled(True) self.newAct.setEnabled(True) self.updateConnected(connected) self.exitAct.setEnabled(True) def blockUI(self): self.openAct.setEnabled(False) self.importAct.setEnabled(False) self.newAct.setEnabled(False) self.connectRamanAct.setEnabled(False) self.disconnectRamanAct.setEnabled(False) self.exitAct.setEnabled(False) self.opticalScanAct.setEnabled(False) self.detectParticleAct.setEnabled(False) self.ramanScanAct.setEnabled(False) def updateConnected(self, connected): if connected: self.connectRamanAct.setEnabled(False) self.disconnectRamanAct.setEnabled(True) else: self.connectRamanAct.setEnabled(True) self.disconnectRamanAct.setEnabled(False) def createMenus(self): self.fileMenu = QtWidgets.QMenu("&File", self) self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.importAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.viewMenu = QtWidgets.QMenu("&View", self) self.viewMenu.addAction(self.zoomInAct) self.viewMenu.addAction(self.zoomOutAct) self.viewMenu.addAction(self.normalSizeAct) self.viewMenu.addSeparator() self.viewMenu.addAction(self.fitToWindowAct) self.modeMenu = QtWidgets.QMenu("&Mode", self) self.modeMenu.addAction(self.opticalScanAct) self.modeMenu.addAction(self.detectParticleAct) self.toolsMenu = QtWidgets.QMenu("&Tools") self.toolsMenu.addAction(self.snapshotAct) self.toolsMenu.addAction(self.configRamanCtrlAct) self.toolsMenu.addAction(self.recalculateCoordAct) self.dispMenu = QtWidgets.QMenu("&Display", self) self.overlayActGroup = QtWidgets.QActionGroup(self.dispMenu) self.overlayActGroup.setExclusive(True) for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct]: self.dispMenu.addAction(act) self.overlayActGroup.addAction(act) self.dispMenu.addSeparator() self.dispMenu.addActions([self.transpAct, self.hideLabelAct, self.darkenAct, self.seedAct]) self.helpMenu = QtWidgets.QMenu("&Help", self) self.helpMenu.addAction(self.aboutAct) self.menuBar().addMenu(self.fileMenu) self.menuBar().addMenu(self.viewMenu) self.menuBar().addMenu(self.modeMenu) self.menuBar().addMenu(self.toolsMenu) self.menuBar().addMenu(self.dispMenu) self.menuBar().addMenu(self.helpMenu) def createToolBar(self): self.toolbar = QtWidgets.QToolBar("Tools") self.toolbar.setIconSize(QtCore.QSize(100,50)) self.toolbar.addAction(self.aboutAct) self.toolbar.addAction(self.newAct) self.toolbar.addAction(self.importAct) self.toolbar.addAction(self.openAct) self.toolbar.addSeparator() self.toolbar.addAction(self.connectRamanAct) self.toolbar.addAction(self.disconnectRamanAct) self.toolbar.addSeparator() self.toolbar.addAction(self.opticalScanAct) self.toolbar.addAction(self.detectParticleAct) self.toolbar.addAction(self.ramanScanAct) self.toolbar.addSeparator() self.toolbar.addAction(self.exitAct) self.toolbar.setOrientation(QtCore.Qt.Vertical) self.addToolBar(QtCore.Qt.LeftToolBarArea, self.toolbar) if __name__ == '__main__': import sys from time import localtime, strftime def closeAll() -> None: """ Closes the app and, with that, all windows. Josef: I implemented this, as with the simulated microscope stage it was difficult to find a proper way to ONLY close it at the end of running the program. Closing it on disconnect of the ramanctrl is not suitable, as it should be opened also in disconnected stage (e.g., when another instance is running in optical or raman scan, but the UI (disconnected) should still update what's going on. """ app.deleteLater() def excepthook(excType, excValue, tracebackobj): """ Global function to catch unhandled exceptions. @param excType exception type @param excValue exception value @param tracebackobj traceback object :return: """ tbinfofile = StringIO() traceback.print_tb(tracebackobj, None, tbinfofile) tbinfofile.seek(0) tbinfo = tbinfofile.read() logging.critical("Fatal error in program excecution!") logging.critical(tbinfo) from .errors import showErrorMessageAsWidget showErrorMessageAsWidget(tbinfo) sys.excepthook = excepthook logger = logging.getLogger(__name__) app = QtWidgets.QApplication(sys.argv) app.setApplicationName("GEPARD") # appname needed for logpath logpath = QtCore.QStandardPaths.writableLocation( QtCore.QStandardPaths.AppLocalDataLocation) fp = None if logpath != "": if not os.path.exists(logpath): os.mkdir(logpath) logname = os.path.join(logpath, 'logfile.txt') logger.addHandler( logging.handlers.RotatingFileHandler( logname, maxBytes=5*(1 << 20), backupCount=10) ) setDefaultLoggingConfig(logger) logger.info("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime())) gepard = GEPARDMainWindow(logger) gepard.showMaximized() ret = app.exec_()