Commit 7d28e523 authored by JosefBrandt's avatar JosefBrandt

Merge branch 'RefactoringAnalysisModules'

parents 8fc15b1e 582761bd
__pycache__/
gepard\.cfg
analysis/database_config\.txt
*.so
*.c
external/build/
.idea/
......@@ -18,18 +18,17 @@ 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 QtCore, QtWidgets, QtGui
from sampleview import SampleView
from scalebar import ScaleBar
from ramancom.ramancontrol import defaultPath
from ramancom.ramanSwitch import RamanSwitch
from colorlegend import ColorLegend
import os
from PyQt5 import QtCore, QtWidgets, QtGui
from .sampleview import SampleView
from .scalebar import ScaleBar
from .ramancom.ramancontrol import defaultPath
from .ramancom.ramanSwitch import RamanSwitch
from .analysis.colorlegend import ColorLegend
class MeasureParticleWindow(QtWidgets.QMainWindow):
class GEPARDMainWindow(QtWidgets.QMainWindow):
def __init__(self, logpath):
super(MeasureParticleWindow, self).__init__()
super(GEPARDMainWindow, self).__init__()
self.setWindowTitle("GEPARD")
self.resize(900, 700)
......@@ -101,13 +100,24 @@ class MeasureParticleWindow(QtWidgets.QMainWindow):
fileName = QtWidgets.QFileDialog.getSaveFileName(self, "Create New Project",
defaultPath, "*.pkl")[0]
if fileName:
if fileName.find(' ') < 0:
isValid, msg = self.testFilename(fileName)
if isValid:
self.fname = str(fileName)
self.view.new(self.fname)
self.scalingChanged(1.)
else:
QtWidgets.QMessageBox.critical(self, "Error", "File path must not contain spaces.")
QtWidgets.QMessageBox.critical(self, "Error", msg)
@QtCore.pyqtSlot()
def testFilename(self, fileName):
if self.view.ramanctrl.name == 'RenishawCOM': #the renishawCom does not allow Spaces within filePath
if fileName.find(' ') == 0:
return False, "File path must not contain spaces."
else:
return True, ""
else:
return True, ""
@QtCore.pyqtSlot()
def about(self):
QtWidgets.QMessageBox.about(self, 'GEPARD',
......@@ -116,7 +126,7 @@ class MeasureParticleWindow(QtWidgets.QMainWindow):
def createActions(self):
fname = os.path.join(os.path.split(__file__)[0],
os.path.join("data","brand.png"))
os.path.join('data', 'brand.png'))
self.aboutAct = QtWidgets.QAction(QtGui.QIcon(fname),
"About Particle Measurment", self)
self.aboutAct.triggered.connect(self.about)
......@@ -125,7 +135,7 @@ class MeasureParticleWindow(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)
......@@ -188,12 +198,23 @@ class MeasureParticleWindow(QtWidgets.QMainWindow):
self.snapshotAct = QtWidgets.QAction("Save Screenshot", self)
self.snapshotAct.triggered.connect(self.view.takeScreenshot)
self.snapshotAct.setDisabled(True)
self.configRamanCtrlAct = QtWidgets.QAction("Configure Raman Control", self)
self.configRamanCtrlAct.triggered.connect(self.view.configureRamanControl)
if self.view.simulatedRaman:
self.configRamanCtrlAct.setDisabled(True)
self.noOverlayAct = QtWidgets.QAction("&No Overlay", self)
self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self)
self.fullOverlayAct = QtWidgets.QAction("&Full Overlay", self)
self.transpAct = QtWidgets.QAction("&Transparent Overlay", self)
self.hideLabelAct = QtWidgets.QAction('&Hide Spectra Numbers', self)
self.darkenAct = QtWidgets.QAction("&Darken Image", self)
self.seedAct = QtWidgets.QAction("&Set Color Seed", self)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct, self.seedAct]:
act.setDisabled(True)
def updateModes(self, active=None, maxenabled=None):
ose, osc, pde, pdc, rse, rsc, pae, pac = [False]*8
if maxenabled=="OpticalScan":
......@@ -273,6 +294,17 @@ class MeasureParticleWindow(QtWidgets.QMainWindow):
self.toolsMenu = QtWidgets.QMenu("&Tools")
self.toolsMenu.addAction(self.snapshotAct)
self.toolsMenu.addAction(self.configRamanCtrlAct)
self.dispMenu = QtWidgets.QMenu("&Display", self)
self.overlayActGroup = QtWidgets.QActionGroup(self.dispMenu)
self.overlayActGroup.setExclusive(True)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct]:
self.dispMenu.addAction(act)
self.overlayActGroup.addAction(act)
self.dispMenu.addSeparator()
self.dispMenu.addActions([self.transpAct, self.hideLabelAct, self.darkenAct, self.seedAct])
self.helpMenu = QtWidgets.QMenu("&Help", self)
self.helpMenu.addAction(self.aboutAct)
......@@ -281,9 +313,9 @@ class MeasureParticleWindow(QtWidgets.QMainWindow):
self.menuBar().addMenu(self.viewMenu)
self.menuBar().addMenu(self.modeMenu)
self.menuBar().addMenu(self.toolsMenu)
self.menuBar().addMenu(self.dispMenu)
self.menuBar().addMenu(self.helpMenu)
def createToolBar(self):
self.toolbar = QtWidgets.QToolBar("Tools")
self.toolbar.setIconSize(QtCore.QSize(100,50))
......@@ -322,12 +354,10 @@ if __name__ == '__main__':
fp = open(logname, "a")
sys.stderr = fp
sys.stdout = fp
print("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime()), flush=True)
print("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime()),
flush=True)
meas = MeasureParticleWindow(logpath)
meas.showMaximized()
gepard = GEPARDMainWindow(logpath)
gepard.showMaximized()
ret = app.exec_()
if fp is not None:
fp.close()
\ No newline at end of file
fp.close()
# -*- 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/>.
"""
import pickle
def loadAssigments(fname):
with open(fname, "rb") as fp:
assignment = pickle.load(fp)
return assignment
def saveAssignments(assignment, fname):
with open(fname, "wb") as fp:
pickle.dump(assignment, fp, protocol=-1)
class DBAssignment(object):
def __init__(self):
self.filename = None
self.assignments = []
def setFileName(self, fname):
self.filename = fname
def save(self):
saveAssignments(self, self.filename)
def hasAssignment(self, polymerName):
polymIsPresent = False
for assignment in self.assignments:
if assignment.polymerName == polymerName:
polymIsPresent = True
break
return polymIsPresent
def getAssignment(self, polymerName):
for assignment in self.assignments:
if assignment.polymerName == polymerName:
return assignment
def createNewAssignment(self, polymerName):
self.assignments.append(Assignment(polymerName))
def updateAssignment(self, polymerName, result, catRes, indic_paint):
for assignment in self.assignments:
if assignment.polymerName == polymerName:
assignment.update(result, catRes, indic_paint)
return
class Assignment(object):
def __init__(self, polymerName):
self.polymerName = polymerName
self.result = None
self.categorizedResult = None
self.indication_paint = None
def update(self, result, catRes, indic_paint):
self.result = result
self.categorizedResult = catRes
self.indication_paint = indic_paint
#!/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 numpy as np
from .colorlegend import getColorFromNameWithSeed
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
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.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 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())
self.spec_axis = self.canvas.figure.subplots()
self.reference_axis = 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):
self.spectra = self.dataset.particleContainer.getSpectraFromDisk()
self.canvas.draw()
def updateParticleSpectrum(self, specIndex, assignment, hqi):
#draw Sample Spectrum
self.spec_axis.axis("on")
self.spec_axis.clear()
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 = 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)
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 = self.fontsize)
self.reference_axis.plot(ref_wavenumber, ref_intensity, 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
This diff is collapsed.
This diff is collapsed.
......@@ -20,10 +20,25 @@ If not, see <https://www.gnu.org/licenses/>.
"""
from PyQt5 import QtCore, QtWidgets, QtGui
import numpy as np
import random
import colorsys
def getColorFromNameWithSeed(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
WX, WY = 1024, 200
class ColorLegend(QtWidgets.QMdiSubWindow):
def __init__(self, parent=None):
super().__init__(parent)
......@@ -40,7 +55,6 @@ class ColorLegend(QtWidgets.QMdiSubWindow):
def setTextColorItems(self, items):
for text, color in items:
print(text, color)
assert type(text)==str or type(text)==np.str_, "items must be tuples of text and QColor"
assert type(color)==QtGui.QColor or type(color)==QtCore.Qt.GlobalColor, "items must be tuples of text and QColor"
self.items = items
......@@ -109,6 +123,4 @@ class ColorLegend(QtWidgets.QMdiSubWindow):
y0 = index*(fontSize+spacer) + (fontSize - tileSize)/2 +spacer/2
qp.drawRect(x0, y0, tileSize, tileSize)
qp.end()
\ No newline at end of file
qp.end()
\ No newline at end of file
......@@ -27,24 +27,26 @@ with permission from github user CJ Carey (perimosocordiae)
from PyQt5 import QtWidgets, QtCore
import numpy as np
import sys
import dill
import dill #TODO: Make it run with pickle... Having two different methods is not so sensefull...
import os
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.figure import Figure
import functools
from scipy.linalg import solveh_banded
from pathlib import Path
class DataBaseWindow(QtWidgets.QMainWindow):
def __init__(self, parent):
super(DataBaseWindow, self).__init__()
self.parent = parent
self.path = os.path.join(Path.home(), 'gepard', 'databases')
logpath = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppLocalDataLocation)