Commit 582761bd authored by JosefBrandt's avatar JosefBrandt

Modularization of analysisview, DocStrings

parent 07883df1
......@@ -10,3 +10,5 @@ analysis/database_config\.txt
*.c
external/build/
.idea/
......@@ -135,7 +135,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.openAct.setShortcut("Ctrl+O")
self.openAct.triggered.connect(self.open)
self.importAct = QtWidgets.QAction("&Import Project...", self)
self.importAct = QtWidgets.QAction("&Import Zeiss Project...", self)
self.importAct.setShortcut("Ctrl+I")
self.importAct.triggered.connect(self.importProject)
......
......@@ -23,9 +23,168 @@ 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 numpy as np
from .colorlegend import getColorFromNameWithSeed
class ParticleIndicator(QtWidgets.QPushButton):
class SizeHistogramPlot(QtWidgets.QGroupBox):
def __init__(self, dataset):
super(SizeHistogramPlot, self).__init__()
self.dataset = dataset
self.minX = 3
self.maxX = 1E4
self.fontsize = 15
layout = QtWidgets.QHBoxLayout()
self.setLayout(layout)
self.sizeHistogramCanvas = FigureCanvas(Figure())
self.sizeHist_ax = self.sizeHistogramCanvas.figure.subplots()
self.sizeHist_ax.axis('off')
self.sizeHistogramCanvas.figure.subplots_adjust(left=0.1, top=0.93, bottom=0.15, right=0.995)
histNavigation = NavigationToolbar(self.sizeHistogramCanvas, self)
histNavigation.setOrientation(QtCore.Qt.Vertical)
histNavigation.setFixedWidth(50)
layout.addWidget(histNavigation)
layout.addWidget(self.sizeHistogramCanvas)
def drawHistograms(self, listOfHistograms):
if type(listOfHistograms) != list:
assert type(listOfHistograms) == SizeHistogramData
listOfHistograms = [listOfHistograms]
self.resetPlotFormat()
self.adjustPlotLimits(listOfHistograms)
for histData in listOfHistograms:
self.drawHistogram(histData)
self.drawLegend()
self.sizeHistogramCanvas.draw()
def resetPlotFormat(self):
self.sizeHist_ax.clear()
self.sizeHist_ax.axis('on')
self.sizeHist_ax.tick_params(axis='both', which='both', labelsize = self.fontsize)
self.sizeHist_ax.set_xlabel('Size (µm)', fontsize = self.fontsize)
self.sizeHist_ax.set_ylabel('Number', fontsize = self.fontsize)
self.sizeHist_ax.set_title('Size Distribution', fontsize=self.fontsize)
def adjustPlotLimits(self, listOfHistograms):
xmin, xmax = None, None
for histData in listOfHistograms:
if xmin is None:
xmin = np.clip(10**histData.minSizeExponent, self.minX, self.maxX)
xmax = np.clip(10**histData.maxSizeExponent, self.minX, self.maxX)
else:
currentXMin = np.clip(10**histData.minSizeExponent, self.minX, self.maxX)
currentXMax = np.clip(10**histData.maxSizeExponent, self.minX, self.maxX)
xmin = min(xmin, currentXMin)
xmax = max(xmax, currentXMax)
self.sizeHist_ax.set_xlim(xmin, xmax)
def drawHistogram(self, histData):
label = histData.label
x_values = histData.sizeHist_binCenters
y_values = histData.sizeHist_abundancies
color = getColorFromNameWithSeed(label, self.dataset.colorSeed, base255=False)
self.sizeHist_ax.semilogx(x_values, y_values, label=label, color=color)
def drawLegend(self):
self.sizeHist_ax.legend(prop = {'size': self.fontsize})
class SizeHistogramData(object):
def __init__(self, label, listOfSizes):
self.label = label
self.listOfSizes = listOfSizes
self.minSizeExponent = 0.1
self.maxSizeExponent = 3
self.numBins = 20
self.sizeHist_binCenters = None
self.sizeHist_abundancies = None
self.createSizeHistogramData()
def createSizeHistogramData(self):
assert self.listOfSizes is not None
bins = np.logspace(self.minSizeExponent, self.maxSizeExponent, self.numBins)
self.sizeHist_abundancies, binEdges = np.histogram(self.listOfSizes, bins)
self.sizeHist_binCenters = self.getBinCenters(binEdges)
def getBinCenters(self, binEdges):
binCenters = []
for i in range(self.numBins-1):
currentCenter = np.mean((binEdges[i], binEdges[i+1]))
binCenters.append(currentCenter)
return binCenters
class TypeHistogramPlot(QtWidgets.QScrollArea):
indexClicked = QtCore.pyqtSignal(int)
def __init__(self, dataset):
super(TypeHistogramPlot, self).__init__()
self.dataset = dataset
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.abundancyIndicators = []
def updateTypeHistogram(self, typeHistogram):
self.resetPlot()
colorList = []
abundancyList = []
labelList = []
for polymType in typeHistogram:
text = f'{typeHistogram[polymType]} x {polymType}'
labelList.append(text)
abundancyList.append(typeHistogram[polymType])
curColor = getColorFromNameWithSeed(polymType, self.dataset.colorSeed)
colorList.append(QtGui.QColor(*curColor))
types = list(zip(abundancyList, labelList, colorList))
numtotal= sum(abundancyList)
for index, entry in enumerate(types):
num, text, color = entry
indicator = AbundancyIndicator(num, numtotal, color, text)
self.indicatorbox.addWidget(indicator)
indicator.clicked.connect(self._getIndexFunction(index))
self.abundancyIndicators.append(indicator)
self.indicatorbox.addStretch()
self.view.update()
def resetPlot(self):
for indicator in self.abundancyIndicators:
self.indicatorbox.removeWidget(indicator)
indicator.setParent(None)
indicator.destroy()
self.indicatorbox.takeAt(0)
self.abundancyIndicators = []
def _getIndexFunction(self, index):
return lambda : self.indexClicked.emit(index)
class AbundancyIndicator(QtWidgets.QPushButton):
def __init__(self, number, numtotal, color, text, parent=None):
super().__init__(parent)
self.number = number
......@@ -61,54 +220,12 @@ class ParticleIndicator(QtWidgets.QPushButton):
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):
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
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
self.fontsize = 15
layout = QtWidgets.QHBoxLayout()
self.canvas = FigureCanvas(Figure())
......@@ -131,14 +248,14 @@ class SpectraPlot(QtWidgets.QGroupBox):
#draw Sample Spectrum
self.spec_axis.axis("on")
self.spec_axis.clear()
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.tick_params(axis='both', which='both', labelsize = self.fontsize)
self.spec_axis.set_xlabel('Wavenumber (cm-1)', fontsize = self.fontsize)
self.spec_axis.set_ylabel('Counts', fontsize = self.fontsize)
if self.spectra is not None:
specInfo = f'ScanPoint Number {specIndex+1}, with assignment {assignment} (hqi = {hqi})'
self.spec_axis.plot(self.spectra[:, 0], self.spectra[:, specIndex+1])
self.spec_axis.set_title(specInfo, fontsize=13)
self.spec_axis.set_title(specInfo, fontsize = self.fontsize)
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)
......@@ -152,13 +269,10 @@ class SpectraPlot(QtWidgets.QGroupBox):
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.tick_params(axis='both', which='both', labelsize = self.fontsize)
self.reference_axis.plot(ref_wavenumber, ref_intensity, color = 'r')
self.reference_axis.set_ylabel('Ref. Intensity', fontsize = 15, color = 'r')
self.reference_axis.set_ylabel('Ref. Intensity', fontsize = self.fontsize, 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]))
self.canvas.draw()
\ No newline at end of file
\ No newline at end of file
This diff is collapsed.
#!/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. <bittrich-lars@ipfdd.de>
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 <https://www.gnu.org/licenses/>.
"""
from PyQt5 import QtWidgets, QtCore
class PolymerTypeCheckboxes(QtWidgets.QScrollArea):
PolymerCheckBoxToggled = QtCore.pyqtSignal()
def __init__(self):
super(PolymerTypeCheckboxes, self).__init__()
self.setWidgetResizable(True)
widget = QtWidgets.QWidget()
self.setWidget(widget)
self.layout = QtWidgets.QVBoxLayout(widget)
self.checkBoxToggledSignalFunc = lambda: self.PolymerCheckBoxToggled.emit()
self.createCheckBoxGroup()
self.addShowAllCheckbox()
self.polymerCheckBoxes = []
def createCheckBoxGroup(self):
'''
Create groupbox for holding the individual checkeboxes for each polymer type
:return:
'''
self.checkBoxGroup = QtWidgets.QGroupBox('Display Polymer Types:')
self.checkBoxGroupLayout = QtWidgets.QVBoxLayout()
self.checkBoxGroup.setLayout(self.checkBoxGroupLayout)
self.layout.addWidget(self.checkBoxGroup)
def addShowAllCheckbox(self):
'''
Adds a checkbox for toggling whether to display all polymer types in the connected plots,
or only the ones that are selected.
:return:
'''
self.showAllCheckBox = QtWidgets.QCheckBox('Show All')
self.showAllCheckBox.setChecked(True)
self.showAllCheckBox.setDisabled(True)
self.showAllCheckBox.stateChanged.connect(self.checkBoxToggledSignalFunc)
self.checkBoxGroupLayout.addWidget(self.showAllCheckBox)
def updatePolymerCheckBoxes(self, polymerTypes):
'''
Takes a list of polymerTypes and recreates the polymer Checkboxes accordingly.
:return:
'''
lastSelectedCheckBoxNames = self.getSelectedPolymers()
self.resetCheckBoxGroup()
self.polymerCheckBoxes = []
for polymer in polymerTypes:
newCheckBox = QtWidgets.QCheckBox(self)
newCheckBox.setText(polymer)
newCheckBox.stateChanged.connect(self.checkBoxToggledSignalFunc)
if polymer in lastSelectedCheckBoxNames:
newCheckBox.setChecked(True)
self.polymerCheckBoxes.append(newCheckBox)
self.checkBoxGroupLayout.addWidget(newCheckBox)
self.checkBoxGroupLayout.addStretch()
self.checkBoxGroup.setLayout(self.checkBoxGroupLayout)
def resetCheckBoxGroup(self):
self.checkBoxGroup.setParent(None)
self.createCheckBoxGroup()
self.addShowAllCheckbox()
self.showAllCheckBox.setDisabled(False)
def getSelectedPolymers(self):
'''
Return list of currently selected polymers
:return:
'''
return [checkbox.text() for checkbox in self.polymerCheckBoxes if checkbox.isChecked()]
class PolymerNavigationToolbar(QtWidgets.QGroupBox):
WidgetsUpdated = QtCore.pyqtSignal()
JumpToIndicatedSpec = QtCore.pyqtSignal()
def __init__(self, particleContainer):
super(PolymerNavigationToolbar, self).__init__()
self.setTitle('Navigate through polymers')
self.layout = QtWidgets.QHBoxLayout()
self.setLayout(self.layout)
self.setDisabled(True)
self.particleContainer = particleContainer
self.currentSpectrumIndex = None
self.currentParticleIndex = None
self.createWidgets()
def createWidgets(self):
self.specNumberSelector = QtWidgets.QSpinBox()
self.specNumberSelector.setMinimumWidth(100)
self.specNumberSelector.setMinimum(1)
numSpectra = self.particleContainer.getNumberOfMeasurements()
self.specNumberSelector.setMaximum(numSpectra)
self.jumpToSpecBtn = QtWidgets.QPushButton('Jump To Spectrum of Number:')
self.jumpToSpecBtn.released.connect(lambda: self.JumpToIndicatedSpec.emit())
self.typeSelectorCombo = QtWidgets.QComboBox()
self.typeSelectorCombo.setMinimumWidth(150)
self.particleSelector = QtWidgets.QSpinBox()
self.particleNumberLabel = QtWidgets.QLabel('of xx particles; ')
self.spectrumSelector = QtWidgets.QSpinBox()
self.spectrumNumberLabel = QtWidgets.QLabel('of xx spectra')
for spinbox in [self.particleSelector, self.spectrumSelector]:
spinbox.setMinimum(1)
spinbox.setSingleStep(1)
spinbox.setValue(1)
self.typeSelectorCombo.currentIndexChanged.connect(self.setTypeSelector)
self.particleSelector.valueChanged.connect(self.setParticleSelector)
self.spectrumSelector.valueChanged.connect(self.setSpecSelector)
self.layout.addWidget(self.jumpToSpecBtn)
self.layout.addWidget(self.specNumberSelector)
self.layout.addWidget(QtWidgets.QLabel('Select Polymer Type:'))
self.layout.addWidget(self.typeSelectorCombo)
self.layout.addStretch()
self.layout.addWidget(QtWidgets.QLabel('Select Particle'))
self.layout.addWidget(self.particleSelector)
self.layout.addWidget(self.particleNumberLabel)
self.layout.addStretch()
self.layout.addWidget(QtWidgets.QLabel('Select Spectrum'))
self.layout.addWidget(self.spectrumSelector)
self.layout.addWidget(self.spectrumNumberLabel)
def keyPressEvent(self, event):
"""
If the enter key is pressed, all views are updated to the indicated spectrum number
"""
if event.key() in [QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter]:
self.JumpToIndicatedSpec.emit()
def updateWidgets(self):
"""
Whenever the particle assignments in the particleContainer have changed, the widges have to be updated.
:return:
"""
uniquePolymers = self.particleContainer.getUniquePolymers()
if len(uniquePolymers) > 0:
self.setDisabled(False)
self.typeSelectorCombo.currentIndexChanged.disconnect()
self.typeSelectorCombo.clear()
self.typeSelectorCombo.addItems(uniquePolymers)
self.typeSelectorCombo.currentIndexChanged.connect(self.setTypeSelector)
numSpectra = self.particleContainer.getNumberOfMeasurements()
self.specNumberSelector.setMaximum(numSpectra)
self.setTypeSelector()
else:
self.setDisabled(True)
@QtCore.pyqtSlot(int)
def setWidgetsToNewParticleIndex(self, particleIndex):
"""
When a particle is selected in the sampleview, this method makes updating all widgets accordingly.
:return:
"""
try:
self.particleSelector.valueChanged.disconnect()
self.spectrumSelector.valueChanged.disconnect()
self.typeSelectorCombo.currentIndexChanged.disconnect()
except TypeError:
pass #signals were not connected, nothing to disconnect...
assignment = self.particleContainer.getParticleAssignmentByIndex(particleIndex)
self.typeSelectorCombo.setCurrentText(assignment)
self.currentParticleIndex = particleIndex
partIndices = self.particleContainer.getIndicesOfParticleType(assignment)
self.particleSelector.setMaximum(len(partIndices))
self.particleNumberLabel.setText(f'of {len(partIndices)} particles; ')
self.particleSelector.setValue(partIndices.index(particleIndex)+1)
specIndices = self.particleContainer.getSpectraIndicesOfParticle(self.currentParticleIndex)
self.spectrumSelector.setValue(1)
self.spectrumSelector.setMaximum(len(specIndices))
self.spectrumNumberLabel.setText(f'of {len(specIndices)} spectra')
self.currentSpectrumIndex = specIndices[self.spectrumSelector.value()-1]
self.specNumberSelector.setValue(self.currentSpectrumIndex+1)
self.typeSelectorCombo.currentIndexChanged.connect(self.setTypeSelector)
self.spectrumSelector.valueChanged.connect(self.setSpecSelector)
self.particleSelector.valueChanged.connect(self.setParticleSelector)
self.WidgetsUpdated.emit()
def setTypeSelector(self):
assignment = self.typeSelectorCombo.currentText()
partIndices = self.particleContainer.getIndicesOfParticleType(assignment)
self.particleSelector.setMaximum(len(partIndices))
self.particleNumberLabel.setText(f'of {len(partIndices)} particles; ')
self.particleSelector.setValue(1)
self.setParticleSelector()
def setParticleSelector(self):
assignment = self.typeSelectorCombo.currentText()
if assignment != '':
partIndices = self.particleContainer.getIndicesOfParticleType(assignment)
self.currentParticleIndex = partIndices[self.particleSelector.value()-1]
specIndices = self.particleContainer.getSpectraIndicesOfParticle(self.currentParticleIndex)
self.spectrumSelector.setValue(1)
self.spectrumSelector.setMaximum(len(specIndices))
self.spectrumNumberLabel.setText(f'of {len(specIndices)} spectra')
self.setSpecSelector()
def setSpecSelector(self):
specIndices = self.particleContainer.getSpectraIndicesOfParticle(self.currentParticleIndex)
self.currentSpectrumIndex = specIndices[self.spectrumSelector.value()-1]
self.specNumberSelector.setValue(self.currentSpectrumIndex+1)
self.WidgetsUpdated.emit()
self.JumpToIndicatedSpec.emit()
class FindColoredParticleWindow(QtWidgets.QWidget):
ParticleOfIndexSelected = QtCore.pyqtSignal(int)
def __init__(self, analysisparent):
super(FindColoredParticleWindow, self).__init__()
self.analysisparent = analysisparent
self.particleContainer = analysisparent.particleContainer
self.coloredParticles = None
self.numColoredParticles = None
self.currentParticleIndex = None
self.label = QtWidgets.QLabel()
self.prevBtn = QtWidgets.QPushButton("Go to partilcle 0", self)
self.prevBtn.released.connect(self.goToPreviousParticle)
self.nextBtn = QtWidgets.QPushButton("Go to particle 1", self)
self.nextBtn.released.connect(self.goToNextParticle)
self.cancelBtn = QtWidgets.QPushButton("Cancel", self)
self.cancelBtn.released.connect(self.close)
self.createLayout()
self.getColoredParticles()
self.goToFirstParticle()
def createLayout(self):
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
layout.addWidget(self.label)
layout.addWidget(self.prevBtn)
layout.addWidget(self.nextBtn)
layout.addWidget(self.cancelBtn)
self.show()
def getColoredParticles(self):
"""
Retrieves a list of Particles from the PrticleContainer with all particles that are not unknown and not white
:return:
"""
self.coloredParticles = self.particleContainer.getIndicesOfColoredNotUnknownParticles()
self.numColoredParticles = len(self.coloredParticles)
def goToFirstParticle(self):
self.currentParticleIndex = 0
self.enableDisableButtons()
self.updateToCurrentIndex()
def goToNextParticle(self):
if self.currentParticleIndex < self.numColoredParticles-1:
self.currentParticleIndex += 1
self.updateToCurrentIndex()
self.enableDisableButtons()
def goToPreviousParticle(self):
if self.currentParticleIndex > 0:
self.currentParticleIndex -= 1
self.updateToCurrentIndex()
self.enableDisableButtons()
def enableDisableButtons(self):
"""
Checks the previous and next particle buttons if they have to be enabled or disabled
:return:
"""
self.prevBtn.setEnabled(True)
self.nextBtn.setEnabled(True)
if self.currentParticleIndex == 0:
self.prevBtn.setDisabled(True)
self.prevBtn.setText('No previous particle.')