# -*- coding: utf-8 -*- """ Created on Wed Jan 16 12:43:00 2019 @author: brandt """ """ 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 numpy as np import cv2 from PyQt5 import QtWidgets, QtCore class ParticleContextMenu(QtWidgets.QMenu): combineParticlesSignal = QtCore.pyqtSignal(list, str) reassignParticlesSignal = QtCore.pyqtSignal(list, str) def __init__(self, sampleView): super(ParticleContextMenu, self).__init__() self.sampleView = sampleView self.selectedParticleIndices = self.sampleView.selectedParticleIndices self.particleContainer = self.sampleView.dataset.particleContainer def executeAtScreenPos(self, screenPos): self.combineActs = [] self.combineMenu = QtWidgets.QMenu("Combine Particles into") assignments = [] for particleIndex in self.selectedParticleIndices: assignment = self.particleContainer.getParticleAssignmentByIndex(particleIndex) assignments.append(assignment) for assignment in np.unique(assignments): self.combineActs.append(self.combineMenu.addAction(assignment)) self.combineActs.append(self.combineMenu.addAction("other")) self.reassignActs = [] self.reassignMenu = QtWidgets.QMenu("Reassign particle(s) into") for polymType in self.particleContainer.getUniquePolymers(): self.reassignActs.append(self.reassignMenu.addAction(polymType)) self.reassignActs.append(self.reassignMenu.addAction("other")) numParticles = len(self.selectedParticleIndices) if numParticles == 0: self.reassignMenu.setDisabled(True) self.combineMenu.setDisabled(True) elif numParticles == 1: self.combineMenu.setDisabled(True) self.addMenu(self.combineMenu) self.addMenu(self.reassignMenu) action = self.exec_(screenPos) if action in self.combineActs: newAssignment = action.text() self.combineParticlesSignal.emit(self.selectedParticleIndices, newAssignment) elif action in self.reassignActs: newAssignment = action.text() self.reassignParticlesSignal.emit(self.selectedParticleIndices, newAssignment) class ParticleEditor(QtCore.QObject): particlesWereEdited = QtCore.pyqtSignal() def __init__(self, sampleView, particleContainer): super(ParticleEditor, self).__init__() self.particleContainer = particleContainer self.sampleView = sampleView #the assigned analysis widget self.backupFreq = 3 #save a backup every n actions self.neverBackedUp = True self.actionCounter = 0 def connectToSignals(self, contextMenu): contextMenu.combineParticlesSignal.connect(self.combineParticles) contextMenu.reassignParticlesSignal.connect(self.reassignParticles) def createSafetyBackup(self): self.actionCounter += 1 if self.actionCounter == self.backupFreq-1 or self.neverBackedUp: backupname = self.sampleView.dataset.saveBackup() print('backing up as', backupname) self.neverBackedUp = False self.actionCounter = 0 def getNewEntry(self): text, okClicked = QtWidgets.QInputDialog.getText(QtWidgets.QWidget(), "Custom assignment", "Enter new assignment") if okClicked and text != '': return text @QtCore.pyqtSlot(list, str) def combineParticles(self, contourIndices, new_assignment): print(contourIndices, new_assignment) # if new_assignment == 'other': # new_assignment = self.getNewEntry() # if new_assignment is None: # return # # contourIndices = sorted(contourIndices) #we want to keep the contour with lowest index # #get contours: # contours = self.particleContainer.getParticleContoursByIndex(contourIndices) ## contours = [self.datastats.dataset.particlecontours[i] for i in contourIndices] # cnt = np.vstack(tuple(contours)) #combine contous # # #draw contours # xmin, xmax = cnt[:,0,:][:, 0].min(), cnt[:,0,:][:, 0].max() # ymin, ymax = cnt[:,0,:][:, 1].min(), cnt[:,0,:][:, 1].max() # # padding = 2 #pixel in each direction # rangex = int(np.round((xmax-xmin)+2*padding)) # rangey = int(np.round((ymax-ymin)+2*padding)) # # img = np.zeros((rangey, rangex)) # for i in contourIndices: # curCnt = self.particleContainer.getParticleContoursByIndex(i).copy() ## curCnt = self.datastats.dataset.particlecontours[i].copy() # for i in range(len(curCnt)): # curCnt[i][0][0] -= xmin-padding # curCnt[i][0][1] -= ymin-padding # # cv2.drawContours(img, [curCnt], -1, 1, -1) # cv2.drawContours(img, [curCnt], -1, 1, 1) # # img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3)))) # # if cv2.__version__ > '3.5': # contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) # else: # temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) # # if len(contours)>1: # QtWidgets.QMessageBox.critical(self.parent, 'ERROR!', # 'Particle contours are not connected and cannot be combined!') # return # # newContour = contours[0] # stats = self.characterizeParticle(newContour) #dateContours() # for i in range(len(newContour)): # newContour[i][0][0] += xmin-padding # newContour[i][0][1] += ymin-padding # # print('merging contours:', contourIndices) # self.createSafetyBackup() # ## #check, if dataset contains (already modified) particle2spectra, otherwise create new. ## if self.datastats.dataset.particles2spectra is None: #create default assignment ## print('recreating particles2spectra from within edit particles...') ## sortindices = self.datastats.dataset.ramanscansortindex ## self.datastats.dataset.particles2spectra = [[int(np.where(sortindices == i)[0])] for i in range(len(sortindices))] # # #Contour indices are the same as the original particlestats, which are contained in the dataset. # #We have to modify that and reload in the analysisview # #first, overwrite first index with new particlestats # self.datastats.dataset.particlestats[contourIndices[0]] = stats # # #now, delete the rest... # self.datastats.dataset.particlestats = [i for ind, i in enumerate(self.datastats.dataset.particlestats) if ind not in contourIndices[1:]] # # #same with the contours # self.datastats.dataset.particlecontours[contourIndices[0]] = newContour # self.datastats.dataset.particlecontours = [i for ind, i in enumerate(self.datastats.dataset.particlecontours) if ind not in contourIndices[1:]] # # #update particle2spectra_list # #what is the current particle index?? # specIndices = [] # #other spectra indices: # for index in contourIndices: # specIndices.append(self.datastats.particles2spectra[index]) # # #flatten index list (in case, that a nested list was created...) # specIndices = list(np.concatenate(specIndices)) # for i in specIndices: # self.datastats.spectraResults[i] = new_assignment # self.datastats.hqis[i] = 100 #avoid sorting them out again by hqi-filter... # print(f'spectrum {i} of particle{contourIndices[0]} is now {new_assignment}') # # #modify particles2spectra.. # self.datastats.dataset.particles2spectra[contourIndices[0]] = specIndices # for index in reversed(contourIndices[1:]): # print('removing index from particles2spectra:', index) # del self.datastats.dataset.particles2spectra[index] # # #update contours in sampleview # self.parent.parent.contouritem.resetContours(self.datastats.dataset.particlecontours) # self.parent.loadParticleData() # #save data # minHQI = self.parent.hqiSpinBox.value() # compHQI = self.parent.compHqiSpinBox.value() # if not self.datastats.saveAnalysisResults(minHQI, compHQI): # QtWidgets.QMessageBox.warning(self.parent, 'Error!', # 'Data inconsistency after saving!', QtWidgets.QMessageBox.Ok, # QtWidgets.QMessageBox.Ok) @QtCore.pyqtSlot(list, str) def reassignParticles(self, contourindices, new_assignment): if new_assignment == 'other': new_assignment = self.getNewEntry() if new_assignment is None: return self.createSafetyBackup() print(f'reassigning indices {contourindices} into {new_assignment}') for partIndex in contourindices: self.particleContainer.reassignParticleToAssignment(partIndex, new_assignment) self.particlesWereEdited.emit() def characterizeParticle(self, contours): ##characterize particle longellipse, shortellipse = np.nan, np.nan cnt = contours if cnt.shape[0] >= 5: ##at least 5 points required for ellipse fitting... ellipse = cv2.fitEllipse(cnt) shortellipse, longellipse = ellipse[1] rect = cv2.minAreaRect(cnt) long, short = rect[1] if short>long: long, short = short, long return long, short, longellipse, shortellipse, cv2.contourArea(cnt)