Commit 9120c7ba authored by Josef Brandt's avatar Josef Brandt

Particle Contours and complete Gepard Test

parent 0c465724
......@@ -23,12 +23,13 @@ import logging.handlers
import traceback
import os
from io import StringIO
from typing import List
from PyQt5 import QtCore, QtWidgets, QtGui
from .sampleview import SampleView
from .gui.scalebar import ScaleBar
from .ramancom.ramancontrol import defaultPath
from .ramancom.lightModeSwitch import LightModeSwitch
from .analysis.colorlegend import ColorLegend
from .gui.colorlegend import ColorLegend
from .gepardlogging import setDefaultLoggingConfig
from .workmodes import ModeHandler
from .unittests.test_gepard import testGepard
......@@ -41,13 +42,11 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.setWindowTitle("GEPARD")
self.fname: str = ''
self.resize(900, 700)
self.view = SampleView(logger)
self.view.imparent = self
self.view.ScalingChanged.connect(self.scalingChanged)
self.scalebar = ScaleBar(self)
self.legend = ColorLegend(self)
self.lightModeSwitch = LightModeSwitch(self)
self.view = SampleView(self, logger)
self.view.ScalingChanged.connect(self.scalingChanged)
self.view.ScalingChanged.connect(self.scalebar.updateScale)
mdiarea = QtWidgets.QMdiArea(self)
......@@ -109,7 +108,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
@QtCore.pyqtSlot()
def new(self, fileName=False):
if fileName is False:
fileName = QtWidgets.QFileDialog.getSaveFileName(self, "Create New Project",defaultPath, "*.pkl")[0]
fileName = QtWidgets.QFileDialog.getSaveFileName(self, "Create New Project", defaultPath, "*.pkl")[0]
if fileName:
isValid, msg = self.testFilename(fileName)
if isValid:
......@@ -134,6 +133,75 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
QtWidgets.QMessageBox.about(self, 'GEPARD', "Developed by Complex Fiber Structures GmbH "
"on behalf of Leibniz-IPF Dresden")
def getParticleRelevantActs(self) -> List[QtWidgets.QAction]:
return [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct, self.seedAct]
def disableParticleRelevantActs(self) -> None:
for act in self.getParticleRelevantActs():
act.setDisabled(True)
def enableParticleRelevantActs(self) -> None:
for act in self.getParticleRelevantActs():
act.setEnabled(True)
def updateModes(self, active=None, maxenabled=None):
ose, osc, pde, pdc, rse, rsc = [False]*6
if maxenabled == "OpticalScan":
ose = True
elif maxenabled == "ParticleDetection":
ose, pde = True, True
elif maxenabled == "SpectrumScan":
ose, pde, rse = True, True, True
if active == "OpticalScan" and ose:
osc = True
elif active == "ParticleDetection" and pde:
pdc = True
elif active == "SpectrumScan" and rse:
rsc = True
self.opticalScanAct.setEnabled(ose)
self.opticalScanAct.setChecked(osc)
self.detectParticleAct.setEnabled(pde)
self.detectParticleAct.setChecked(pdc)
self.ramanScanAct.setEnabled(rse)
self.ramanScanAct.setChecked(rsc)
def activateMaxMode(self, loadnew=False) -> None:
self.modeHandler.activateMaxMode(loadnew)
def getCurrentMode(self) -> str:
mode: str = 'None'
if self.modeHandler.activeMode is not None:
mode = self.modeHandler.activeMode.name
return mode
def unblockUI(self, connected):
self.openAct.setEnabled(True)
self.importAct.setEnabled(True)
self.newAct.setEnabled(True)
self.updateConnected(connected)
self.exitAct.setEnabled(True)
def blockUI(self):
self.openAct.setEnabled(False)
self.importAct.setEnabled(False)
self.newAct.setEnabled(False)
self.connectRamanAct.setEnabled(False)
self.disconnectRamanAct.setEnabled(False)
self.exitAct.setEnabled(False)
self.opticalScanAct.setEnabled(False)
self.detectParticleAct.setEnabled(False)
self.ramanScanAct.setEnabled(False)
def updateConnected(self, connected):
if connected:
self.connectRamanAct.setEnabled(False)
self.disconnectRamanAct.setEnabled(True)
else:
self.connectRamanAct.setEnabled(True)
self.disconnectRamanAct.setEnabled(False)
def createActions(self):
def runGepardTest(gebbard):
return lambda: testGepard(gebbard)
......@@ -189,17 +257,18 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.opticalScanAct = QtWidgets.QAction("Optical Scan", self)
self.opticalScanAct.setEnabled(False)
self.opticalScanAct.setCheckable(True)
self.opticalScanAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.modeHandler.switchMode("OpticalScan")))
self.opticalScanAct.triggered.connect(QtCore.pyqtSlot()(lambda: self.modeHandler.switchMode("OpticalScan")))
self.detectParticleAct = QtWidgets.QAction("Detect Particles", self)
self.detectParticleAct.setEnabled(False)
self.detectParticleAct.setCheckable(True)
self.detectParticleAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.modeHandler.switchMode("ParticleDetection")))
self.detectParticleAct.triggered.connect(
QtCore.pyqtSlot()(lambda: self.modeHandler.switchMode("ParticleDetection")))
self.ramanScanAct = QtWidgets.QAction("Raman Scan", self)
self.ramanScanAct.setEnabled(False)
self.ramanScanAct.setCheckable(True)
self.ramanScanAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.modeHandler.switchMode("SpectrumScan")))
self.ramanScanAct.triggered.connect(QtCore.pyqtSlot()(lambda: self.modeHandler.switchMode("SpectrumScan")))
self.snapshotAct = QtWidgets.QAction("&Save Screenshot", self)
self.snapshotAct.triggered.connect(self.view.takeScreenshot)
......@@ -211,82 +280,31 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.configRamanCtrlAct.setDisabled(True)
self.testAct: QtWidgets.QAction = QtWidgets.QAction("&Run Automated Gepard Test")
self.testAct.setShortcut("Ctrl+T")
self.testAct.triggered.connect(runGepardTest(self))
self.noOverlayAct = QtWidgets.QAction("&No Overlay", self)
self.noOverlayAct.setShortcut("1")
self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self)
self.noOverlayAct.setCheckable(True)
self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self) # TODO: Is that needed??
self.selOverlayAct.setShortcut("2")
self.selOverlayAct.setCheckable(True)
self.fullOverlayAct = QtWidgets.QAction("&Full Overlay", self)
self.fullOverlayAct.setShortcut("3")
self.fullOverlayAct.setCheckable(True)
self.transpAct = QtWidgets.QAction("&Transparent Overlay", self)
self.transpAct.setShortcut("T")
self.transpAct.setCheckable(True)
self.transpAct.setChecked(False)
self.transpAct.triggered.connect(self.view.viewItemHandler.adjustParticleViewItemsVisibility)
self.hideLabelAct = QtWidgets.QAction('&Hide Spectra Numbers', self)
self.hideLabelAct.setShortcut("H")
self.darkenAct = QtWidgets.QAction("&Darken Image", self)
self.darkenAct.setShortcut("D")
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 = [False]*6
if maxenabled == "OpticalScan":
ose = True
elif maxenabled == "ParticleDetection":
ose, pde = True, True
elif maxenabled == "SpectrumScan":
ose, pde, rse = True, True, True
if active == "OpticalScan" and ose:
osc = True
elif active == "ParticleDetection" and pde:
pdc = True
elif active == "SpectrumScan" and rse:
rsc = True
self.opticalScanAct.setEnabled(ose)
self.opticalScanAct.setChecked(osc)
self.detectParticleAct.setEnabled(pde)
self.detectParticleAct.setChecked(pdc)
self.ramanScanAct.setEnabled(rse)
self.ramanScanAct.setChecked(rsc)
def activateMaxMode(self, loadnew=False) -> None:
self.modeHandler.activateMaxMode(loadnew)
def getCurrentMode(self) -> str:
mode: str = 'None'
if self.modeHandler.activeMode is not None:
mode = self.modeHandler.activeMode.name
return mode
def unblockUI(self, connected):
self.openAct.setEnabled(True)
self.importAct.setEnabled(True)
self.newAct.setEnabled(True)
self.updateConnected(connected)
self.exitAct.setEnabled(True)
def blockUI(self):
self.openAct.setEnabled(False)
self.importAct.setEnabled(False)
self.newAct.setEnabled(False)
self.connectRamanAct.setEnabled(False)
self.disconnectRamanAct.setEnabled(False)
self.exitAct.setEnabled(False)
self.opticalScanAct.setEnabled(False)
self.detectParticleAct.setEnabled(False)
self.ramanScanAct.setEnabled(False)
def updateConnected(self, connected):
if connected:
self.connectRamanAct.setEnabled(False)
self.disconnectRamanAct.setEnabled(True)
else:
self.connectRamanAct.setEnabled(True)
self.disconnectRamanAct.setEnabled(False)
self.disableParticleRelevantActs()
def createMenus(self):
self.fileMenu = QtWidgets.QMenu("&File", self)
......@@ -315,11 +333,12 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.dispMenu = QtWidgets.QMenu("&Display", self)
self.overlayActGroup = QtWidgets.QActionGroup(self.dispMenu)
self.overlayActGroup.setExclusive(True)
self.overlayActGroup.triggered.connect(self.view.viewItemHandler.adjustParticleViewItemsVisibility)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct]:
self.dispMenu.addAction(act)
self.overlayActGroup.addAction(act)
self.fullOverlayAct.setChecked(True)
self.dispMenu.addSeparator()
self.dispMenu.addActions([self.transpAct, self.hideLabelAct, self.darkenAct, self.seedAct])
......@@ -365,7 +384,7 @@ if __name__ == '__main__':
as it should be opened also in disconnected stage (e.g., when another instance is running in optical or raman
scan, but the UI (disconnected) should still update what's going on.
"""
app.deleteLater()
app.closeAllWindows()
def excepthook(excType, excValue, tracebackobj):
"""
......
# -*- 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
"""
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 os
import numpy as np
class AdvancedWITecSpectra(object):
"""
Handles Spectra formatting and storage when using the advanced "silent spectrum" option in the WITec COM interface
:return:
"""
def __init__(self):
super(AdvancedWITecSpectra, self).__init__()
self.dsetpath = None
self.tmpspecpath = None
self.curSpecIndex = None
self.excitWavel = None
self.spectraBatchSize = None
def setDatasetPath(self, path):
self.dsetpath = path
def setSpectraBatchSize(self, batchSize):
self.spectraBatchSize = batchSize
def createTmpSpecFolder(self):
assert self.dsetpath is not None
self.tmpspecpath = os.path.join(self.dsetpath, 'spectra')
if not os.path.exists(self.tmpspecpath):
os.mkdir(self.tmpspecpath)
def registerNewSpectrum(self, specString, specIndex):
wavenumbers, averaged_counts = self.deassembleSpecString(specString)
if specIndex == 0:
fname = os.path.join(self.tmpspecpath, 'Wavenumbers.npy')
np.save(fname, wavenumbers)
fname = os.path.join(self.tmpspecpath, f'Spectrum ({specIndex}).npy')
np.save(fname, averaged_counts)
self.curSpecIndex = specIndex
def createSummarizedSpecFiles(self):
allSpectra = self.getAllSpectra()
allspecfname = os.path.join(self.dsetpath, 'spectra.npy')
np.save(allspecfname, allSpectra)
self.createTrueMatchTxt(allSpectra, self.excitWavel)
def deassembleSpecString(self, specString):
keywordLines = self.getKeyWordLines(specString)
try:
specSize = self.getSpecSize(specString, keywordLines['SpectrumSize'][0])
except:
print(keywordLines)
raise
wavenumbers = self.getWavenumbers(specString, keywordLines['[XData]'][0], specSize)
xDataKind = self.getXDataKind(specString, keywordLines['XDataKind'][0])
self.excitWavel = self.getExcitationWavelength(specString, keywordLines['ExcitationWavelength'][0])
if xDataKind == 'nm':
wavenumbers = self.convertWavenumbersFrom_nm_to_Percm(wavenumbers, self.excitWavel)
else:
print('warning, unexpected xDataKind:', xDataKind)
print('please check how to deal with it!!!')
assert False
averaged_counts = self.getAveragedSpectra(specString, keywordLines['SpectrumData'], specSize)
return wavenumbers, averaged_counts
def getKeyWordLines(self, specString):
keywordLines = {'[WITEC_TRUEMATCH_ASCII_HEADER]': [],
'[XData]': [],
'ExcitationWavelength': [],
'SpectrumSize': [],
'XDataKind': [],
'SpectrumHeader': [],
'SampleMetaData': [],
'SpectrumData': []}
for index, line in enumerate(specString):
for key in keywordLines.keys():
if line.find(key) != -1:
keywordLines[key].append(index)
return keywordLines
def getSpecSize(self, specString, specSizeIndex):
line = specString[specSizeIndex]
specSize = [int(s) for s in line.split() if self.isNumber(s)]
assert len(specSize) == 1
return specSize[0]
def getExcitationWavelength(self, specString, excitWavenumIndex):
line = specString[excitWavenumIndex]
excitWavel = [float(s) for s in line.split() if self.isNumber(s)]
assert len(excitWavel) == 1
return excitWavel[0]
def getXDataKind(self, specString, xDataKindIndex):
line = specString[xDataKindIndex]
return line.split()[-1]
def getWavenumbers(self, specString, startXDataIndex, specSize):
wavenumbers = []
curIndex = startXDataIndex+1
curLine = specString[curIndex]
while self.isNumber(curLine):
wavenumbers.append(float(curLine))
curIndex += 1
curLine = specString[curIndex]
assert len(wavenumbers) == specSize
return wavenumbers
def convertWavenumbersFrom_nm_to_Percm(self, wavenumbers, excit_nm):
newWavenumbers = []
for abs_nm in wavenumbers:
raman_shift = 1E7/excit_nm - 1E7/abs_nm
newWavenumbers.append(raman_shift)
return newWavenumbers
def getAveragedSpectra(self, specString, startIndices, specSize):
startIndices = [i+1 for i in startIndices] #the spectrum starts one line AFTER the SpectrumData-Tag
spectrum = []
for index in range(specSize):
curSlice = [float(specString[index + startIndex]) for startIndex in startIndices]
spectrum.append(np.mean(curSlice))
return spectrum
def getAllSpectra(self):
numSpectra = self.curSpecIndex + 1
wavenumbers = np.load(os.path.join(self.tmpspecpath, 'Wavenumbers.npy'))
allSpectra = np.zeros((wavenumbers.shape[0], numSpectra+1))
allSpectra[:, 0] = wavenumbers
for i in range(numSpectra):
curSpecPath = os.path.join(self.tmpspecpath, f'Spectrum ({i}).npy')
allSpectra[:, i+1] = np.load(curSpecPath )
os.remove(curSpecPath)
return allSpectra
def createTrueMatchTxt(self, allSpectra, wavelength):
def writeHeader(fp):
fp.write('[WITEC_TRUEMATCH_ASCII_HEADER]\n\r')
fp.write('Version = 2.0\n\r\n\r')
def writeWavenumbers(fp, wavenumbers):
fp.write('[XData]\n\r')
for line in wavenumbers:
fp.write(str(line) + '\n\r')
def writeSpectrum(fp, intensities):
fp.write('\n\r')
fp.write('[SpectrumHeader]\n\r')
fp.write(f'Title = Spectrum {specIndex} \n\r')
fp.write(f'ExcitationWavelength = {wavelength}\n\r')
fp.write(f'SpectrumSize = {specSize}\n\r')
fp.write('XDataKind = 1/cm\n\r\n\r')
fp.write('[SampleMetaData]\n\r')
fp.write(f'int Spectrum_Number = {specIndex}\n\r\n\r')
fp.write('[SpectrumData]\n\r')
for line in intensities:
fp.write(str(line) + '\n\r')
wavenumbers = allSpectra[:, 0]
spectra = allSpectra[:, 1:]
specSize = allSpectra.shape[0]
del allSpectra
numSpectra = spectra.shape[1]
numBatches = int(np.ceil(numSpectra/self.spectraBatchSize))
for batchIndex in range(numBatches):
outName = os.path.join(self.dsetpath, f'SpectraForTrueMatch {batchIndex}.txt')
if os.path.exists(outName):
os.remove(outName)
if batchIndex < numBatches-1:
specIndicesInBatch = np.arange(batchIndex*self.spectraBatchSize, (batchIndex+1)*self.spectraBatchSize)
else:
specIndicesInBatch = np.arange(batchIndex*self.spectraBatchSize, numSpectra)
with open(outName, 'w') as fp:
writeHeader(fp)
writeWavenumbers(fp, wavenumbers)
for specIndex in specIndicesInBatch:
spec = spectra[:, specIndex]
writeSpectrum(fp, spec)
def isNumber(self, string):
isNumber = False
try:
float(string)
isNumber = True
except ValueError:
pass
return isNumber
#!/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: