#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Thu May 31 10:07:45 2018 @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 from PyQt5 import QtCore, QtGui, QtWidgets 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 import pandas as pd from analysis.loadresults import LoadWITecResults from analysis.sqlexport import SQLExport from analysis.editParticles import ParticleEditor class ParticleAnalysis(QtWidgets.QWidget): 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.setLayout(self.layout) 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.expWindow = None 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.setMinimumWidth(50) histNavigation.setMaximumWidth(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.specCanvas.figure.subplots_adjust(left=0.1, top=0.93, bottom=0.15, right=0.995) specNavigation = NavigationToolbar(self.specCanvas, self) specNavigation.setOrientation(QtCore.Qt.Vertical) specNavigation.setMinimumWidth(50) specNavigation.setMaximumWidth(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) self.overlayGroup = QtWidgets.QGroupBox('Color Overlay') self.overlayLayout = QtWidgets.QHBoxLayout() self.noOverlayBtn = QtWidgets.QRadioButton('None') self.selOverlayBtn = QtWidgets.QRadioButton('Selected') self.fullOverlayBtn = QtWidgets.QRadioButton('Full') for index, button in enumerate([self.noOverlayBtn, self.selOverlayBtn, self.fullOverlayBtn]): if index == 2: button.setChecked(True) else: button.setChecked(False) button.released.connect(self.createPolymerOverlay) button.released.connect(self.updateHistogram) self.overlayLayout.addWidget(button) self.seedBtn = QtWidgets.QPushButton('Set Color Seed') self.seedBtn.released.connect(self.updateColorSeed) self.overlayLayout.addWidget(self.seedBtn) self.hideLabelBtn = QtWidgets.QCheckBox('Hide Polymer Numbers') self.hideLabelBtn.stateChanged.connect(self.show_hide_labels) self.hideLabelBtn.setChecked(False) # self.hideLabelBtn.setChecked(True) #change twice in order to run the connected function... self.overlayLayout.addWidget(self.hideLabelBtn) self.transpBtn = QtWidgets.QCheckBox('Transparent') self.transpBtn.setChecked(False) self.transpBtn.stateChanged.connect(self.createPolymerOverlay) self.overlayLayout.addWidget(self.transpBtn) self.darkenBtn = QtWidgets.QCheckBox('Darken Image') self.darkenBtn.setChecked(False) self.darkenBtn.stateChanged.connect(self.darkenBackground) self.overlayLayout.addWidget(self.darkenBtn) self.overlayGroup.setLayout(self.overlayLayout) 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) topLayout = QtWidgets.QHBoxLayout() topLayout.addWidget(self.navigationGroup) topLayout.addWidget(self.overlayGroup) viewLayout.addLayout(topLayout) viewLayout.addWidget(splitter2) viewLayout.setStretch(1, 1) reloadGroup = QtWidgets.QGroupBox('Reload Results from:') reloadLayout = QtWidgets.QVBoxLayout() self.reloadWITec = QtWidgets.QRadioButton('WITec True Match') self.reloadWITec.setChecked(True) self.reloadTxt = QtWidgets.QRadioButton('Ordered text file') reloadBtn = QtWidgets.QPushButton('reload Results') reloadBtn.clicked.connect(self.importResults) reloadLayout.addWidget(self.reloadWITec) reloadLayout.addWidget(self.reloadTxt) reloadLayout.addWidget(reloadBtn) reloadGroup.setLayout(reloadLayout) 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.exportbtn= QtWidgets.QPushButton('Export Results') self.exportbtn.clicked.connect(self.exportData) self.exportbtn.setDisabled(True) self.menuLayout.addWidget(reloadGroup) self.menuLayout.addWidget(self.optionsGroup) self.menuLayout.addWidget(self.resultScrollarea) self.menuLayout.addWidget(self.exportbtn) 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.updateData() def loadSpectra(self, fname): try: return np.loadtxt(fname) except: return None def updateData(self): 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 self.spectra = self.loadSpectra(os.path.join(self.parent.dataset.path, self.parent.dataset.name + '_000_Spec.Data 1.txt')) 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.spec_ax.set_xbound(100, (3400 if self.spectra[-1, 0] > 3400 else self.spectra[-1, 0])) 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)): QtWidgets.QMessageBox.about(self, 'Info', 'No (or inconsistent) spectra results found, please run import dialog.') else: self.updateBtn.clicked.connect(self.formatResults) self.formatResults() def importResults(self): if self.reloadWITec.isChecked(): self.importWindow = LoadWITecResults(self) self.importWindow.show() elif self.reloadTxt.isChecked(): QtWidgets.QMessageBox.about(self, 'sorry...', 'Ordered Text import not yet implemented...') 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): 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} assert len(self.particles2spectra) == len(self.particlestats), 'inconsistent data!!' 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.exportbtn.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 exportData(self): if self.expWindow is None: self.expWindow = ExportDialog(self) self.expWindow.show() else: self.expWindow.__init__(self) self.expWindow.show() def updatePolymerSpectrum(self, centerOn=True, highlightContour=True): 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.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.updatePolymerSpectrum(centerOn=True) else: self.currentParticleIndex = self.indices[self.polymerIndex][self.particleSelector.value()-1] self.currentSpectrumIndex = self.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] self.updatePolymerSpectrum(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.updatePolymerSpectrum() 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.updatePolymerSpectrum() 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.selOverlayBtn.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.selOverlayBtn.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.darkenBtn.isChecked() if self.darkenBtn.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.noOverlayBtn.isChecked() and self.indices is not None: if len(self.indices) > 0: # ramansortindices = self.parent.dataset.ramanscansortindex alpha = (128 if self.transpBtn.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.fullOverlayBtn.isChecked() or (self.selOverlayBtn.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[ramansortindices[i]] = color colorList[i] = color self.parent.contouritem.colorList = colorList self.parent.contouritem.update() self.parent.imparent.legend.items = legendItems self.parent.imparent.legend.update() self.parent.imparent.legend.show() else: self.parent.contouritem.colorList = [] self.parent.contouritem.update() self.parent.imparent.legend.items = [] self.parent.imparent.legend.update() self.parent.imparent.legend.hide() def show_hide_labels(self): hidden = self.hideLabelBtn.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()} # 'dispResults': self.dispResultSpinBox.value()} self.parent.dataset.save() print('saved dataset') def closeEvent(self, event): for window in [self.expWindow, self.additivePlot, self.importWindow]: try: window.close() except: pass self.saveAnalysisResults() event.accept() class ExportDialog(QtWidgets.QWidget): def __init__(self, parent): super(ExportDialog, 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) sqlGroup = QtWidgets.QGroupBox('Export to SQL') sqlLayout = QtWidgets.QVBoxLayout() self.sqlbtn = QtWidgets.QPushButton('Export to SQL Database') self.sqlbtn.resize(self.sqlbtn.sizeHint()) self.sqlbtn.clicked.connect(self.toSQL) self.sqlExport = None sqlLayout.addWidget(self.sqlbtn) sqlGroup.setLayout(sqlLayout) self.layout.addWidget(sqlGroup) 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') self.finalData = np.array(['nothing to see here, iam a boring placeholder']*((self.polymers.shape[0]+1)*len(requiredcolumns))).reshape(((self.polymers.shape[0]+1), len(requiredcolumns))) #create header: self.finalData[0, :] = np.array(requiredcolumns) self.finalData[0, 0] = 'Polymer Type' rowindex = 1 for polymer in np.unique(self.polymers): indices = np.where(self.polymers == polymer)[0] numentries = int(len(indices)) for colindex, column in enumerate(requiredcolumns): if column == 'Polymer Type (mandatory)': self.finalData[rowindex:rowindex+numentries, colindex] = self.polymers[indices] if column == 'Additives': self.finalData[rowindex:rowindex+numentries, colindex] = self.additives[indices] if column == 'Long Size (µm)': self.finalData[rowindex:rowindex+numentries, colindex] = longSize[indices] if column == 'Short Size (µm)': self.finalData[rowindex:rowindex+numentries, colindex] = shortSize[indices] if column == 'Area (µm²)': self.finalData[rowindex:rowindex+numentries, colindex] = area[indices] if column == 'HQI': self.finalData[rowindex:rowindex+numentries, colindex] = self.hqis[indices] if '> 100 µm' in requiredcolumns: ##append size classes numPrevCols = len(requiredcolumns) - 6 #number of previous columns for tableindex, dataindex in enumerate(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: self.finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(1) else: self.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(self.finalData) df.to_excel(writer, sheet_name = 'Individual Particles', header = False, index = False) if '> 100 µm' in requiredcolumns: #generate particle statistics report report = [['Polymer Type', '0 - 5 µm', '5 - 10 µm', '10 - 20 µm', '20 - 50 µm', '50 - 100 µm', '> 100 µm']] for polymer in np.unique(self.polymers): indices = np.where(self.polymers == polymer)[0] classes = np.array([0, 0, 0, 0, 0, 0]) for size in self.sizes[indices]: if size < 5: classes[0] += 1 elif size < 10: classes[1] += 1 elif size < 20: classes[2] += 1 elif size < 50: classes[3] += 1 elif size < 100: classes[4] += 1 else: classes[5] += 1 report.append([polymer, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5]]) report = pd.DataFrame(np.array(report)) report.to_excel(writer, sheet_name = 'Particle Statistics', header = False, index = False) QtWidgets.QMessageBox.about(self, 'Done!', 'Particle Data exported') def toSQL(self): self.sqlExport = SQLExport(self.parent) self.sqlExport.show() def closeEvent(self, event): if self.sqlExport is not None: self.sqlExport.close() event.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() ret = app.exec_() run()