diff --git a/analysis/analysisplots.py b/analysis/analysisplots.py new file mode 100644 index 0000000000000000000000000000000000000000..14ce4e4a19fd52390df1eadaf8c0a8ed4d9be2a9 --- /dev/null +++ b/analysis/analysisplots.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# -*- 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 QtWidgets, QtGui, QtCore +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure +from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar +import os +import numpy as np + +from analysis import importSpectra + + +class ParticleIndicator(QtWidgets.QPushButton): + def __init__(self, number, numtotal, color, text, parent=None): + super().__init__(parent) + self.number = number + self.numtotal = numtotal + self.color = color + self.text = text + self.setFixedHeight(30) + + def paintEvent(self, event): + r = self.number/self.numtotal + width = self.width() + height = self.height() + + qp = QtGui.QPainter() + qp.begin(self) + #qp.fillRect(self.rect(), QtCore.Qt.white) + qp.setBrush(QtCore.Qt.white) + qp.drawRoundedRect(0, 0, width, height, 5. ,5.) + qp.setPen(self.color) + qp.setBrush(self.color) + qp.drawRoundedRect(0, 0, int(width*r), height, 5. ,5.) + qp.setPen(QtCore.Qt.black) + qp.setBrush(QtCore.Qt.NoBrush) + qp.drawRoundedRect(0, 0, width, height, 5. ,5.) + font = qp.font() + font.setPointSize(13) + font.setStyleStrategy(QtGui.QFont.NoAntialias) + font.setWeight(0) + qp.setFont(font) + qp.setCompositionMode(QtGui.QPainter.RasterOp_SourceXorDestination) + qp.setPen(QtCore.Qt.white) + qp.drawText(5, 0, width-10, height, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, + self.text) + qp.end() + + +class TypeHistogramView(QtWidgets.QScrollArea): + indexClicked = QtCore.pyqtSignal(int) + def __init__(self, parent=None): + super().__init__(parent) + self.view = QtWidgets.QWidget(self) + self.view.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor)) + self.view.setMinimumWidth(250) + + group = QtWidgets.QGroupBox('Polymer Type Distribution', self.view) + self.indicatorbox = QtWidgets.QVBoxLayout() + self.indicatorbox.setContentsMargins(5,5,5,5) + group.setLayout(self.indicatorbox) + + hbox = QtWidgets.QHBoxLayout() + hbox.addWidget(group) + self.view.setLayout(hbox) + self.setWidgetResizable(True) + self.setWidget(self.view) + self.setAlignment(QtCore.Qt.AlignHCenter) + self.widgets = [] + + def updateTypeHistogram(self, types): +# print("Updating polymer type view", flush=True) + for pi in self.widgets: + self.indicatorbox.removeWidget(pi) + pi.setParent(None) + pi.destroy() + self.indicatorbox.takeAt(0) + self.widgets = [] + numtotal = sum([num for num, text, color in types]) + + def getIndexFunction(index): + return lambda : self.indexClicked.emit(index) + + for index, entry in enumerate(types): + num, text, color = entry +# print("num, text, color:", num, text, color, flush=True) + pi = ParticleIndicator(num, numtotal, color, text) + self.indicatorbox.addWidget(pi) + pi.clicked.connect(getIndexFunction(index)) + self.widgets.append(pi) + self.indicatorbox.addStretch() + self.view.update() + + +class SpectraPlot(QtWidgets.QGroupBox): + def __init__(self, dataset): + super(SpectraPlot, self).__init__() + self.dataset = dataset + self.spectra = None + + layout = QtWidgets.QHBoxLayout() + self.canvas = FigureCanvas(Figure()) + self.spec_axis = self.canvas.figure.subplots() + self.spec_axis.axis("off") + self.reference_ax = self.spec_axis.twinx() + self.canvas.figure.subplots_adjust(left=0.1, top=0.93, bottom=0.15, right=0.9) + specNavigation = NavigationToolbar(self.canvas, self) + specNavigation.setOrientation(QtCore.Qt.Vertical) + specNavigation.setFixedWidth(50) + + layout.addWidget(specNavigation) + layout.addWidget(self.canvas) + self.setLayout(layout) + + def loadSpectraAndInitializeSpecPlot(self): #formerly updateData(self).... + def tryLoadingNumpySpecFile(): + specPath = self.dataset.getSpectraFileName() + if os.path.exists(specPath): + return np.load(specPath) + else: + raise ImportError + try: + self.spectra = tryLoadingNumpySpecFile() + except ImportError: + + fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File', self.dataset.path, 'text file (*.txt)')[0] + + try: + self.spectra, spectraNames = importSpectra.importWITecSpectra(fname) + except ImportError: + try: + self.spectra, spectraNames = importSpectra.importRenishawSpectra(fname) + except ImportError: + self.spectra, spectraNames = importSpectra.importPerkinElmerSpectra(fname) + + if self.spectra is None: + raise ImportError + else: + np.save(self.dataset.getSpectraFileName(), self.spectra) + + self.canvas.draw() + + def updateParticleSpectrum(self, specIndex, particleSize, hqi): + if self.spectra is not None: + #draw Sample Spectrum + self.spec_axis.axis("on") + self.spec_axis.clear() + self.spec_axis.plot(self.spectra[:, 0], self.spectra[:, specIndex+1]) + self.spec_axis.tick_params(axis='both', which='both', labelsize=15) + self.spec_axis.set_xlabel('Wavenumber (cm-1)', fontsize = 15) + self.spec_axis.set_ylabel('Counts', fontsize = 15) + self.spec_axis.set_title('ScanPoint Number {}, Size = {} µm, HQI = {}'.format(specIndex+1, particleSize, hqi)) + self.spec_axis.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_axis.set_ybound(0.9*y_min, 1.1*y_max) + + self.canvas.draw() + + def updateReferenceSpectrum(self, ref_wavenumber, ref_intensity): + #draw Reference + self.reference_axis.clear() + self.reference_axis.tick_params(axis='both', which='both', labelsize=15) + self.reference_axis.plot(ref_wavenumber, ref_intensity, color = 'r') + self.reference_axis.set_ylabel('Ref. Intensity', fontsize = 15, color = 'r') + self.reference_axis.tick_params('y', colors = 'r') + self.reference_axis.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.canvas.draw() + + + \ No newline at end of file diff --git a/analysis/analysisview.py b/analysis/analysisview.py index 0a5c2ce557374fbcbb66564f16574c000b6834fd..173a7746600afb6cfa970a4e9c66b806e3a06b92 100644 --- a/analysis/analysisview.py +++ b/analysis/analysisview.py @@ -22,20 +22,20 @@ If not, see . from PyQt5 import QtCore, QtGui, QtWidgets import numpy as np -import sys, os -import random -import colorsys +import sys + from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar -from .analysiswidgets import ExpExcelDialog, AdditiveViewer, ParticleTypeView +from .analysiswidgets import ExpExcelDialog +from .analysisplots import TypeHistogramView, SpectraPlot from .loadresults import LoadWITecResults from .particleeditor import ParticleEditor from .database import DataBaseWindow -#from .datastats import DataStats -from analysis import importSpectra +from .colorlegend import ColorHandler + try: from .sqlexport import SQLExport sqlEnabled = True @@ -56,8 +56,8 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.parent = parent self.dataset = dataset self.particleContainer = dataset.particleContainer -# self.datastats = DataStats(dataset) self.editor = ParticleEditor(self.particleContainer, self) + self.colorHandler = ColorHandler() # self.additivePlot = None self.importWindow = None @@ -67,9 +67,9 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.currentParticleIndex = 0 self.currentSpectrumIndex = 0 - self.lastSpectrumInFocus = None +# self.lastSpectrumInFocus = None - self.typeHistogramPlot = ParticleTypeView(self) + self.typeHistogramPlot = TypeHistogramView(self) # self.typeHistogramPlot.indexClicked.connect(self.getAdditivePlot) self.sizeHistogramCanvas = FigureCanvas(Figure()) @@ -85,76 +85,69 @@ class ParticleAnalysis(QtWidgets.QMainWindow): 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() + self.specPlot = SpectraPlot(self.dataset) splitter1 = QtWidgets.QSplitter(QtCore.Qt.Vertical) - splitter1.addWidget(specGroup) + splitter1.addWidget(self.specPlot) splitter1.addWidget(sizeHistGroup) splitter2 = QtWidgets.QSplitter(QtCore.Qt.Horizontal) splitter2.addWidget(splitter1) splitter2.addWidget(self.typeHistogramPlot) splitter2.setSizes([300, 150]) + referenceGroup = QtWidgets.QGroupBox('Reference Spectra') + referenceLayout = QtWidgets.QHBoxLayout() + + self.refSelector = QtWidgets.QComboBox() + self.refSelector.setMinimumWidth(200) + 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) + referenceLayout.addStretch() + referenceGroup.setLayout(referenceLayout) + + 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.typeSelectorCombo = QtWidgets.QComboBox() + self.typeSelectorCombo.currentIndexChanged.connect(self.displayNewPolymerType) + self.typeSelectorCombo.setMinimumWidth(150) self.particleSelector = QtWidgets.QSpinBox() - self.particleSelector.valueChanged.connect(self.selectParticle) + self.particleSelector.valueChanged.connect(self.updateParticleSelection) + self.particleNumberLabel = QtWidgets.QLabel('of xx particles; ') + self.spectrumSelector = QtWidgets.QSpinBox() self.spectrumSelector.valueChanged.connect(self.selectSpectrum) + self.spectrumNumberLabel = QtWidgets.QLabel('of xx spectra') 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.addWidget(self.typeSelectorCombo) navigationLayout.addStretch() navigationLayout.addWidget(QtWidgets.QLabel('Select Particle')) navigationLayout.addWidget(self.particleSelector) + navigationLayout.addWidget(self.particleNumberLabel) + navigationLayout.addStretch() navigationLayout.addWidget(QtWidgets.QLabel('Select Spectrum')) navigationLayout.addWidget(self.spectrumSelector) - navigationLayout.addStretch() + navigationLayout.addWidget(self.spectrumNumberLabel) +# 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 = QtWidgets.QVBoxLayout() viewLayout.addLayout(topLayout) viewLayout.addWidget(splitter2) viewLayout.setStretch(1, 1) @@ -163,11 +156,12 @@ class ParticleAnalysis(QtWidgets.QMainWindow): optionsLayout = QtWidgets.QFormLayout() self.hqiSpinBox = QtWidgets.QDoubleSpinBox() - self.hqiSpinBox.setValue(75.0) + self.hqiSpinBox.setValue(self.dataset.resultParams['minHQI']) self.hqiSpinBox.setDecimals(1) self.hqiSpinBox.setMinimum(0) self.hqiSpinBox.setMaximum(100) - self.hqiSpinBox.setMaximumWidth(45) + self.hqiSpinBox.setMaximumWidth(100) + self.hqiSpinBox.valueChanged.connect(self.applyHQIThresholdToResults) optionsLayout.addRow(QtWidgets.QLabel('min HQI:'), self.hqiSpinBox) # self.compHqiSpinBox = QtWidgets.QDoubleSpinBox() @@ -180,13 +174,10 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.dispResultSpinBox = QtWidgets.QSpinBox() self.dispResultSpinBox.setValue(20) self.dispResultSpinBox.setMinimum(1) + self.dispResultSpinBox.setMaximumWidth(100) self.dispResultSpinBox.valueChanged.connect(self.updateHistograms) optionsLayout.addRow(QtWidgets.QLabel('Max. items in display:'), self.dispResultSpinBox) - self.updateBtn = QtWidgets.QPushButton('Update Results') - self.updateBtn.clicked.connect(self.applyHQIThresholdToResults) -# self.updateBtn.setDisabled(True) - optionsLayout.addRow(self.updateBtn) self.optionsGroup.setLayout(optionsLayout) self.optionsGroup.setMinimumWidth(175) @@ -211,7 +202,8 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.resultCheckBoxes.setLayout(self.resultCheckBoxesLayout) self.layout_SArea.addWidget(self.resultCheckBoxes) - + + self.menuLayout = QtWidgets.QVBoxLayout() self.menuLayout.addWidget(self.optionsGroup) self.menuLayout.addWidget(self.resultScrollarea) @@ -226,9 +218,13 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.createActions() self.createMenus() - - self.loadSpectraAndInitializeSpecPlot() self.applyHQIThresholdToResults() + self.createHistogramData() + self.updateHistograms() + self.initializeSpecPlot() + self.displayNewPolymerType() + self.updateParticleSelection() +# self.createPolymerOverlay() def createActions(self): self.loadTrueMatchAct = QtWidgets.QAction("Load &TrueMatch Results", self) @@ -312,50 +308,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.refSelector.addItems(self.dbWin.activeDatabase.spectraNames) self.refSelector.setDisabled(False) - def loadSpectraAndInitializeSpecPlot(self): #formerly updateData(self).... - def tryLoadingNumpySpecFile(): - specPath = self.dataset.getSpectraFileName() - if os.path.exists(specPath): - return np.load(specPath) - else: - raise ImportError - try: - self.spectra = tryLoadingNumpySpecFile() - except ImportError: - - fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File', self.dataset.path, 'text file (*.txt)')[0] - - try: - self.spectra, spectraNames = importSpectra.importWITecSpectra(fname) - except ImportError: - try: - self.spectra, spectraNames = importSpectra.importRenishawSpectra(fname) - except ImportError: - self.spectra, spectraNames = importSpectra.importPerkinElmerSpectra(fname) - - if self.spectra is None: - raise ImportError - else: - np.save(self.dataset.getSpectraFileName(), self.spectra) - - self.specCanvas.draw() -# self.loadParticleData() - -# def loadParticleData(self): -# #check, if dataset already contains results. Otherwise load them... -# if not self.datastats.loadParticleData(): -# 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.datastats.invalidateSpectra() -# self.updateBtn.clicked.connect(self.formatResults) -# self.formatResults() -# else: -# self.updateBtn.clicked.connect(self.formatResults) -# self.formatResults() - def importTrueMatchResults(self): self.importWindow = LoadWITecResults(self.particleContainer, self) self.importWindow.exec() @@ -371,13 +323,17 @@ class ParticleAnalysis(QtWidgets.QMainWindow): @QtCore.pyqtSlot() def applyHQIThresholdToResults(self): - self.particleContainer.applyHQITresholdToParticles(self.hqiSpinBox.value()) + hqi = self.hqiSpinBox.value() + self.particleContainer.applyHQITresholdToParticles(hqi) + self.dataset.resultParams['minHQI'] = hqi + self.dataset.save() self.createHistogramData() + self.updateHistograms() + # def formatResults(self): # if self.datastats.spectraResults is not None: # print('formatResults') -# self.updateBtn.setDisabled(False) # self.optionsGroup.setDisabled(False) # # self.datastats.formatResults(self.hqiSpinBox.value(), self.compHqiSpinBox.value()) @@ -422,7 +378,7 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.resultCheckBoxesLayout.addWidget(self.showTotalSelector) #generate new checkboxes - self.polymerCheckBoxes = [] + self.polymerCheckBoxes = []# uniquePolymers = self.particleContainer.getUniquePolymers() for index, polymer in enumerate(uniquePolymers): self.polymerCheckBoxes.append(QtWidgets.QCheckBox(self)) @@ -444,20 +400,20 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.expSQLAct.setDisabled(False) self.navigationGroup.setEnabled(True) - self.polymerComboBox.currentIndexChanged.disconnect() - self.polymerComboBox.clear() - self.polymerComboBox.addItems(uniquePolymers) - self.polymerComboBox.currentIndexChanged.connect(self.displayNewPolymerType) + self.typeSelectorCombo.currentIndexChanged.disconnect() + self.typeSelectorCombo.clear() + self.typeSelectorCombo.addItems(uniquePolymers) + self.typeSelectorCombo.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.polymerIndex = self.typeSelectorCombo.currentIndex() +# if self.lastSpectrumInFocus is not None: +# self.currentSpectrumIndex = self.lastSpectrumInFocus +# self.displayNewPolymerType(resetCurrentIndex=False) +# print('displaying new type without resetting index') +# else: +# print('displaying new type with resetting index') +# self.displayNewPolymerType() - self.updateHistograms() - self.createPolymerOverlay() def exportToExcel(self): expWin = ExpExcelDialog(self.particleContainer, self) @@ -466,47 +422,23 @@ class ParticleAnalysis(QtWidgets.QMainWindow): def exportToSQL(self): sqlexp = SQLExport(self.particleContainer, self) sqlexp.exec() - + + def initializeSpecPlot(self): + self.specPlot.loadSpectraAndInitializeSpecPlot() + self.updateSpecPlot() 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, - self.particleContainer.getSizeOfParticleByIndex(self.currentParticleIndex))) - 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() + particleSize = self.particleContainer.getSizeOfParticleByIndex(self.currentParticleIndex) + hqi = self.particleContainer.getHQIOfSpectrumIndex(self.currentSpectrumIndex) + self.specPlot.updateParticleSpectrum(self.currentSpectrumIndex, particleSize, hqi) + self.parent.centerOnRamanIndex(self.currentSpectrumIndex, centerOn=centerOn, highlightContour=highlightContour) #TODO: is this reasonable to do in that way??? + self.parent.highLightRamanIndex(self.currentSpectrumIndex) +# self.lastSpectrumInFocus = self.currentSpectrumIndex + 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 + self.specPlot.updateReferenceSpectrum(ref[:, 0], ref[:, 1]) def selectContour(self, index, centerOn=True): print('select Contour in analysisview not yet refactored') @@ -531,7 +463,7 @@ class ParticleAnalysis(QtWidgets.QMainWindow): # #disconnect analysis widgets: # self.particleSelector.valueChanged.disconnect() # self.spectrumSelector.valueChanged.disconnect() -# self.polymerComboBox.currentIndexChanged.disconnect() +# self.typeSelectorCombo.currentIndexChanged.disconnect() # # #set widgets... # self.particleSelector.setValue(subPartInd+1) @@ -542,53 +474,61 @@ class ParticleAnalysis(QtWidgets.QMainWindow): # # selectedPolymer = self.datastats.currentPolymers[specIndex] # self.polymerIndex = uniquePolymers.index(selectedPolymer) -# self.polymerComboBox.setCurrentIndex(self.polymerIndex) +# self.typeSelectorCombo.setCurrentIndex(self.polymerIndex) # # #reconnect all widgets: -# self.particleSelector.valueChanged.connect(self.selectParticle) +# self.particleSelector.valueChanged.connect(self.updateParticleSelection) # self.spectrumSelector.valueChanged.connect(self.selectSpectrum) -# self.polymerComboBox.currentIndexChanged.connect(self.displayNewPolymerType) +# self.typeSelectorCombo.currentIndexChanged.connect(self.displayNewPolymerType) # # self.updateSpecPlot(centerOn=centerOn) def displayNewPolymerType(self, resetCurrentIndex=True): - self.polymerIndex = self.polymerComboBox.currentIndex() - polymerName = self.polymerComboBox.currentText() -# self.particleSelector.setMaximum(len(self.datastats.indices[self.polymerIndex])) - self.particleSelector.setMaximum(self.particleContainer.getNumberOfParticlesOfAssignment(polymerName)) - if resetCurrentIndex: - self.particleSelector.setValue(1) - self.spectrumSelector.setValue(1) - self.spectrumSelector.setMaximum(len(self.datastats.particles2spectra[self.currentParticleIndex])) - self.currentParticleIndex = self.datastats.indices[self.polymerIndex][self.particleSelector.value()-1] - self.currentSpectrumIndex = self.datastats.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] - self.updateSpecPlot(centerOn=True) - else: - self.currentParticleIndex = self.datastats.indices[self.polymerIndex][self.particleSelector.value()-1] - self.currentSpectrumIndex = self.datastats.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] - self.updateSpecPlot(centerOn=False) + polymerName = self.typeSelectorCombo.currentText() + numParticles = self.particleContainer.getNumberOfParticlesOfAssignment(polymerName) + self.particleSelector.setMaximum(numParticles) + self.particleNumberLabel.setText(f'of {numParticles} Particles; ') + + self.particleSelector.setValue(1) + self.spectrumSelector.setValue(1) + + self.currentParticleIndex = self.getParticleIndexFromParticleSelector() + self.spectrumSelector.setMaximum(self.particleContainer.getNumberOfSpectraOfParticle(self.currentParticleIndex)) + self.currentSpectrumIndex = self.getSpectrumIndexFromSpectrumSelector() + self.updateSpecPlot(centerOn=False) - def selectParticle(self, resetSpectrumCount=True): - if self.datastats.particles2spectra is not None: - self.currentParticleIndex = self.datastats.indices[self.polymerIndex][self.particleSelector.value()-1] - self.spectrumSelector.setMaximum(len(self.datastats.particles2spectra[self.currentParticleIndex])) + def updateParticleSelection(self, resetSpectrumCount=True): + polymerName = self.typeSelectorCombo.currentText() + if polymerName != '': + self.currentParticleIndex = self.getParticleIndexFromParticleSelector() + numSpectra = self.particleContainer.getNumberOfSpectraOfParticle(self.currentParticleIndex) + self.spectrumSelector.setMaximum(numSpectra) + self.spectrumNumberLabel.setText(f'of {numSpectra} Spectra') if resetSpectrumCount: self.spectrumSelector.setValue(1) - self.currentSpectrumIndex = self.datastats.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] + + self.currentSpectrumIndex = self.getSpectrumIndexFromSpectrumSelector() self.updateSpecPlot() - else: - print('no spectrum assignment found...') def selectSpectrum(self): - if self.datastats.particles2spectra is not None: - self.currentSpectrumIndex = self.datastats.particles2spectra[self.currentParticleIndex][self.spectrumSelector.value()-1] - self.updateSpecPlot() + self.currentSpectrumIndex = self.getSpectrumIndexFromSpectrumSelector() + self.updateSpecPlot() + + def getSpectrumIndexFromSpectrumSelector(self): + specIndicesOfParticle = self.particleContainer.getSpectraIndicesOfParticle(self.currentParticleIndex) + try: + return specIndicesOfParticle[self.spectrumSelector.value()-1] + except IndexError: + print(f'requested specIndex {self.spectrumSelector.value()-1} for particle Index {self.currentParticleIndex} of specIndices: {specIndicesOfParticle}') + + def getParticleIndexFromParticleSelector(self): + particleIndicesOfType = self.particleContainer.getIndicesOfParticleType(self.typeSelectorCombo.currentText()) + return particleIndicesOfType[self.particleSelector.value()-1] def updateHistograms(self): self.updateTypeHistogram() self.updateSizeHistogram() - def updateTypeHistogram(self): #draw the general histogram colorList = [] @@ -597,8 +537,10 @@ class ParticleAnalysis(QtWidgets.QMainWindow): typeHistogram = self.particleContainer.getTypeHistogram() for index, polymType in enumerate(typeHistogram): if not self.selOverlayAct.isChecked() or self.polymerCheckBoxes[index].isChecked(): + text = f'{typeHistogram[polymType]} x {polymType}' + labelList.append(text) abundancyList.append(typeHistogram[polymType]) - curColor = self.getColorFromName(polymType) + curColor = self.colorHandler.getColorFromName(self.dataset.colorSeed, polymType) colorList.append(QtGui.QColor(*curColor)) # print("abundancyList:", abundancyList) @@ -607,50 +549,53 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.typeHistogramPlot.updateTypeHistogram(list(zip(abundancyList, labelList, colorList))) def updateSizeHistogram(self): - #general size histogram - self.sizeHist_ax.clear() - self.sizeHist_ax.axis('on') - - self.bins = np.logspace(0.1, 3, 20) - self.sizes = self.particleContainer.getSizesOfAllParticles() -# self.sizes = [i[0] if np.isnan(i[2]) else i[2] for i in self.datastats.getParticleStats()] #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') + if self.particleContainer.getNumberOfParticles() > 0: + #general size histogram + self.sizeHist_ax.clear() + self.sizeHist_ax.axis('on') + + self.bins = np.logspace(0.1, 3, 20) + self.sizes = self.particleContainer.getSizesOfAllParticles() + + # self.sizes = [i[0] if np.isnan(i[2]) else i[2] for i in self.datastats.getParticleStats()] #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 + selectedTypes = [] + for checkbox in self.polymerCheckBoxes: + if checkbox.isChecked() == True: + selectedTypes.append(checkbox.text()) + + for polymType in selectedTypes: + sizes = self.particleContainer.getSizesOfParticleType(polymType) + # sizes = [self.sizes[index] for index in range(len(self.sizes)) + # if self.datastats.currentPolymers[index] == i] + sizehist = np.histogram(sizes, self.bins) + color = self.colorHandler.getColorFromName(self.dataset.colorSeed, polymType, base255=False) + self.sizeHist_ax.semilogx(self.totalhistx, sizehist[0], label=polymType, color=color) + + 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()] - #get selected boxes - selectedTypes = [] - for checkbox in self.polymerCheckBoxes: - if checkbox.isChecked() == True: - selectedTypes.append(checkbox.text()) - - for polymType in selectedTypes: - sizes = self.particleContainer.getSizesOfParticleType(polymType) -# sizes = [self.sizes[index] for index in range(len(self.sizes)) -# if self.datastats.currentPolymers[index] == i] - sizehist = np.histogram(sizes, self.bins) - self.sizeHist_ax.semilogx(self.totalhistx, sizehist[0], label = polymType, color = self.getColorFromName(polymType, 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() @@ -663,57 +608,47 @@ class ParticleAnalysis(QtWidgets.QMainWindow): self.parent.item.setOpacity(1) def updateColorSeed(self): - text, ok = QtWidgets.QInputDialog.getText(self, 'Color Seed', 'Enter New Seed here', text=self.datastats.colorSeed) + text, ok = QtWidgets.QInputDialog.getText(self, 'Color Seed', 'Enter New Seed here', text=self.dataset.colorSeed) if ok: - self.datastats.colorSeed = text - self.datastats.dataset.colorSeed = text + self.dataset.colorSeed = text +# self.datastats.dataset.colorSeed = text self.updateHistograms() self.createPolymerOverlay() - - def getColorFromName(self, name, base255=True): - random.seed(self.datastats.colorSeed + name) - hue = random.random() - random.seed((self.datastats.colorSeed + name)*2) - saturation = random.random()/4 + 0.75 #i.e., between 0.75 and 1 - random.seed((self.datastats.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): - uniquePolymers = self.datastats.getUniquePolymers() - if not self.noOverlayAct.isChecked() and self.datastats.indices is not None: - if len(self.datastats.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.datastats.particleContainer) - legendItems = [] - - for index, indexList in enumerate(self.datastats.indices): - if self.fullOverlayAct.isChecked() or (self.selOverlayAct.isChecked() and self.polymerCheckBoxes[index].isChecked()): - color = self.getColorFromName(uniquePolymers[index], base255=True) - color = QtGui.QColor(color[0], color[1], color[2], alpha=alpha) - legendItems.append((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() + pass #REFACTOR TO INDIVIDUAL CONTOUR ITEMS +## uniquePolymers = self.datastats.getUniquePolymers() +# uniquePolymers = self.particleContainer.getUniquePolymers() +## if not self.noOverlayAct.isChecked() and self.datastats.indices is not None: +# if not self.noOverlayAct.isChecked(): +## if len(self.datastats.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.datastats.particleResults) +# legendItems = [] +# +# for index, indexList in enumerate(self.datastats.indices): +# if self.fullOverlayAct.isChecked() or (self.selOverlayAct.isChecked() and self.polymerCheckBoxes[index].isChecked()): +# +# color = self.colorHandler.getColorFromName(self.dataset.colorSeed, uniquePolymers[index]) +## color = self.getColorFromName(uniquePolymers[index], base255=True) +# color = QtGui.QColor(color[0], color[1], color[2], alpha=alpha) +# legendItems.append((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() @@ -723,7 +658,7 @@ class ParticleAnalysis(QtWidgets.QMainWindow): def closeEvent(self, event): - for window in [self.additivePlot, self.importWindow, self.dbWin]: + for window in [self.importWindow, self.dbWin]: try: window.close() except: pass self.parent.imparent.particelAnalysisAct.setChecked(False) diff --git a/analysis/analysiswidgets.py b/analysis/analysiswidgets.py index 8c9ff6fb8ee8adc10b70cc45a2a62f425db99f19..8c4658583b536f397f2c636f485113acdd66fd4d 100644 --- a/analysis/analysiswidgets.py +++ b/analysis/analysiswidgets.py @@ -204,86 +204,5 @@ class AdditiveViewer(QtWidgets.QWidget): self.ax.hist(sortedAdditives) self.ax.set_ylabel('Number', fontsize = 15) self.ax.tick_params(axis='both', which='both', labelsize=15) - - -class ParticleIndicator(QtWidgets.QPushButton): - def __init__(self, number, numtotal, color, text, parent=None): - super().__init__(parent) - self.number = number - self.numtotal = numtotal - self.color = color - self.text = text - self.setFixedHeight(30) - - def paintEvent(self, event): - r = self.number/self.numtotal - width = self.width() - height = self.height() - - qp = QtGui.QPainter() - qp.begin(self) - #qp.fillRect(self.rect(), QtCore.Qt.white) - qp.setBrush(QtCore.Qt.white) - qp.drawRoundedRect(0, 0, width, height, 5. ,5.) - qp.setPen(self.color) - qp.setBrush(self.color) - qp.drawRoundedRect(0, 0, int(width*r), height, 5. ,5.) - qp.setPen(QtCore.Qt.black) - qp.setBrush(QtCore.Qt.NoBrush) - qp.drawRoundedRect(0, 0, width, height, 5. ,5.) - font = qp.font() - font.setPointSize(13) - font.setStyleStrategy(QtGui.QFont.NoAntialias) - font.setWeight(0) - qp.setFont(font) - qp.setCompositionMode(QtGui.QPainter.RasterOp_SourceXorDestination) - qp.setPen(QtCore.Qt.white) - qp.drawText(5, 0, width-10, height, QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, - self.text) - qp.end() - - -class ParticleTypeView(QtWidgets.QScrollArea): - indexClicked = QtCore.pyqtSignal(int) - def __init__(self, parent=None): - super().__init__(parent) - self.view = QtWidgets.QWidget(self) - self.view.setCursor(QtGui.QCursor(QtCore.Qt.WhatsThisCursor)) - self.view.setMinimumWidth(250) - - group = QtWidgets.QGroupBox('Polymer Type Distribution', self.view) - self.indicatorbox = QtWidgets.QVBoxLayout() - self.indicatorbox.setContentsMargins(5,5,5,5) - group.setLayout(self.indicatorbox) - - hbox = QtWidgets.QHBoxLayout() - hbox.addWidget(group) - self.view.setLayout(hbox) - self.setWidgetResizable(True) - self.setWidget(self.view) - self.setAlignment(QtCore.Qt.AlignHCenter) - self.widgets = [] - - def updateTypeHistogram(self, types): -# print("Updating polymer type view", flush=True) - for pi in self.widgets: - self.indicatorbox.removeWidget(pi) - pi.setParent(None) - pi.destroy() - self.indicatorbox.takeAt(0) - self.widgets = [] - numtotal = sum([num for num, text, color in types]) - - def getIndexFunction(index): - return lambda : self.indexClicked.emit(index) - - for index, entry in enumerate(types): - num, text, color = entry -# print("num, text, color:", num, text, color, flush=True) - pi = ParticleIndicator(num, numtotal, color, text) - self.indicatorbox.addWidget(pi) - pi.clicked.connect(getIndexFunction(index)) - self.widgets.append(pi) - self.indicatorbox.addStretch() - self.view.update() + \ No newline at end of file diff --git a/colorlegend.py b/analysis/colorlegend.py similarity index 83% rename from colorlegend.py rename to analysis/colorlegend.py index ae7885cdcc478e6b577860e914e76ecc6cda6aa1..029db78210747768db52f5c3849847e760f9136d 100644 --- a/colorlegend.py +++ b/analysis/colorlegend.py @@ -20,6 +20,8 @@ If not, see . """ from PyQt5 import QtCore, QtWidgets, QtGui import numpy as np +import random +import colorsys WX, WY = 1024, 200 @@ -111,4 +113,23 @@ class ColorLegend(QtWidgets.QMdiSubWindow): qp.end() - \ No newline at end of file +class ColorHandler(object): + def __init__(self): + pass + #We should implement another method of getting colors, rather than taking the random color generator + #presets and other stuff can be defined here + + def getColorFromName(self, name, seed, base255=True): + random.seed(seed + name) + hue = random.random() + random.seed((seed + name)*2) + saturation = random.random()/4 + 0.75 #i.e., between 0.75 and 1 + random.seed((seed + 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 \ No newline at end of file diff --git a/analysis/datastats.py b/analysis/particleInfo.py similarity index 71% rename from analysis/datastats.py rename to analysis/particleInfo.py index c5fb2b9fbfc3d7955ea98c72b1e01eaf2b955d05..55c1d0a41cac0d1b781867fba2713f3743b8f5cc 100644 --- a/analysis/datastats.py +++ b/analysis/particleInfo.py @@ -18,17 +18,16 @@ You should have received a copy of the GNU General Public License along with this program, see COPYING. If not, see . """ -import os +#import os import numpy as np import operator -from PyQt5 import QtWidgets, QtCore +#from PyQt5 import QtWidgets, QtCore -from analysis import importSpectra -try: - from dataset import loadData, recursiveDictCompare - print('exported dataset methods from datastats') -except: - print('failed exported dataset methods from datastats') +#try: +# from dataset import loadData, recursiveDictCompare +# print('exported dataset methods from datastats') +#except: +# print('failed exported dataset methods from datastats') class ParticleContainer(object): def __init__(self, parent): @@ -89,7 +88,12 @@ class ParticleContainer(object): meas.setAssignment(hqiList[scanIndex]) indicesOfTransferredAssignments.append(scanIndex) assert np.unique(indicesOfTransferredAssignments) == np.unique((range(len(hqiList)))) - + + def getParticleOfIndex(self, index): + particle = self.particles[index] + assert particle.index == index, f'particle.index ({particle.index}) does match requested index in particleList ({index})' + return particle + def getNumberOfParticles(self): return len(self.particles) @@ -99,9 +103,9 @@ class ParticleContainer(object): def getParticleContoursByIndex(self, partIndices): contours = [] - for part in self.particles: - if part.index in partIndices: - contours.append(part.contour) + for index in partIndices: + particle = self.getParticleOfIndex(index) + contours.append(particle.contour) return contours def getMeasurementPixelCoords(self): @@ -118,6 +122,14 @@ class ParticleContainer(object): num += 1 return num + def getNumberOfSpectraOfParticle(self, particleIndex): + particle = self.getParticleOfIndex(particleIndex) + return particle.getNumberOfMeasurements() + + def getSpectraIndicesOfParticle(self, particleIndex): + particle = self.getParticleOfIndex(particleIndex) + return particle.getMeasurementIndices() + def getListOfParticleAssignments(self): particleAssignments = [] for particle in self.particles: @@ -130,7 +142,12 @@ class ParticleContainer(object): hqis.append(particle.getHighestHQI()) return hqis - def getInconsistentParticles(self): #i.e., particles that have multiple measurements with different assignments + def getHQIOfSpectrumIndex(self, specIndex): + for particle in self.particles: + if specIndex in particle.getMeasurementIndices(): + return particle.getHQIOfMeasurementIndex(specIndex) + + def testForInconsistentParticles(self): #i.e., particles that have multiple measurements with different assignments self.inconsistentParticles = [] for particle in self.particles: if not particle.measurementsHaveSameOrigAssignment(): @@ -141,32 +158,45 @@ class ParticleContainer(object): print(f'Particle with index {particle.index} has the following assignments:') for assignment in particle.getOrigMeasurementAssignments(): print(assignment) + else: + print('All particles have consistent spectra assignments') - return self.inconsistentParticles - def getSizesOfAllParticles(self): particleSizes = [] for particle in self.particles: particleSizes.append(particle.getParticleSize()) + return particleSizes def getShortSizesOfAllParticles(self): shortSizes = [] for particle in self.particles: - shortSizes.append(particle.getShortParticleSize()) + shortSizes.append(particle.getShortParticleSize())# + return shortSizes def getSizesOfParticleType(self, assignment): particleSizes = [] for particle in self.particles: if particle.getParticleAssignment() == assignment: particleSizes.append(particle.getParticleSize()) + return particleSizes - def getSizeOfParticleByIndex(self, index): + def getIndicesOfParticleType(self, assignment): + indices = [] for particle in self.particles: - if particle.index == index: - return particle.getParticleSize() + if particle.getParticleAssignment() == assignment: + indices.append(particle.index) + return indices + + def getSizeOfParticleByIndex(self, index): + particle = self.getParticleOfIndex(index) + return particle.getParticleSize() + def getUniquePolymers(self): + typeHist = self.getTypeHistogram() + return typeHist.keys() + def getTypeHistogram(self): - uniquePolymers = self.getUniquePolymers() + uniquePolymers = np.unique(self.getListOfParticleAssignments()) typehistogram = {i: 0 for i in uniquePolymers} for assignment in self.getListOfParticleAssignments(): typehistogram[assignment] += 1 @@ -175,18 +205,15 @@ class ParticleContainer(object): #convert back to dict final_typehistogram = {i[0]: i[1] for i in sorted_typehistogram} return final_typehistogram - - def getUniquePolymers(self): - return np.unique(self.getListOfParticleAssignments()) class Particle(object): def __init__(self, index): self.index = index - self.longSize_ellipse = None - self.shortSize_ellipse = None - self.longSize_box = None - self.shortSize_box = None + self.longSize_ellipse = np.nan + self.shortSize_ellipse = np.nan + self.longSize_box = np.nan + self.shortSize_box = np.nan self.area = None self.contour = None self.measurements = [] @@ -212,6 +239,17 @@ class Particle(object): hqis.append(meas.getHQI()) return max(hqis) + def getHQIOfMeasurementIndex(self, index): + for meas in self.measurements: + if meas.ramanScanIndex == index: + return meas.getHQI() + + def getMeasurementIndices(self): + indices = [] + for meas in self.measurements: + indices.append(meas.ramanScanIndex) + return indices + def getMeasurements(self): return self.measurements @@ -225,23 +263,27 @@ class Particle(object): return assignments[indexOfHighestHQI] def getParticleSize(self): - if self.longSize_ellipse is not None: - return round(self.longSize_ellipse) - elif self.longSize_box is not None: - return round(self.longSize_box) + if not np.isnan(self.longSize_ellipse): + size = self.longSize_ellipse + elif not np.isnan(self.longSize_box): + size = self.longSize_box else: print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}') raise ValueError + assert size is not None, f'Error, size or particle {self.index} is None' + return round(size) def getShortParticleSize(self): - if self.shortSize_ellipse is not None: + if not np.isnan(self.shortSize_ellipse): return round(self.shortSize_ellipse) - elif self.shortSize_box is not None: - return round(self.shortSize_box ) + elif not np.isnan(self.shortSize_box): + return round(self.shortSize_box) else: print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}') raise ValueError + def getNumberOfMeasurements(self): + return len(self.measurements) def measurementsHaveSameOrigAssignment(self): allResults = [meas.getOrigAssignment() for meas in self.measurements] @@ -300,58 +342,58 @@ class Measurement(object): -def readDataStats(fname): - ds = loadData(fname) - datastats = DataStats(ds) - datastats.update() - datastats.loadParticleData() - minHQI = datastats.dataset.resultParams['minHQI'] - compHQI = datastats.dataset.resultParams['compHQI'] - datastats.formatResults(minHQI, compHQI) - datastats.createHistogramData() - return datastats - -class DataStats(object): - def __init__(self, dataset): - self.dataset = dataset - - self.spectraResults = None #entire List of all spectra assignments - self.additiveResults = None #entire List of all additives - 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.spectra = None #acquired spectra - self.indices = None #assignment of what spectra-indices belong to what substance - - self.particles2spectra = None - self.manualPolymers = {} - self.manualAdditives = {} - - def resetResults(self, spectraResults, additiveResults, hqis, addhqis): - self.spectraResults = spectraResults - self.additiveResults = additiveResults - self.hqis = hqis - self.addhqis = addhqis - - def update(self): - print('updating data from', self.dataset.name) - self.spectraResults = self.dataset.results['polymers'] - self.additiveResults = self.dataset.results['additives'] - self.hqis = self.dataset.results['hqis'] - self.addhqis = self.dataset.results['additive_hqis'] - - self.colorSeed = self.dataset.colorSeed - if type(self.colorSeed) != str: - self.colorSeed = 'default' - - #load Spectra - if self.dataset.spectraPath is None: - fname = os.path.join(self.dataset.path, self.dataset.name + '_000_Spec.Data 1.txt') - else: - fname = self.dataset.spectraPath - return self.loadSpectra(fname) - +#def readDataStats(fname): +# ds = loadData(fname) +# datastats = DataStats(ds) +# datastats.update() +# datastats.loadParticleData() +# minHQI = datastats.dataset.resultParams['minHQI'] +# compHQI = datastats.dataset.resultParams['compHQI'] +# datastats.formatResults(minHQI, compHQI) +# datastats.createHistogramData() +# return datastats +# +#class DataStats(object): +# def __init__(self, dataset): +# self.dataset = dataset +# +# self.spectraResults = None #entire List of all spectra assignments +# self.additiveResults = None #entire List of all additives +# 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.spectra = None #acquired spectra +# self.indices = None #assignment of what spectra-indices belong to what substance +# +# self.particles2spectra = None +# self.manualPolymers = {} +# self.manualAdditives = {} +# +# def resetResults(self, spectraResults, additiveResults, hqis, addhqis): +# self.spectraResults = spectraResults +# self.additiveResults = additiveResults +# self.hqis = hqis +# self.addhqis = addhqis +# +# def update(self): +# print('updating data from', self.dataset.name) +# self.spectraResults = self.dataset.results['polymers'] +# self.additiveResults = self.dataset.results['additives'] +# self.hqis = self.dataset.results['hqis'] +# self.addhqis = self.dataset.results['additive_hqis'] +# +# self.colorSeed = self.dataset.colorSeed +# if type(self.colorSeed) != str: +# self.colorSeed = 'default' +# +# #load Spectra +# if self.dataset.spectraPath is None: +# fname = os.path.join(self.dataset.path, self.dataset.name + '_000_Spec.Data 1.txt') +# else: +# fname = self.dataset.spectraPath +# return self.loadSpectra(fname) +# # def loadSpectra(self, fname): # import time # t0 = time.time() @@ -422,18 +464,18 @@ class DataStats(object): # if self.currentAdditives is not None: # self.currentAdditives[self.addhqis < compHqi] = 'unknown' - def getUniquePolymers(self): - if self.currentPolymers is None: - return None - return self.uniquePolymers - - def getParticleStats(self): - particlestats = np.array(self.dataset.particlestats) - pixelscale = self.dataset.getPixelScale() - #convert to mikrometer scale - particlestats[:,:5] *= pixelscale - particlestats[:,4] *= pixelscale #again for the area... - return particlestats +# def getUniquePolymers(self): +# if self.currentPolymers is None: +# return None +# return self.uniquePolymers +# +# def getParticleStats(self): +# particlestats = np.array(self.dataset.particlestats) +# pixelscale = self.dataset.getPixelScale() +# #convert to mikrometer scale +# particlestats[:,:5] *= pixelscale +# particlestats[:,4] *= pixelscale #again for the area... +# return particlestats # def createHistogramData(self): # particlestats = self.getParticleStats() @@ -473,22 +515,22 @@ class DataStats(object): # nonentries = np.where(self.sorted_additives[i] == 'none') # self.sorted_additives[i] = np.delete(self.sorted_additives[i], nonentries) # return True - - def saveAnalysisResults(self, minHQI, compHQI): - self.dataset.results = {'polymers': self.spectraResults, - 'hqis': self.hqis, - 'additives': self.additiveResults, - 'additive_hqis': self.addhqis} - - self.dataset.resultParams = {'minHQI': minHQI, - 'compHQI': compHQI} - self.dataset.save() - testresult = self.testRead() - print('saved dataset; Valid:', testresult) - return testresult - - - def testRead(self): - statsread = readDataStats(self.dataset.fname) - return recursiveDictCompare(self.__dict__, statsread.__dict__) +# +# def saveAnalysisResults(self, minHQI, compHQI): +# self.dataset.results = {'polymers': self.spectraResults, +# 'hqis': self.hqis, +# 'additives': self.additiveResults, +# 'additive_hqis': self.addhqis} +# +# self.dataset.resultParams = {'minHQI': minHQI, +# 'compHQI': compHQI} +# self.dataset.save() +# testresult = self.testRead() +# print('saved dataset; Valid:', testresult) +# return testresult +# +# +# def testRead(self): +# statsread = readDataStats(self.dataset.fname) +# return recursiveDictCompare(self.__dict__, statsread.__dict__) \ No newline at end of file diff --git a/dataset.py b/dataset.py index 0717eb13d5a1fce2ce402b521a38a7677921eee6..2076b0b41d79fcf5260e7c5ead60690103bc9cc8 100644 --- a/dataset.py +++ b/dataset.py @@ -24,7 +24,7 @@ import numpy as np import cv2 from helperfunctions import cv2imread_fix, cv2imwrite_fix from copy import copy -from analysis.datastats import ParticleContainer +from analysis.particleInfo import ParticleContainer currentversion = 3 @@ -250,6 +250,7 @@ class DataSet(object): self.version = 2 if self.version == 2: + self.particleContainer = ParticleContainer(self) def recreateMeasurement2ParticleFromScanIndices(): measurements2particles = [[int(np.where(self.ramanscansortindex == i)[0])] for i in range(len(self.ramanscansortindex))] return measurements2particles @@ -280,7 +281,8 @@ class DataSet(object): specIndex = meas.ramanScanIndex meas.setAssignment(self.results['polymers'][specIndex]) meas.setHQI(self.results['hqis'][specIndex]) - + + self.particleContainer.testForInconsistentParticles() # self.version = 3 # add later conversion for higher version numbers here @@ -374,7 +376,7 @@ class DataSet(object): self.name = os.path.splitext(os.path.basename(self.fname))[0] def getSpectraFileName(self): - return os.path.join(self.path + 'spectra.npy') + return os.path.join(self.path, 'spectra.npy') def getImageName(self): return os.path.join(self.path, 'fullimage.tif') diff --git a/fakeData/image.bmp b/fakeData/image.bmp old mode 100755 new mode 100644 diff --git a/gepard.py b/gepard.py index 72683132b26fda394e2457ad8f7efd3fbc08c1bc..11e6678ac6bd3b19689b0761f73e4259085533a1 100644 --- a/gepard.py +++ b/gepard.py @@ -23,7 +23,7 @@ from sampleview import SampleView from scalebar import ScaleBar from ramancom.ramancontrol import defaultPath from ramancom.ramanSwitch import RamanSwitch -from colorlegend import ColorLegend +from analysis.colorlegend import ColorLegend import os class GEPARDMainWindow(QtWidgets.QMainWindow): diff --git a/sampleview.py b/sampleview.py index dfae213bf99bde60d5ff11fb2c28b79a68b6d17c..39bebe6c277b6958bb039384296a5bef5789318e 100644 --- a/sampleview.py +++ b/sampleview.py @@ -28,7 +28,7 @@ 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 viewitems import FitPosIndicator, Node, Edge, ScanIndicator, RamanScanIndicator, SegmentationContour from helperfunctions import polygoncovering, cv2imread_fix import cv2 from ramancom.configRaman import RamanConfigWin @@ -76,8 +76,9 @@ class SampleView(QtWidgets.QGraphicsView): self.ramanscanitems = [] self.imgdata = None self.isblocked = False - self.contouritem = SegmentationContours(self) - scene.addItem(self.contouritem) + self.contourItems = [] +# 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) @@ -173,7 +174,8 @@ class SampleView(QtWidgets.QGraphicsView): self.detectionwidget.close() self.detectionwidget.destroy() self.ramanwidget.setVisible(False) - self.contouritem.resetContours([]) +# self.contouritem.resetContours([]) + self.updateParticleContours() self.mode = mode self.loadPixmap(self.microscopeMode) if mode == "OpticalScan": @@ -223,7 +225,8 @@ class SampleView(QtWidgets.QGraphicsView): widget.destroy() del widget - self.contouritem.resetContours() +# self.contouritem.resetContours() + self.updateParticleContours() # if self.dataset is not None: # del self.dataset # self.scene().removeItem(self.contouritem) @@ -377,7 +380,8 @@ class SampleView(QtWidgets.QGraphicsView): @QtCore.pyqtSlot(str) def detectionUpdate(self): - self.contouritem.resetContours(self.dataset.particlecontours) +# self.contouritem.resetContours(self.dataset.particlecontours) + self.updateParticleContours() self.prepareAnalysis() self.update() @@ -390,7 +394,8 @@ class SampleView(QtWidgets.QGraphicsView): data = self.imgdata fname = self.dataset.getImageName() if self.mode == "ParticleDetection" or self.mode == "ParticleAnalysis": - self.contouritem.resetContours(self.dataset.particlecontours) +# self.contouritem.resetContours(self.dataset.particlecontours) + self.updateParticleContours() if data is None and os.path.exists(fname): data = cv2.cvtColor(cv2imread_fix(fname), cv2.COLOR_BGR2RGB) self.imgdata = data @@ -500,7 +505,18 @@ class SampleView(QtWidgets.QGraphicsView): item = RamanScanIndicator(self, i+1, 20, (data[i][0],data[i][1])) self.scene().addItem(item) self.ramanscanitems.append(item) - + + def updateParticleContours(self): + for cnt in self.contourItems: + self.scene().removeItem(cnt) + self.contourItems = [] + if self.dataset is not None: + for contour in self.dataset.particleContainer.getParticleContours(): + newCnt = SegmentationContour(self, contour) + self.contourItems.append(newCnt) + self.scene().addItem(newCnt) + + def highLightRamanIndex(self, index): if index < len(self.ramanscanitems): for item in self.ramanscanitems: diff --git a/viewitems.py b/viewitems.py index 4a1fa54ee8fbbf83d65ca1413206bae25a45067b..25d5fdb3dfd8d627c76f05cce26fa05810535557 100644 --- a/viewitems.py +++ b/viewitems.py @@ -21,6 +21,56 @@ If not, see . import numpy as np from PyQt5 import QtCore, QtWidgets, QtGui +class SegmentationContour(QtWidgets.QGraphicsItem): + def __init__(self, parent, contourData, pos=(0,0)): + super().__init__() + self.parent = parent + self.setPos(pos[0], pos[1]) + self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable) + + self.brect = QtCore.QRectF(0,0,1,1) + self.contourData = contourData + self.polygon = None + self.color = QtCore.Qt.green + self.isSelected = False + self.getBrectAndPolygon() + + def getBrectAndPolygon(self): + polygon = QtGui.QPolygonF() + x0 = self.contourData[:,0,0].min() + x1 = self.contourData[:,0,0].max() + y0 = self.contourData[:,0,1].min() + y1 = self.contourData[:,0,1].max() + for point in self.contourData: + polygon.append(QtCore.QPointF(point[0,0], point[0,1])) + + self.brect.setCoords(x0, y0, x1, y1) + self.polygon = polygon + + def boundingRect(self): + return self.brect + + def paint(self, painter, option, widget): + if self.polygon is not None: + painter.setPen(QtCore.Qt.green) + if self.parent.analysiswidget is not None: + nonePaintMode = self.parent.analysiswidget.noOverlayAct.isChecked() + else: + nonePaintMode = False + + if self.isSelected: + painter.setPen(QtGui.QColor(int(self.color.red()*0.7), int(self.color.green()*0.7), int(self.color.blue()*0.7), self.color.alpha())) + if not nonePaintMode: + painter.setBrush(self.color) + else: + alpha = 200 + if not nonePaintMode: + painter.setBrush(QtGui.QColor(200, 200, 200, alpha)) + painter.setPen(QtCore.Qt.white) + + painter.drawPolygon(self.polygon) + + class SegmentationContours(QtWidgets.QGraphicsItem): def __init__(self, parent, contours=[], pos=(0,0)): super().__init__()