# -*- 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 . """ from PyQt5 import QtCore, QtGui, QtWidgets import numpy as np import os from dataset import DataSet, loadData from ramancom.ramancontrol import RamanControl, simulatedRaman from opticalscan import OpticalScan from ramanscanui import RamanScanUI from detectionview import ParticleDetectionView from analysis.analysisview import ParticleAnalysis from zeissimporter import ZeissImporter from viewitems import FitPosIndicator, Node, Edge, ScanIndicator, RamanScanIndicator, SegmentationContours from helperfunctions import polygoncovering, cv2imread_fix import cv2 from ramancom.configRaman import RamanConfigWin class SampleView(QtWidgets.QGraphicsView): ScalingChanged = QtCore.pyqtSignal(float) def __init__(self, logpath): super(SampleView, self).__init__() self.logpath = logpath self.item = QtWidgets.QGraphicsPixmapItem() self.item.setPos(0, 0) self.item.setAcceptedMouseButtons(QtCore.Qt.NoButton) self.scaleFactor = 1.0 scene = QtWidgets.QGraphicsScene(self) scene.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex) scene.addItem(self.item) scene.setBackgroundBrush(QtCore.Qt.darkGray) self.setScene(scene) self.setCacheMode(QtWidgets.QGraphicsView.CacheBackground) self.setViewportUpdateMode(QtWidgets.QGraphicsView.BoundingRectViewportUpdate) self.setRenderHint(QtGui.QPainter.Antialiasing) self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter) self.ramanctrl = RamanControl() self.simulatedRaman = simulatedRaman #determine, if ramanSwitch is needed: self.ramanctrl.connect() if not self.ramanctrl.connected: QtWidgets.QMessageBox.warning(self, 'Error', 'Please enable Raman Connection') return if self.ramanctrl.getImageDimensions(mode='bf')[0] == self.ramanctrl.getImageDimensions(mode='df')[0]: self.ramanSwitchNeeded = False else: self.ramanSwitchNeeded = True self.ramanctrl.disconnect() self.drag = None self.mode = None self.dataset = None self.fititems = [] self.boundaryitems = [[],[]] self.scanitems = [] self.ramanscanitems = [] self.imgdata = None self.isblocked = False self.contouritem = SegmentationContours(self) scene.addItem(self.contouritem) self.detectionwidget = None self.ramanwidget = RamanScanUI(self.ramanctrl, None, self.logpath, self) self.ramanwidget.imageUpdate.connect(self.loadPixmap) self.oscanwidget = OpticalScan(self.ramanctrl, None, self.logpath, self) self.oscanwidget.imageUpdate.connect(self.loadPixmap) self.oscanwidget.boundaryUpdate.connect(self.resetBoundary) self.analysiswidget = None self.setMinimumSize(600, 600) self.darkenPixmap = False self.microscopeMode = None self.coordTestMode = False def takeScreenshot(self): #TODO: #LIMIT SCREENSHOT TO ACTUAL VIEWSIZE OF LOADED IMAGE... #hide scrollbars self.setHorizontalScrollBarPolicy(1) self.setVerticalScrollBarPolicy(1) #capture screen screen = QtWidgets.QApplication.primaryScreen() self.repaint() screenshot = screen.grabWindow(self.winId()) #unhide scrollbars self.setHorizontalScrollBarPolicy(0) self.setVerticalScrollBarPolicy(0) fname = self.dataset.path + '/screenshot.png' validFileName = False incr = 1 while not validFileName: if not os.path.exists(fname): validFileName = True else: fname = self.dataset.path + '/screenshot ({}).png'.format(incr) incr += 1 screenshot.save(fname , 'png') QtWidgets.QMessageBox.about(self, 'Message', 'Saved as {} to project directory.'.format(fname.split('/')[-1])) def closeEvent(self, event): reply = QtWidgets.QMessageBox.question(self, 'Message', "Are you sure to quit?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.disconnectRaman() self.saveDataSet() event.accept() self.oscanwidget.close() if self.detectionwidget is not None: self.detectionwidget.close() if self.analysiswidget is not None: self.analysiswidget.close() self.ramanwidget.close() else: event.ignore() def configureRamanControl(self): self.configWin = RamanConfigWin(self) self.configWin.show() def saveDataSet(self): if self.dataset is not None: self.dataset.save() @QtCore.pyqtSlot() def zoomIn(self): self.scaleImage(1.25) @QtCore.pyqtSlot() def zoomOut(self): self.scaleImage(0.8) @QtCore.pyqtSlot() def normalSize(self): self.scaleFactor = 1.0 self.setTransform(QtGui.QTransform.fromScale(1., 1.)) self.announceScaling() @QtCore.pyqtSlot() def fitToWindow(self): # print("fitting to Window") brect = self.item.sceneBoundingRect() self.fitInView(0, 0, brect.width(), brect.height(), QtCore.Qt.KeepAspectRatio) self.scaleFactor = self.transform().m11() self.announceScaling() def switchMode(self, mode, loadnew=False): if mode is None: return assert mode in ["OpticalScan", "ParticleDetection", "RamanScan", "ParticleAnalysis"] self.oscanwidget.setVisible(False) if self.detectionwidget is not None: self.detectionwidget.close() self.detectionwidget.destroy() self.ramanwidget.setVisible(False) self.contouritem.resetContours([]) self.mode = mode self.loadPixmap(self.microscopeMode) if mode == "OpticalScan": self.oscanwidget.setVisible(True) self.oscanwidget.resetDataset(self.dataset) elif mode == "ParticleDetection": if self.detectionwidget is None: self.detectionwidget = ParticleDetectionView(self.imgdata, self.dataset, self) self.detectionwidget.show() self.detectionwidget.imageUpdate.connect(self.detectionUpdate) self.detectionwidget.detectionFinished.connect(self.activateMaxMode) elif mode == "RamanScan": self.ramanwidget.resetDataset(self.dataset) self.ramanwidget.setVisible(True) elif mode == "ParticleAnalysis": if self.ramanwidget.isVisible(): self.ramanwidget.setVisible(False) if self.analysiswidget is None: print('creating new analysiswidget') self.analysiswidget = ParticleAnalysis(self.dataset, self) self.analysiswidget.showMaximized() else: print('show maximized already exisiting analysiswidget') self.analysiswidget.showMaximized() # self.ramanwidget.setVisible(False) if self.detectionwidget is not None: self.detectionwidget.setVisible(False) #show legend: self.imparent.legend.show() if loadnew: self.fitToWindow() self.imparent.updateModes(mode, self.getMaxMode()) def open(self, fname): self.saveDataSet() #close all widgets for widget in [self.detectionwidget, self.ramanwidget, self.oscanwidget, self.analysiswidget]: if widget is not None: widget.close() widget.destroy() del widget self.contouritem.resetContours() # if self.dataset is not None: # del self.dataset # self.scene().removeItem(self.contouritem) # del self.contouritem # self.contouritem = SegmentationContours(self) # self.scene().addItem(self.contouritem) self.dataset = loadData(fname) self.setMicroscopeMode() self.imparent.setWindowTitle(self.dataset.name + (" SIMULATION" if simulatedRaman else "")) self.imgdata = None self.activateMaxMode(loadnew=True) self.imparent.snapshotAct.setEnabled(True) def importProject(self, fname): zimp = ZeissImporter(fname, self.ramanctrl, self) if zimp.validimport: zimp.exec() if zimp.result() == QtWidgets.QDialog.Accepted: self.open(zimp.gepardname) def new(self, fname): self.saveDataSet() if self.dataset is not None: self.dataset.save() self.dataset = DataSet(fname, newProject=True) self.setMicroscopeMode() self.imparent.setWindowTitle(self.dataset.name + (" SIMULATION" if simulatedRaman else "")) self.imgdata = None self.activateMaxMode(loadnew=True) self.imparent.snapshotAct.setEnabled(True) def setMicroscopeMode(self): if self.ramanSwitchNeeded: self.imparent.ramanSwitch.connectToSampleView() self.imparent.ramanSwitch.show() self.microscopeMode = ('df' if self.imparent.ramanSwitch.df_btn.isChecked() else 'bf') @QtCore.pyqtSlot() def activateMaxMode(self, loadnew=False): mode = self.getMaxMode() self.imparent.updateModes(self.mode, self.getMaxMode()) self.switchMode(mode, loadnew=loadnew) def blockUI(self): self.isblocked = True self.imparent.blockUI() def unblockUI(self): self.isblocked = False self.imparent.unblockUI(self.ramanctrl.connected) self.imparent.updateModes(self.mode, self.getMaxMode()) def getMaxMode(self): if self.dataset is None: return None if not self.ramanctrl.connected: self.connectRaman() if not self.ramanctrl.connected: return None maxmode = "OpticalScan" if os.path.exists(self.dataset.getImageName()): maxmode = "ParticleDetection" if len(self.dataset.ramanpoints)>0: maxmode = "RamanScan" if self.dataset.ramanscandone: #uncomment!! maxmode = "ParticleAnalysis" return maxmode def mousePressEvent(self, event): # if event.button()==QtCore.Qt.RightButton: if event.button()==QtCore.Qt.MiddleButton: self.drag = event.pos() elif event.button()==QtCore.Qt.LeftButton and self.mode in ["OpticalScan", "RamanScan"] \ and event.modifiers()==QtCore.Qt.ControlModifier: p0 = self.mapToScene(event.pos()) if self.dataset is not None and self.dataset.pshift is not None: if self.dataset.readin: reply = QtWidgets.QMessageBox.critical(self, 'Dataset is newly read from disk!', "Coordinate systems might have changed since. Do you want to continue with saved coordinates?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: self.dataset.readin = False else: return x, y, z = self.dataset.mapToLengthRaman([p0.x(), p0.y()], microscopeMode=self.microscopeMode, noz=(False if self.mode=="RamanScan" else True)) if z is not None: assert z>-100. self.ramanctrl.moveToAbsolutePosition(x, y, z) elif event.button()==QtCore.Qt.LeftButton and self.mode=="ParticleDetection": p0 = self.mapToScene(event.pos()) self.detectionwidget.setImageCenter([p0.x(), p0.y()]) # elif event.button()==QtCore.Qt.LeftButton and self.mode=="ParticleAnalysis": # p0 = self.mapToScene(event.pos()) else: p0 = self.mapToScene(event.pos()) super(SampleView, self).mousePressEvent(event) def mouseMoveEvent(self, event): if self.drag is not None: p0 = event.pos() move = self.drag-p0 self.horizontalScrollBar().setValue(move.x() + self.horizontalScrollBar().value()) self.verticalScrollBar().setValue(move.y() + self.verticalScrollBar().value()) self.drag = p0 else: super(SampleView, self).mouseMoveEvent(event) def mouseReleaseEvent(self, event): self.drag = None super(SampleView, self).mouseReleaseEvent(event) def wheelEvent(self, event): factor = 1.01**(event.angleDelta().y()/8) self.scaleImage(factor) def scaleImage(self, factor): if factor<1 and not self.imparent.zoomOutAct.isEnabled(): return if factor>1 and not self.imparent.zoomInAct.isEnabled(): return self.scaleFactor *= factor self.scale(factor, factor) self.announceScaling() def announceScaling(self): pixelscale = self.dataset.getPixelScale(self.microscopeMode) if self.dataset is None or pixelscale is None: self.ScalingChanged.emit(-1.0) else: self.ScalingChanged.emit(pixelscale/self.scaleFactor) ##CURRENTLY ONLY DARKFIELD!!! FIX NEEDED!!! def connectRaman(self): if not self.ramanctrl.connect(): msg = QtWidgets.QMessageBox() msg.setText("Connection failed! Please enable remote control.") msg.exec() else: mode = self.getMaxMode() self.switchMode(mode) self.imparent.updateConnected(self.ramanctrl.connected) def disconnectRaman(self): self.ramanctrl.disconnect() self.imparent.updateConnected(self.ramanctrl.connected) @QtCore.pyqtSlot(str) def detectionUpdate(self): self.contouritem.resetContours(self.dataset.particlecontours) self.prepareAnalysis() self.update() @QtCore.pyqtSlot(str) def loadPixmap(self, microscope_mode='df'): self.clearItems() if self.dataset is None: self.item.setPixmap(QtGui.QPixmap()) else: data = self.imgdata fname = self.dataset.getImageName() if self.mode == "ParticleDetection" or self.mode == "ParticleAnalysis": self.contouritem.resetContours(self.dataset.particlecontours) if data is None and os.path.exists(fname): data = cv2.cvtColor(cv2imread_fix(fname), cv2.COLOR_BGR2RGB) self.imgdata = data if data is not None: height, width, channel = data.shape bytesPerLine = 3 * width pix = QtGui.QPixmap() pix.convertFromImage(QtGui.QImage(data.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)) self.item.setPixmap(pix) if self.darkenPixmap: self.scene().setBackgroundBrush(QtGui.QColor(5, 5, 5)) self.item.setOpacity(0.2) else: self.scene().setBackgroundBrush(QtCore.Qt.darkGray) self.item.setOpacity(1) else: self.item.setPixmap(QtGui.QPixmap()) if self.mode == "OpticalScan": for i, p in zip(self.dataset.fitindices, self.dataset.fitpoints): p = self.dataset.mapToPixel(p, mode=microscope_mode, force=True) fititem = FitPosIndicator(i+1, pos=p) self.scene().addItem(fititem) self.fititems.append(fititem) if self.mode == "ParticleDetection" or self.mode == "ParticleAnalysis": self.prepareAnalysis() else: self.fitToWindow() @QtCore.pyqtSlot() def resetScanPositions(self): micMode = ('df' if self.oscanwidget.df_btn.isChecked() else 'bf') for item in self.scanitems: self.scene().removeItem(item) edges, nodes = self.boundaryitems boundary = [] for n in nodes: p = n.pos().x(), n.pos().y() boundary.append(self.dataset.mapToLength(p, self.microscopeMode, force=True)) boundary = np.array(boundary) # print(boundary) self.dataset.boundary = boundary if micMode == 'df': width, height, angle = self.dataset.imagedim_df else: width, height, angle = self.dataset.imagedim_bf margin = min(width, height)*0.02 wx, wy = width-margin, height-margin print(wx,wy) p1 = polygoncovering(boundary, wx, wy) b2 = boundary.copy() b2 = b2[:,[1,0]] p2 = polygoncovering(b2, wy, wx) if len(p2)0: data = [] for i in self.dataset.ramanscansortindex: # data.append(list(self.dataset.ramanpoints[i])+list(self.dataset.particlestats[i])) data.append(list(self.dataset.ramanpoints[i])) #particlestats are not needed here. Plus, they dont need to match spectra indices anymore.. for i in range(len(data)): item = RamanScanIndicator(self, i+1, 20, (data[i][0],data[i][1])) self.scene().addItem(item) self.ramanscanitems.append(item) def highLightRamanIndex(self, index): if index < len(self.ramanscanitems): for item in self.ramanscanitems: item.setHighLight(False) self.ramanscanitems[index].setHighLight(True) def centerOnRamanIndex(self, index, centerOn=True, highlightContour=True): if centerOn: self.centerOn(self.ramanscanitems[index]) # if highlightContour: # self.contouritem.selectedContours = [] # else: # self.ensureVisible(self.ramanscanitems[index]) def clearItems(self): for item in self.fititems: self.scene().removeItem(item) self.fititems = [] for item in self.scanitems: self.scene().removeItem(item) edges, nodes = self.boundaryitems for item in edges: self.scene().removeItem(item) for item in nodes: self.scene().removeItem(item) self.scanitems = [] self.boundaryitems = [], [] for item in self.ramanscanitems: self.scene().removeItem(item) self.ramanscanitems = []