# -*- coding: utf-8 -*- #!/usr/bin/env python3 """ 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 pandas as pd import sys import operator import os import random import colorsys from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar from .loadresults import LoadWITecResults from .editParticles import ParticleEditor from .database import DataBaseWindow try: from analysis.sqlexport import SQLExport sqlEnabled = True except: sqlEnabled = False class ParticleAnalysis(QtWidgets.QMainWindow): def __init__(self, parent): super(ParticleAnalysis, self).__init__() self.setGeometry(100, 100, 1680, 1050) self.setWindowTitle('Results of polymer analysis') self.layout = QtWidgets.QHBoxLayout() self.widget = QtWidgets.QWidget() self.widget.setLayout(self.layout) self.setCentralWidget(self.widget) self.parent = parent if self.parent is not None: self.config = self.parent.dataset.resultParams self.editor = ParticleEditor(self) self.spectraResults = None #entire List of all spectra assignments self.additiveResults = None #entire List of all additives self.particlestats = None self.particleResults = None #final assignment for each particle self.currentPolymers = None #list of polymers after setting entries with low hqi to unknown self.currentAdditives = None #same thing for the additives self.uniquePolymers = None #list of present polymer types self.spectra = None #acquired spectra self.indices = None #assignment of what spectra-indices belong to what substance self.additivePlot = None self.importWindow = None self.directory = None self.particles2spectra = None self.manualPolymers = {} self.manualAdditives = {} self.polymerCheckBoxes = [] self.lastSelectedCheckBoxNames = [] self.currentParticleIndex = 0 self.currentSpectrumIndex = 0 self.lastSpectrumInFocus = None self.typeHistogramCanvas = FigureCanvas(Figure()) self.sizeHistogramCanvas = FigureCanvas(Figure()) self.typeHist_ax = self.typeHistogramCanvas.figure.subplots() self.typeHist_ax.axis('off') sizeHistGroup = QtWidgets.QGroupBox() sizeHistLayout = QtWidgets.QHBoxLayout() self.sizeHist_ax = self.sizeHistogramCanvas.figure.subplots() self.sizeHist_ax.axis('off') self.sizeHistogramCanvas.figure.subplots_adjust(left=0.1, top=0.93, bottom=0.15, right=0.995) histNavigation = NavigationToolbar(self.sizeHistogramCanvas, self) histNavigation.setOrientation(QtCore.Qt.Vertical) histNavigation.setFixedWidth(50) sizeHistLayout.addWidget(histNavigation) sizeHistLayout.addWidget(self.sizeHistogramCanvas) sizeHistGroup.setLayout(sizeHistLayout) specGroup = QtWidgets.QGroupBox() specLayout = QtWidgets.QHBoxLayout() self.specCanvas = FigureCanvas(Figure()) self.spec_ax = self.specCanvas.figure.subplots() self.spec_ax.axis("off") self.ref_ax = self.spec_ax.twinx() self.specCanvas.figure.subplots_adjust(left=0.1, top=0.93, bottom=0.15, right=0.9) specNavigation = NavigationToolbar(self.specCanvas, self) specNavigation.setOrientation(QtCore.Qt.Vertical) specNavigation.setFixedWidth(50) specLayout.addWidget(specNavigation) specLayout.addWidget(self.specCanvas) specGroup.setLayout(specLayout) viewLayout = QtWidgets.QVBoxLayout() self.menuLayout = QtWidgets.QVBoxLayout() splitter1 = QtWidgets.QSplitter(QtCore.Qt.Vertical) splitter1.addWidget(specGroup) splitter1.addWidget(sizeHistGroup) splitter2 = QtWidgets.QSplitter(QtCore.Qt.Horizontal) splitter2.addWidget(splitter1) splitter2.addWidget(self.typeHistogramCanvas) splitter2.setSizes([300, 150]) self.navigationGroup = QtWidgets.QGroupBox('Navigate through polymers') self.navigationGroup.setDisabled(True) navigationLayout = QtWidgets.QHBoxLayout() self.polymerComboBox = QtWidgets.QComboBox() self.polymerComboBox.currentIndexChanged.connect(self.displayNewPolymerType) self.polymerComboBox.setMinimumWidth(150) self.particleSelector = QtWidgets.QSpinBox() self.particleSelector.valueChanged.connect(self.selectParticle) self.spectrumSelector = QtWidgets.QSpinBox() self.spectrumSelector.valueChanged.connect(self.selectSpectrum) for spinbox in [self.particleSelector, self.spectrumSelector]: spinbox.setMinimum(1) spinbox.setSingleStep(1) spinbox.setValue(1) navigationLayout.addWidget(QtWidgets.QLabel('Select Polymer Type:')) navigationLayout.addWidget(self.polymerComboBox) navigationLayout.addStretch() navigationLayout.addWidget(QtWidgets.QLabel('Select Particle')) navigationLayout.addWidget(self.particleSelector) navigationLayout.addWidget(QtWidgets.QLabel('Select Spectrum')) navigationLayout.addWidget(self.spectrumSelector) navigationLayout.addStretch() self.navigationGroup.setLayout(navigationLayout) referenceGroup = QtWidgets.QGroupBox('Reference Spectra') referenceLayout = QtWidgets.QHBoxLayout() self.refSelector = QtWidgets.QComboBox() self.refSelector.setDisabled(True) self.dbWin = DataBaseWindow(self) self.dbWin.selectDataBase(refreshParent=True) #this includes updating the refSelector self.refSelector.currentIndexChanged.connect(self.updateSpecPlot) referenceLayout.addWidget(QtWidgets.QLabel('Select Reference')) referenceLayout.addWidget(self.refSelector) referenceGroup.setLayout(referenceLayout) topLayout = QtWidgets.QHBoxLayout() topLayout.addWidget(self.navigationGroup) topLayout.addWidget(referenceGroup) topLayout.addStretch() viewLayout.addLayout(topLayout) viewLayout.addWidget(splitter2) viewLayout.setStretch(1, 1) self.optionsGroup = QtWidgets.QGroupBox('Further Options') optionsLayout = QtWidgets.QFormLayout() self.hqiSpinBox = QtWidgets.QDoubleSpinBox() self.hqiSpinBox.setValue(75.0) self.hqiSpinBox.setDecimals(1) self.hqiSpinBox.setMinimum(0) optionsLayout.addRow(QtWidgets.QLabel('min HQI:'), self.hqiSpinBox) self.compHqiSpinBox = QtWidgets.QDoubleSpinBox() self.compHqiSpinBox.setValue(30.0) self.compHqiSpinBox.setDecimals(1) self.compHqiSpinBox.setMinimum(0) self.compHqiSpinBox.setDisabled(True) optionsLayout.addRow(QtWidgets.QLabel('min component HQI'), self.compHqiSpinBox) self.dispResultSpinBox = QtWidgets.QSpinBox() self.dispResultSpinBox.setValue(20) self.dispResultSpinBox.setMinimum(1) self.dispResultSpinBox.valueChanged.connect(self.updateHistogram) optionsLayout.addRow(QtWidgets.QLabel('Max. items in display:'), self.dispResultSpinBox) for spinbox in [self.hqiSpinBox, self.compHqiSpinBox]: spinbox.setMaximum(100) spinbox.setMaximumWidth(45) self.updateBtn = QtWidgets.QPushButton('Update Results') self.updateBtn.setDisabled(True) optionsLayout.addRow(self.updateBtn) self.optionsGroup.setLayout(optionsLayout) self.optionsGroup.setMinimumWidth(175) self.optionsGroup.setDisabled(True) self.resultScrollarea = QtWidgets.QScrollArea(self) self.resultScrollarea.setFixedWidth(250) self.resultScrollarea.setWidgetResizable(True) widget = QtWidgets.QWidget() self.resultScrollarea.setWidget(widget) self.layout_SArea = QtWidgets.QVBoxLayout(widget) self.resultCheckBoxes = QtWidgets.QGroupBox('Display Polymer Types:') self.resultCheckBoxesLayout = QtWidgets.QVBoxLayout() self.showTotalSelector = QtWidgets.QCheckBox('Show Total Distribution') self.showTotalSelector.setChecked(True) self.showTotalSelector.setDisabled(True) self.resultCheckBoxesLayout.addWidget(self.showTotalSelector) self.resultCheckBoxesLayout.addStretch() self.resultCheckBoxes.setLayout(self.resultCheckBoxesLayout) self.layout_SArea.addWidget(self.resultCheckBoxes) # self.layout_SArea.addStretch(1) # self.menuLayout.addWidget(reloadGroup) self.menuLayout.addWidget(self.optionsGroup) self.menuLayout.addWidget(self.resultScrollarea) self.layout.addLayout(self.menuLayout) self.layout.addLayout(viewLayout) # update config, if present: if self.parent is not None: if self.config['minHQI'] is not None: self.hqiSpinBox.setValue(self.config['minHQI']) self.compHqiSpinBox.setValue(self.config['compHQI']) self.createActions() self.createMenus() self.updateData() def createActions(self): self.loadTrueMatchAct = QtWidgets.QAction("Load &TrueMatch Results", self) self.loadTrueMatchAct.triggered.connect(self.importTrueMatchResults) self.loadTextFileAct = QtWidgets.QAction("Load &ordered Text File", self) self.loadTextFileAct.setDisabled(True) self.noOverlayAct = QtWidgets.QAction("&No Overlay", self) self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self) self.fullOverlayAct = QtWidgets.QAction("&Full Overlay", self) self.transpAct = QtWidgets.QAction("&Transparent Overlay") self.transpAct.triggered.connect(self.createPolymerOverlay) self.hideLabelAct = QtWidgets.QAction('&Hide Polymer Numbers', self) self.hideLabelAct.triggered.connect(self.show_hide_labels) self.darkenAct = QtWidgets.QAction("&Darken Image", self) self.darkenAct.triggered.connect(self.darkenBackground) for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct]: act.setCheckable(True) self.fullOverlayAct.setChecked(True) self.seedAct = QtWidgets.QAction("&Set Color Seed", self) self.seedAct.triggered.connect(self.updateColorSeed) self.databaseAct = QtWidgets.QAction("&ManageDatabase", self) self.databaseAct.triggered.connect(self.launchDBManager) self.expExcelAct= QtWidgets.QAction("Export &Excel List", self) self.expExcelAct.setDisabled(True) self.expExcelAct.triggered.connect(self.exportToExcel) self.expSQLAct = QtWidgets.QAction("Export to &SQL Database", self) self.expSQLAct.setDisabled(True) self.expSQLAct.triggered.connect(self.exportToSQL) def createMenus(self): self.importMenu = QtWidgets.QMenu("&Import Results") self.importMenu.addActions([self.loadTrueMatchAct, self.loadTextFileAct]) self.dispMenu = QtWidgets.QMenu("&Display", self) self.overlayActGroup = QtWidgets.QActionGroup(self.dispMenu) self.overlayActGroup.setExclusive(True) self.overlayActGroup.triggered.connect(self.createPolymerOverlay) self.overlayActGroup.triggered.connect(self.updateHistogram) 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.refMenu = QtWidgets.QMenu("&References") self.refMenu.addAction(self.databaseAct) self.exportMenu = QtWidgets.QMenu("&Export", self) self.exportMenu.addAction(self.expExcelAct) self.exportMenu.addAction(self.expSQLAct) self.menuBar().addMenu(self.importMenu) self.menuBar().addMenu(self.dispMenu) self.menuBar().addMenu(self.refMenu) self.menuBar().addMenu(self.exportMenu) def launchDBManager(self): if self.dbWin.isHidden(): self.dbWin.show() def populateRefSelector(self): #delete all present entries: self.refSelector.clear() if self.dbWin.activeDatabase is None: self.refSelector.setDisabled(True) else: self.refSelector.addItem('') self.refSelector.addItems(self.dbWin.activeDatabase.spectraNames) self.refSelector.setDisabled(False) def loadSpectra(self, fname): import time t0 = time.time() specfilename = self.parent.dataset.fname.split('.pkl')[0] + '_spectra.npy' if os.path.exists(specfilename): return np.load(specfilename) else: try: specs = np.loadtxt(fname) #if spectra are already in correct format (WITec, first column: wavenumbers, other columns, intensities), #we take them, otherwise we have to convert from Renishaw export format... if not len(np.unique(specs[:, 0])) == len(specs[:, 0]): #--> only unique numbers -> this is the wavenumber column, we have the witec format #Renishaw Convert #columns 0 and 1 are x and y coordinates. We dont need them... startWavenumber = specs[0, 2] startIndices = np.where(specs[:, 2] == startWavenumber)[0] spectra = np.zeros((startIndices[1], len(startIndices)+1)) #create array with shape (numWavenumbers, numSpectra+1) (first column holds wavenumbers) spectra[:, 0] = specs[startIndices[0]:startIndices[1], 2] for i in range(len(startIndices)-1): spectra[:, i+1] = specs[startIndices[i]:startIndices[i+1], 3] #aaand the last spectrum: spectra[:, -1] = specs[startIndices[-1]:, 3] specs = np.flip(spectra, 0) #Renishaw goes from highest to lowest wavenumber, out of whatever reason... #write spectra to binary file, that makes reloading them in future significantly faster np.save(specfilename, specs) print('loading specs:', time.time()-t0) return specs except: return None def updateData(self): print('updating data from', self.parent.dataset.name) self.spectraResults = self.parent.dataset.results['polymers'] self.additiveResults = self.parent.dataset.results['additives'] self.hqis = self.parent.dataset.results['hqis'] self.addhqis = self.parent.dataset.results['additive_hqis'] self.colorSeed = self.parent.dataset.colorSeed if type(self.colorSeed) != str: self.colorSeed = 'default' #load Spectra if self.parent.dataset.spectraPath is None: fname = os.path.join(self.parent.dataset.path, self.parent.dataset.name + '_000_Spec.Data 1.txt') else: fname = self.parent.dataset.spectraPath self.spectra = self.loadSpectra(fname) if self.spectra is None: fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Select Spectra File', self.parent.dataset.path, 'text file (*.txt)')[0] self.spectra = self.loadSpectra(fname) if self.spectra is None: QtWidgets.QMessageBox.critical(self, 'ERROR!', 'spectra file could not be opened with np.loadtxt...') return self.parent.dataset.spectraPath = fname self.specCanvas.draw() self.loadParticleData() def loadParticleData(self): self.particlestats = np.array(self.parent.dataset.particlestats) pixelscale = (self.parent.dataset.pixelscale_df if self.parent.dataset.imagescanMode == 'df' else self.parent.dataset.pixelscale_bf) #convert to mikrometer scale for index in range(len(self.particlestats)): for subindex in range(5): self.particlestats[index][subindex] = self.particlestats[index][subindex] * pixelscale #multiply by pixelscale if subindex == 4: self.particlestats[index][subindex] = self.particlestats[index][subindex] * pixelscale #again for the area... self.particles2spectra = self.parent.dataset.particles2spectra sortindices = self.parent.dataset.ramanscansortindex if self.particles2spectra is None: print('creating default particles2spectra list') #no assignment found, so we assume one measurement per particle and use ramanscansortindex for assignment self.particles2spectra = [[int(np.where(sortindices == i)[0])] for i in range(len(sortindices))] #check, if dataset already contains results. Otherwise load them... if self.spectraResults is None or (len(self.spectraResults) != len(sortindices)): self.show() answer = QtWidgets.QMessageBox.question(self, 'Warning', 'No (or inconsistent) spectra results found, please run import dialog.\nPress OK to import or cancel to set to empty.', QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) if answer == QtWidgets.QMessageBox.Ok: self.importTrueMatchResults() elif answer == QtWidgets.QMessageBox.Cancel: self.spectraResults = ['empty']*(self.spectra.shape[1]-1) self.hqis = [100]*(self.spectra.shape[1]-1) self.updateBtn.clicked.connect(self.formatResults) self.formatResults() else: self.updateBtn.clicked.connect(self.formatResults) self.formatResults() def importTrueMatchResults(self): self.importWindow = LoadWITecResults(self) self.importWindow.show() def getAdditivePlot(self, event): clickedindex = int(np.round(event.xdata)) polymer = self.typehistogram[clickedindex][0] #get the polymer name, that was clicked on if len(self.sorted_additives[clickedindex]) > 0: self.additivePlot = AdditiveViewer(polymer, self.sorted_additives[clickedindex]) self.additivePlot.show() def formatResults(self): if self.spectraResults is not None: print('formatResults') self.updateBtn.setDisabled(False) self.optionsGroup.setDisabled(False) del self.currentPolymers, self.currentAdditives #convert to arrays (makes indexing easier...) self.currentPolymers, self.hqis = np.array(self.spectraResults), np.array(self.hqis) if self.additiveResults is not None: self.currentAdditives, self.addhqis = np.array(self.additiveResults), np.array(self.addhqis) self.compHqiSpinBox.setDisabled(False) else: self.currentAdditives = None #set poor HQI results to unknown self.currentPolymers[self.hqis < self.hqiSpinBox.value()] = 'unknown' if self.currentAdditives is not None: self.currentAdditives[self.addhqis < self.compHqiSpinBox.value()] = 'unknown' self.createHistogramData() def createHistogramData(self): self.uniquePolymers = np.unique(self.currentPolymers) self.particleResults = [None]*len(self.particlestats) self.typehistogram = {i: 0 for i in self.uniquePolymers} if len(self.particles2spectra) != len(self.particlestats): QtWidgets.QMessageBox.critical(self, 'Error', 'Inconsistent particle data. Please restore backup!') return for particleID, specList in enumerate(self.particles2spectra): assignment = self.currentPolymers[specList[0]] #we take the first result as particle result. Hence, all spectra per particle have to have the same result self.particleResults[particleID] = assignment self.typehistogram[assignment] += 1 self.particleResults = np.array(self.particleResults) ##sort typehistogram, it will be converted into a list!! self.typehistogram = sorted(self.typehistogram.items(), key = operator.itemgetter(1), reverse = True) self.uniquePolymers = [i[0] for i in self.typehistogram] self.indices = [] #what particles belong to which polymer type? for polymer in self.uniquePolymers: self.indices.append(list(np.where(self.particleResults == polymer)[0])) ###generate additive array for each type in typehistogram: if self.currentAdditives is None: self.sorted_additives = None else: self.sorted_additives = [] for polymer in self.typehistogram: #get additives of each polymer type self.sorted_additives.append(self.currentAdditives[np.where(self.currentPolymers == polymer[0])]) for i in range(len(self.sorted_additives)): #sort out 'none' entries nonentries = np.where(self.sorted_additives[i] == 'none') self.sorted_additives[i] = np.delete(self.sorted_additives[i], nonentries) ###Handle Checkboxes for all polymers... self.menuLayout.removeWidget(self.resultScrollarea) for i in [self.resultCheckBoxes, self.resultCheckBoxesLayout, self.resultScrollarea, self.layout_SArea]: i.setParent(None) del i for i in self.polymerCheckBoxes: #remove present boxlabels i.setParent(None) del i self.showTotalSelector.setParent(None) self.showTotalSelector.setDisabled(False) self.showTotalSelector.stateChanged.connect(self.updateHistogram) del self.resultCheckBoxes del self.resultCheckBoxesLayout del self.resultScrollarea del self.layout_SArea self.resultScrollarea = QtWidgets.QScrollArea(self) self.resultScrollarea.setFixedWidth(250) self.resultScrollarea.setWidgetResizable(True) widget = QtWidgets.QWidget() self.resultScrollarea.setWidget(widget) self.layout_SArea = QtWidgets.QVBoxLayout(widget) self.resultCheckBoxes = QtWidgets.QGroupBox('Show Polymer Types:') self.resultCheckBoxesLayout = QtWidgets.QVBoxLayout() self.resultCheckBoxesLayout.addWidget(self.showTotalSelector) #generate new checkboxes self.polymerCheckBoxes = [] for index, polymer in enumerate(self.uniquePolymers): self.polymerCheckBoxes.append(QtWidgets.QCheckBox(self)) self.polymerCheckBoxes[index].setText(polymer) self.resultCheckBoxesLayout.addWidget(self.polymerCheckBoxes[index]) if polymer in self.lastSelectedCheckBoxNames: self.polymerCheckBoxes[index].setChecked(True) self.polymerCheckBoxes[index].stateChanged.connect(self.updateHistogram) self.polymerCheckBoxes[index].stateChanged.connect(self.createPolymerOverlay) self.resultCheckBoxesLayout.addStretch() self.resultCheckBoxes.setLayout(self.resultCheckBoxesLayout) self.layout_SArea.addWidget(self.resultCheckBoxes) self.menuLayout.addWidget(self.resultScrollarea) if self.currentAdditives is not None: self.typeHistogramCanvas.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor)) self.typeHistogramCanvas.mpl_connect('button_press_event', self.getAdditivePlot) self.expExcelAct.setDisabled(False) if sqlEnabled: self.expSQLAct.setDisabled(False) self.navigationGroup.setEnabled(True) self.polymerComboBox.currentIndexChanged.disconnect() self.polymerComboBox.clear() self.polymerComboBox.addItems(self.uniquePolymers) self.polymerComboBox.currentIndexChanged.connect(self.displayNewPolymerType) self.polymerIndex = self.polymerComboBox.currentIndex() if self.lastSpectrumInFocus is not None: self.currentSpectrumIndex = self.lastSpectrumInFocus self.displayNewPolymerType(resetCurrentIndex=False) else: self.displayNewPolymerType() self.updateHistogram() self.createPolymerOverlay() def exportToExcel(self): expWin = ExpExcelDialog(self) expWin.exec() def exportToSQL(self): sqlexp = SQLExport(self) sqlexp.exec() def updateSpecPlot(self, centerOn=True, highlightContour=True): #draw Sample Spectrum specIndex = self.currentSpectrumIndex self.spec_ax.axis("on") self.spec_ax.clear() self.spec_ax.plot(self.spectra[:, 0], self.spectra[:, specIndex+1]) self.spec_ax.tick_params(axis='both', which='both', labelsize=15) self.spec_ax.set_xlabel('Wavenumber (cm-1)', fontsize = 15) self.spec_ax.set_ylabel('Counts', fontsize = 15) self.spec_ax.set_title('ScanPoint Number {}, Size = {} µm'.format(specIndex+1, np.round(self.particlestats[self.currentParticleIndex][2], 1))) self.spec_ax.set_xbound(100, (3400 if self.spectra[-1, 0] > 3400 else self.spectra[-1, 0])) wavenumber_diff = list(self.spectra[:, 0]-100) y_start = wavenumber_diff.index(min(wavenumber_diff)) y_min = min(self.spectra[y_start:, specIndex+1]) y_max = max(self.spectra[y_start:, specIndex+1]) self.spec_ax.set_ybound(0.9*y_min, 1.1*y_max) #draw Reference self.ref_ax.clear() if self.refSelector.isEnabled() and self.refSelector.currentText() != '': self.ref_ax.tick_params(axis='both', which='both', labelsize=15) refID = self.dbWin.activeDatabase.spectraNames.index(self.refSelector.currentText()) ref = self.dbWin.activeDatabase.spectra[refID] self.ref_ax.plot(ref[:, 0], ref[:, 1], color = 'r') self.ref_ax.set_ylabel('Ref. Intensity', fontsize = 15, color = 'r') self.ref_ax.tick_params('y', colors = 'r') self.ref_ax.set_xbound(100, (3400 if self.spectra[-1, 0] > 3400 else self.spectra[-1, 0])) # wavenumber_diff = list(ref[:, 0]-100) # y_start = wavenumber_diff.index(min(wavenumber_diff)) # y_min = min(ref[y_start:, specIndex+1]) # y_max = max(ref[y_start:, specIndex+1]) self.spec_ax.figure.canvas.draw() self.parent.centerOnRamanIndex(specIndex, centerOn=centerOn, highlightContour=highlightContour) self.parent.highLightRamanIndex(specIndex) self.lastSpectrumInFocus = specIndex def displayNewPolymerType(self, resetCurrentIndex=True): self.polymerIndex = self.polymerComboBox.currentIndex() self.particleSelector.setMaximum(len(self.indices[self.polymerIndex])) if resetCurrentIndex: self.particleSelector.setValue(1) self.spectrumSelector.setValue(1) self.spectrumSelector.setMaximum(len(self.particles2spectra[self.currentParticleIndex])) self.currentParticleIndex = self.indices[self.polymerIndex][self.particleSelector.value()-1] self.currentSpectrumIndex = self.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] self.updateSpecPlot(centerOn=True) else: self.currentParticleIndex = self.indices[self.polymerIndex][self.particleSelector.value()-1] self.currentSpectrumIndex = self.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] self.updateSpecPlot(centerOn=False) def selectParticle(self, resetSpectrumCount=True): if self.particles2spectra is not None: self.currentParticleIndex = self.indices[self.polymerIndex][self.particleSelector.value()-1] self.spectrumSelector.setMaximum(len(self.particles2spectra[self.currentParticleIndex])) if resetSpectrumCount: self.spectrumSelector.setValue(1) self.currentSpectrumIndex = self.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] self.updateSpecPlot() else: print('no spectrum assignment found...') def selectSpectrum(self): if self.particles2spectra is not None: self.currentSpectrumIndex = self.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] self.updateSpecPlot() def updateHistogram(self): self.sizeHist_ax.clear() self.typeHist_ax.clear() self.typeHist_ax.axis('on') self.sizeHist_ax.axis('on') #draw the general histogram colorList = [] if self.selOverlayAct.isChecked(): abundancyList = [] for index, checkbox in enumerate(self.polymerCheckBoxes): if checkbox.isChecked(): abundancyList.append(self.typehistogram[index][1]) curColor = self.getColorFromName(self.typehistogram[index][0], base255 = False) colorList.append(curColor) else: abundancyList = [i[1] for i in self.typehistogram] for polymer in self.typehistogram: curColor = self.getColorFromName(polymer[0], base255 = False) colorList.append(curColor) self.typeHist_ax.barh(range(len(abundancyList)), abundancyList, color=colorList) itemsInPlot = (len(abundancyList) if len(abundancyList) < self.dispResultSpinBox.value() else self.dispResultSpinBox.value()) self.typeHist_ax.set_ylim([itemsInPlot, -1]) #plot in inverse order (have index 0 (highest abundancy) at top) ###add text labels self.histPlotTextLabels = [] y_label_position = 0 for index, i in enumerate(self.typehistogram): if not self.selOverlayAct.isChecked() or self.polymerCheckBoxes[index].isChecked(): if self.sorted_additives is None: numads = '' else: numads = len(np.unique(self.sorted_additives[index])) if numads == 0: numads = '' else: numads = '(' + str(numads) + ')' numpolymers = i[1] label = ('{} x ' + self.typehistogram[index][0] + ' {}').format(numpolymers, numads) x_label_position = self.typeHist_ax.get_xlim()[1]*0.05 self.histPlotTextLabels.append(self.typeHist_ax.text(x_label_position, y_label_position, label, fontsize = 15, rotation = 0, verticalalignment = 'bottom')) y_label_position += 1 for label in self.histPlotTextLabels: pos = label.get_position() curLimits = self.typeHist_ax.get_ylim() if curLimits[1] < pos[1] < curLimits[0]: label.set_alpha(1) else: label.set_alpha(0) self.typeHist_ax.set_title('Polymer Type Distribution', fontsize = 15) self.typeHist_ax.tick_params(axis='y', which='both', left=False, labelleft=False) self.typeHist_ax.tick_params(axis='both', which='both', labelsize=15) self.typeHist_ax.set_ylabel('Polymer Type', fontsize = 15) self.typeHist_ax.set_xlabel('Number', fontsize = 15) if len(self.typehistogram) > self.dispResultSpinBox.value(): def wheelScroll(event): step = -0.05*event.step*self.dispResultSpinBox.value() ymin, ymax = self.typeHist_ax.get_ylim() if ymin > ymax: ymin, ymax = ymax, ymin self.typeHist_ax.set_ylim([ymax+step, ymin+step]) for label in self.histPlotTextLabels: pos = label.get_position() if ymin+step < pos[1] < ymax+step: label.set_alpha(1) else: label.set_alpha(0) self.typeHist_ax.figure.canvas.draw() self.typeHistogramCanvas.mpl_connect('scroll_event', wheelScroll) self.typeHist_ax.figure.canvas.draw() #general size histogram self.bins = np.logspace(0.1, 3, 20) self.sizes = [i[0] if np.isnan(i[2]) else i[2] for i in self.particlestats] #extract long size (if ellipse fit is nan -> box fit) sizehist = np.histogram(self.sizes, self.bins) self.totalhistx = [] for i in range(19): self.totalhistx.append(np.mean((sizehist[1][i], sizehist[1][i+1]))) self.totalhisty = sizehist[0] self.sizeHist_ax.tick_params(axis='both', which='both', labelsize=15) self.sizeHist_ax.set_xlabel('Size (µm)', fontsize = 15) self.sizeHist_ax.set_ylabel('Number', fontsize = 15) self.sizeHist_ax.set_xlim(3, 1100) self.sizeHist_ax.figure.canvas.draw() if self.showTotalSelector.isChecked(): self.sizeHist_ax.semilogx(self.totalhistx, self.totalhisty, label = 'total') #get selected boxes selected = [] for i in self.polymerCheckBoxes: if i.isChecked() == True: selected.append(i.text()) for i in selected: sizes = [self.sizes[index] for index in range(len(self.sizes)) if self.currentPolymers[index] == i] sizehist = np.histogram(sizes, self.bins) self.sizeHist_ax.semilogx(self.totalhistx, sizehist[0], label = i, color = self.getColorFromName(i, base255 = False)) self.sizeHist_ax.legend(prop = {'size': 15}) self.sizeHist_ax.tick_params(axis='both', which='both', labelsize=15) self.sizeHist_ax.set_xlabel('Size (µm)', fontsize = 15) self.sizeHist_ax.set_ylabel('Number', fontsize = 15) self.sizeHist_ax.set_xlim(3, 1100) self.sizeHist_ax.figure.canvas.draw() self.lastSelectedCheckBoxNames = [checkbox.text() for checkbox in self.polymerCheckBoxes if checkbox.isChecked()] def darkenBackground(self): self.parent.darkenPixmap = self.darkenAct.isChecked() if self.darkenAct.isChecked(): self.parent.scene().setBackgroundBrush(QtGui.QColor(5, 5, 5)) self.parent.item.setOpacity(0.2) else: self.parent.scene().setBackgroundBrush(QtCore.Qt.darkGray) self.parent.item.setOpacity(1) def updateColorSeed(self): text, ok = QtWidgets.QInputDialog.getText(self, 'Color Seed', 'Enter New Seed here', text=self.colorSeed) if ok: self.colorSeed = text self.parent.dataset.colorSeed = text self.updateHistogram() self.createPolymerOverlay() def getColorFromName(self, name, base255=True): random.seed(self.colorSeed + name) hue = random.random() random.seed((self.colorSeed + name)*2) saturation = random.random()/4 + 0.75 #i.e., between 0.75 and 1 random.seed((self.colorSeed + name)*3) value = random.random()/5 + 0.8 #i.e., between 0.8 and 1 color = colorsys.hsv_to_rgb(hue, saturation, value) if base255: color = list(color) for i in range(3): color[i] = np.round(color[i]*255) color = tuple(color) return color def createPolymerOverlay(self): if not self.noOverlayAct.isChecked() and self.indices is not None: if len(self.indices) > 0: alpha = (128 if self.transpAct.isChecked() else 255) #get colors for each polymer type colorList = [QtGui.QColor(255, 255, 255, alpha=50)]*len(self.particleResults) legendItems = [] for index, indexList in enumerate(self.indices): if self.fullOverlayAct.isChecked() or (self.selOverlayAct.isChecked() and self.polymerCheckBoxes[index].isChecked()): color = self.getColorFromName(self.uniquePolymers[index], base255=True) color = QtGui.QColor(color[0], color[1], color[2], alpha=alpha) legendItems.append((self.uniquePolymers[index], color)) for i in indexList: colorList[i] = color self.parent.contouritem.colorList = colorList self.parent.contouritem.update() self.parent.imparent.legend.setTextColorItems(legendItems) self.parent.imparent.legend.show() else: self.parent.contouritem.colorList = [] self.parent.contouritem.update() self.parent.imparent.legend.setTextColorItems([]) self.parent.imparent.legend.hide() def show_hide_labels(self): hidden = self.hideLabelAct.isChecked() for scanIndicator in self.parent.ramanscanitems: scanIndicator.hidden = hidden scanIndicator.update() def saveAnalysisResults(self): self.parent.dataset.results = {'polymers': self.spectraResults, 'hqis': self.hqis, 'additives': self.additiveResults, 'additive_hqis': self.addhqis} self.parent.dataset.resultParams = {'minHQI': self.hqiSpinBox.value(), 'compHQI': self.compHqiSpinBox.value()} self.parent.dataset.save() print('saved dataset') def closeEvent(self, event): for window in [self.additivePlot, self.importWindow, self.dbWin]: try: window.close() except: pass self.parent.imparent.particelAnalysisAct.setChecked(False) event.accept() class ExpExcelDialog(QtWidgets.QDialog): def __init__(self, parent): super(ExpExcelDialog, self).__init__() self.setWindowTitle('Export Options') self.setGeometry(200,200, 300, 300) self.parent = parent self.particles = self.parent.particlestats self.polymers = self.parent.particleResults self.additives = self.parent.currentAdditives self.hqis = self.parent.hqis self.layout = QtWidgets.QHBoxLayout() self.setLayout(self.layout) excelvbox = QtWidgets.QVBoxLayout() excelvbox.addWidget(QtWidgets.QLabel('Select Parameters for Export')) excelgroup = QtWidgets.QGroupBox("Export to Excel", self) self.exportOptions = ['Polymer Type (mandatory)', 'Additives', 'Long Size (µm)', 'Short Size (µm)', 'Area (µm²)', 'HQI', 'Size Classes'] self.checkBoxes = [] self.sizeClasses = [5, 10, 20, 50, 100, 1e6] self.directory = self.parent.parent.dataset.path for index, option in enumerate(self.exportOptions): self.checkBoxes.append(QtWidgets.QCheckBox(self)) self.checkBoxes[-1].setText(option) self.checkBoxes[-1].setChecked(True) if option == 'Polymer Type (mandatory)': self.checkBoxes[-1].setEnabled(False) #is mandatory!!! if option == 'Additives': if self.additives is None: self.checkBoxes[-1].setEnabled(False) self.checkBoxes[-1].setChecked(False) excelvbox.addWidget(self.checkBoxes[-1]) self.xlsFileName = QtWidgets.QLineEdit() self.xlsFileName.setText('{}_Particle_List'.format(self.parent.parent.dataset.name)) excelvbox.addWidget(QtWidgets.QLabel('Filename:')) excelvbox.addWidget(self.xlsFileName) self.exlbtn = QtWidgets.QPushButton('Export to Excel') self.exlbtn.resize(self.exlbtn.sizeHint()) self.exlbtn.clicked.connect(self.toExcel) excelvbox.addWidget(self.exlbtn) excelgroup.setLayout(excelvbox) self.layout.addWidget(excelgroup) self.show() def toExcel(self): requiredcolumns = [] self.sizes = np.round(np.array([i[0] if np.isnan(i[2]) else i[2] for i in self.particles]), 1) for box in self.checkBoxes: if box.isChecked() == True: if box.text() != 'Size Classes': requiredcolumns.append(box.text()) if box.text() == 'Long Size (µm)': longSize = self.sizes elif box.text() == 'Short Size (µm)': shortSize = np.round(np.array([i[1] if np.isnan(i[3]) else i[3] for i in self.particles]), 1) elif box.text() == 'Area (µm²)': area = np.array([np.round(float(entry[4]), 1) for entry in self.particles]) else: requiredcolumns.append('0 - 5 µm') requiredcolumns.append('5 - 10 µm') requiredcolumns.append('10 - 20 µm') requiredcolumns.append('20 - 50 µm') requiredcolumns.append('50 - 100 µm') requiredcolumns.append('> 100 µm') finalData = np.zeros((self.polymers.shape[0],len(requiredcolumns)-1)) polymertypes = [""]*self.polymers.shape[0] rowindex = 0 for polymer in np.unique(self.polymers): indices = self.polymers == polymer numentries = int(np.sum(indices)) print("Num:", numentries) sys.stdout.flush() for colindex, column in enumerate(requiredcolumns): if column == 'Polymer Type (mandatory)': polymertypes[rowindex:rowindex+numentries] = self.polymers[indices] if column == 'Additives': finalData[rowindex:rowindex+numentries, colindex-1] = self.additives[indices] if column == 'Long Size (µm)': finalData[rowindex:rowindex+numentries, colindex-1] = longSize[indices] if column == 'Short Size (µm)': finalData[rowindex:rowindex+numentries, colindex-1] = shortSize[indices] if column == 'Area (µm²)': finalData[rowindex:rowindex+numentries, colindex-1] = area[indices] # hit quality index array does not match the data size if particles have been combined #if column == 'HQI': # finalData[rowindex:rowindex+numentries, colindex-1] = self.hqis[indices] if '> 100 µm' in requiredcolumns: ##append size classes numPrevCols = len(requiredcolumns) - 1 - len(self.sizeClasses) #number of previous columns for tableindex, dataindex in enumerate(np.arange(len(indices))[indices]): for classindex in range(len(self.sizeClasses)): upLimit = self.sizeClasses[classindex] if classindex == 0: lowLimit = 0 else: lowLimit = self.sizeClasses[classindex-1] curSize = self.sizes[dataindex] if curSize > lowLimit and curSize <= upLimit: finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(1) else: finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(0) rowindex = rowindex + numentries #dump into excel file xlsname = self.directory + '//' + self.xlsFileName.text() + '.xlsx' print('exporting excel to:\n file name: {} in directory: {}'.format(self.xlsFileName.text(), self.directory)) validFileName = False incr = 1 while not validFileName: if not os.path.exists(xlsname): validFileName = True else: xlsname = self.directory + self.xlsFileName.text() + ' {}.xlsx'.format(incr) incr += 1 writer = pd.ExcelWriter(xlsname, engine = 'xlsxwriter') df = pd.DataFrame(finalData, columns=requiredcolumns[1:]) df.insert(0, 'Polymer Type', polymertypes) df.to_excel(writer, sheet_name = 'Individual Particles', index = False) if '> 100 µm' in requiredcolumns: #generate particle statistics report header = ['0 - 5 µm', '5 - 10 µm', '10 - 20 µm', '20 - 50 µm', '50 - 100 µm', '> 100 µm'] index = np.unique(self.polymers) particleclasses = [] for polymer in index: indices = np.where(self.polymers == polymer)[0] sortind = np.searchsorted([5,10,20,50,100], self.sizes[indices], 'right') classes = np.bincount(sortind, minlength=6) particleclasses.append(classes) particleclasses = np.array(particleclasses) report = pd.DataFrame(np.array(particleclasses), columns=header, dtype=int) report.insert(0, 'Polymer Type', index) report.insert(len(report.columns), 'Sum total', particleclasses.sum(axis=1)) report.to_excel(writer, sheet_name = 'Particle Statistics', index=False) writer.save() self.accept() class AdditiveViewer(QtWidgets.QWidget): def __init__(self, polymername, sortedAdditives): super(AdditiveViewer, self).__init__() self.setGeometry(200,200, 800, 600) self.setWindowTitle('Additives of {}'.format(polymername)) self.layout = QtWidgets.QGridLayout() self.setLayout(self.layout) self.canvas = FigureCanvas(Figure(figsize=(5, 3))) self.ax = self.canvas.figure.subplots() self.layout.addWidget(self.canvas, 0, 0) self.ax.hist(sortedAdditives) self.ax.set_ylabel('Number', fontsize = 15) self.ax.tick_params(axis='both', which='both', labelsize=15) if __name__ == '__main__': def run(): app = QtWidgets.QApplication(sys.argv) meas = ParticleAnalysis(None) meas.showMaximized() return app.exec_() run()