Commit 1c6ae7e5 authored by JosefBrandt's avatar JosefBrandt

Basic plots working, proceeding to contours
parent d1ae9d81
#!/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, 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
This diff is collapsed.
......@@ -206,84 +206,3 @@ class AdditiveViewer(QtWidgets.QWidget):
self.ax.tick_params(axis='both', which='both', labelsize=15)
\ No newline at end of file
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
......@@ -20,6 +20,8 @@ If not, see <https://www.gnu.org/licenses/>.
"""
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
......@@ -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 <https://www.gnu.org/licenses/>.
"""
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):
......@@ -90,6 +89,11 @@ class ParticleContainer(object):
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)
return self.inconsistentParticles
else:
print('All particles have consistent spectra assignments')
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:
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
......@@ -176,17 +206,14 @@ class ParticleContainer(object):
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