Commit f3c17ebf authored by Josef Brandt's avatar Josef Brandt

Renaming into SpecScans, several bugfixes

parent 9120c7ba
__pycache__/
gepard\.cfg
analysis/database_config\.txt
*.so
*.c
external/build/
.idea/
__pycache__/
gepard\.cfg
analysis/database_config\.txt
*.so
*.c
external/build/
.idea/
*.pyd
ramancom/renishawcom.py
instrumentcom/renishawcom.py
ramancom/renishawtesting.py
instrumentcom/renishawtesting.py
*.exp
......
......@@ -21,7 +21,7 @@ Requirements:
for 64bit as many use cases require a lot of memory (16 GB better 32 GB
recommended)
* the tsp module in external can be built with
* the cython modules can be built with
python setuptsp.py
please note: for this step a valid compiler needs to be installed in the
system; Otherwise use the precompiled tsp-module
......
......@@ -27,12 +27,13 @@ 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 .instrumentcom.instrumentConfig import defaultPath
from .instrumentcom.lightModeSwitch import LightModeSwitch
from .gui.colorlegend import ColorLegend
from .gepardlogging import setDefaultLoggingConfig
from .workmodes import ModeHandler
from .unittests.test_gepard import testGepard
from .helperfunctions import getAppFolder
class GEPARDMainWindow(QtWidgets.QMainWindow):
......@@ -120,7 +121,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
@QtCore.pyqtSlot()
def testFilename(self, fileName):
if self.view.ramanctrl.name == 'RenishawCOM': # the renishawCom does not allow Spaces within filePath
if self.view.instrctrl.name == 'RenishawCOM': # the renishawCom does not allow Spaces within filePath
if fileName.find(' ') == 0:
return False, "File path must not contain spaces."
else:
......@@ -164,8 +165,8 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.opticalScanAct.setChecked(osc)
self.detectParticleAct.setEnabled(pde)
self.detectParticleAct.setChecked(pdc)
self.ramanScanAct.setEnabled(rse)
self.ramanScanAct.setChecked(rsc)
self.specScanAct.setEnabled(rse)
self.specScanAct.setChecked(rsc)
def activateMaxMode(self, loadnew=False) -> None:
self.modeHandler.activateMaxMode(loadnew)
......@@ -192,7 +193,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.exitAct.setEnabled(False)
self.opticalScanAct.setEnabled(False)
self.detectParticleAct.setEnabled(False)
self.ramanScanAct.setEnabled(False)
self.specScanAct.setEnabled(False)
def updateConnected(self, connected):
if connected:
......@@ -265,17 +266,17 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
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.specScanAct = QtWidgets.QAction("Spectrum Scan", self)
self.specScanAct.setEnabled(False)
self.specScanAct.setCheckable(True)
self.specScanAct.triggered.connect(QtCore.pyqtSlot()(lambda: self.modeHandler.switchMode("SpectrumScan")))
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)
self.configRamanCtrlAct.triggered.connect(self.view.configureInstrumentControl)
if self.view.simulatedRaman:
self.configRamanCtrlAct.setDisabled(True)
......@@ -365,7 +366,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.toolbar.addSeparator()
self.toolbar.addAction(self.opticalScanAct)
self.toolbar.addAction(self.detectParticleAct)
self.toolbar.addAction(self.ramanScanAct)
self.toolbar.addAction(self.specScanAct)
self.toolbar.addSeparator()
self.toolbar.addAction(self.exitAct)
self.toolbar.setOrientation(QtCore.Qt.Vertical)
......@@ -380,7 +381,7 @@ if __name__ == '__main__':
"""
Closes the app and, with that, all windows.
Josef: I implemented this, as with the simulated microscope stage it was difficult to find a proper way to
ONLY close it at the end of running the program. Closing it on disconnect of the ramanctrl is not suitable,
ONLY close it at the end of running the program. Closing it on disconnect of the instrctrl is not suitable,
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.
"""
......@@ -409,11 +410,7 @@ if __name__ == '__main__':
logger = logging.getLogger(__name__)
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("GEPARD") # appname needed for logpath
logpath = QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppLocalDataLocation)
fp = None
logpath = getAppFolder()
if logpath != "":
if not os.path.exists(logpath):
os.mkdir(logpath)
......
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
"""
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 QtCore, QtGui, QtWidgets
from .excelexport import ExpExcelDialog
from . import analysisplots
from . import analysiswidgets
from .loadresults import LoadTrueMatchResults
from .database import DataBaseWindow
from .colorlegend import getColorFromNameWithSeed
from .particleCharacterization import updateStatsOfParticlesIfNotManuallyEdited
try:
from .sqlexport import SQLExport
sqlEnabled = True
except:
sqlEnabled = False
class ParticleAnalysis(QtWidgets.QMainWindow):
def __init__(self, dataset, viewparent=None):
super(ParticleAnalysis, self).__init__(viewparent)
self.setWindowTitle('Results of polymer analysis')
self.layout = QtWidgets.QHBoxLayout()
self.widget = QtWidgets.QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
self.viewparent = viewparent
self.dataset = dataset
self.particleContainer = dataset.particleContainer
self.importWindow = None
self.findcoloredParticlesWindow = None
self.currentParticleIndex = 0
self.currentSpectrumIndex = 0
self.typeHistogramPlot = analysisplots.TypeHistogramPlot(self.dataset)
self.sizeHistogramPlot = analysisplots.SizeHistogramPlot(self.dataset)
self.specPlot = analysisplots.SpectraPlot(self.dataset)
splitter1 = QtWidgets.QSplitter(QtCore.Qt.Vertical)
splitter1.addWidget(self.specPlot)
splitter1.addWidget(self.sizeHistogramPlot)
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.navigationToolbar = analysiswidgets.PolymerNavigationToolbar(self.particleContainer)
self.navigationToolbar.WidgetsUpdated.connect(self.updateFromNavigationToolbar)
self.navigationToolbar.JumpToIndicatedSpec.connect(self.jumpToIndicatedSpectrum)
self.viewparent.ParticleOfIndexSelected.connect(self.navigationToolbar.setWidgetsToNewParticleIndex)
topLayout = QtWidgets.QHBoxLayout()
topLayout.addWidget(self.navigationToolbar)
topLayout.addWidget(referenceGroup)
viewLayout = QtWidgets.QVBoxLayout()
viewLayout.addLayout(topLayout)
viewLayout.addWidget(splitter2)
viewLayout.setStretch(1, 1)
self.optionsGroup = QtWidgets.QGroupBox('Set HQI Threshold')
optionsLayout = QtWidgets.QFormLayout()
self.hqiSpinBox = QtWidgets.QDoubleSpinBox()
self.hqiSpinBox.setDecimals(1)
self.hqiSpinBox.setMinimum(0)
self.hqiSpinBox.setMaximum(100)
self.hqiSpinBox.setMaximumWidth(100)
minHQI = self.dataset.resultParams['minHQI']
if minHQI is not None:
self.hqiSpinBox.setValue(minHQI)
self.hqiSpinBox.valueChanged.connect(self.applyHQIThresholdToResults)
minHQI = self.dataset.resultParams['minHQI']
if minHQI is not None:
self.hqiSpinBox.setValue(minHQI)
optionsLayout.addRow(QtWidgets.QLabel('minimum HQI:'), self.hqiSpinBox)
self.optionsGroup.setLayout(optionsLayout)
self.optionsGroup.setMinimumWidth(175)
self.resultCheckBoxes = analysiswidgets.PolymerTypeCheckboxes()
self.resultCheckBoxes.PolymerCheckBoxToggled.connect(self.updatedPlotsAndContoursSelectively)
self.menuLayout = QtWidgets.QVBoxLayout()
self.menuLayout.addWidget(self.optionsGroup)
self.menuLayout.addWidget(self.resultCheckBoxes)
self.layout.addLayout(self.menuLayout)
self.layout.addLayout(viewLayout)
self.createActions()
self.createMenus()
self.applyHQIThresholdToResults()
self.initializeSpecPlot()
self.navigationToolbar.updateWidgets()
def createActions(self):
self.loadTrueMatchAct = QtWidgets.QAction("Load &TrueMatch Results", self)
self.loadTrueMatchAct.triggered.connect(self.importTrueMatchResults)
self.loadSpectraAct = QtWidgets.QAction("Load &Spectra", self)
self.loadSpectraAct.triggered.connect(self.initializeSpecPlot)
self.databaseAct = QtWidgets.QAction("&Manage reference spectra databases", self)
self.databaseAct.triggered.connect(self.launchDBManager)
self.findColorParticlesAct = QtWidgets.QAction("Navigate throuch &colored, not unknown particles", self)
self.findColorParticlesAct.triggered.connect(self.launchColoredParticleWindow)
self.recalculateParticleStatsAct = QtWidgets.QAction("&Recalculated particle stats", self)
self.recalculateParticleStatsAct.triggered.connect(self.recalculateParticleStats)
self.expExcelAct= QtWidgets.QAction("Export &Excel List", self)
self.expExcelAct.setDisabled(True)
self.expExcelAct.triggered.connect(self.exportToExcel)
self.expSQLAct = QtWidgets.QAction("Export to &SQL Database", self)
self.expSQLAct.setDisabled(True)
self.expSQLAct.triggered.connect(self.exportToSQL)
self.getAndActivateActionsFromGepardMain()
def getAndActivateActionsFromGepardMain(self):
"""
For user convenience, the actions from the Gepard main window are also added to the menubar of the analysis window.
:return:
"""
gepard = self.viewparent.imparent
self.noOverlayAct = gepard.noOverlayAct
self.selOverlayAct = gepard.selOverlayAct
self.fullOverlayAct = gepard.fullOverlayAct
self.transpAct = gepard.transpAct
self.transpAct.triggered.connect(self.updateContourColors)
self.hideLabelAct = gepard.hideLabelAct
self.hideLabelAct.triggered.connect(self.showOrHideSpecNumbers)
self.darkenAct = gepard.darkenAct
self.darkenAct.triggered.connect(self.darkenBackground)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct]:
act.setCheckable(True)
self.fullOverlayAct.setChecked(True)
self.seedAct = gepard.seedAct
self.seedAct.triggered.connect(self.updateColorSeed)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct, self.seedAct]:
act.setDisabled(False)
def createMenus(self):
self.importMenu = QtWidgets.QMenu("&Import Spectra and Results")
self.importMenu.addActions([self.loadSpectraAct, self.loadTrueMatchAct])
self.dispMenu = QtWidgets.QMenu("&Display", self)
self.overlayActGroup = QtWidgets.QActionGroup(self.dispMenu)
self.overlayActGroup.setExclusive(True)
self.overlayActGroup.triggered.connect(self.updateContourColors)
self.overlayActGroup.triggered.connect(self.updatePlotsAndContours)
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.toolMenu = QtWidgets.QMenu("&Tools")
self.toolMenu.addAction(self.databaseAct)
self.toolMenu.addAction(self.findColorParticlesAct)
self.toolMenu.addAction(self.recalculateParticleStatsAct)
self.exportMenu = QtWidgets.QMenu("&Export", self)
self.exportMenu.addAction(self.expExcelAct)
self.exportMenu.addAction(self.expSQLAct)
self.menuBar().addMenu(self.importMenu)
self.menuBar().addMenu(self.dispMenu)
self.menuBar().addMenu(self.toolMenu)
self.menuBar().addMenu(self.exportMenu)
def launchDBManager(self):
"""
The Database Manager is launched for editing and creating databases with reference spectra
:return:
"""
if self.dbWin.isHidden():
self.dbWin.show()
def launchColoredParticleWindow(self):
"""
Likely just a temporary function to find colored particles, as the color classification is still not perfect.
The module finds not unknown particles that were assigned to a color and the user can confirm or change the respective colors.
:return:
"""
if self.findcoloredParticlesWindow is not None:
del self.findcoloredParticlesWindow
self.findcoloredParticlesWindow = analysiswidgets.FindColoredParticleWindow(self)
self.findcoloredParticlesWindow.ParticleOfIndexSelected.connect(self.navigationToolbar.setWidgetsToNewParticleIndex)
def recalculateParticleStats(self):
"""
recalculate all particle colors, if not already manually set
"""
self.setDisabled(True)
self.viewparent.blockUI()
reply = QtWidgets.QMessageBox.question(self, 'Recalculate stats',
"Do you want recalculate stats of all particles?\nThis might take a while..",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
updateStatsOfParticlesIfNotManuallyEdited(self.particleContainer)
self.updatedPlotsAndContoursSelectively()
QtWidgets.QMessageBox.about(self, 'Done', 'Recalculation is finished')
self.viewparent.unblockUI()
self.setDisabled(False)
def populateRefSelector(self):
"""
The Combobox for selecting references is updated to the currently selected database
:return:
"""
self.refSelector.clear()
if self.dbWin.activeDatabase is None:
self.refSelector.setDisabled(True)
else:
self.refSelector.addItem('')
self.refSelector.addItems(self.dbWin.activeDatabase.spectraNames)
self.refSelector.setDisabled(False)
def importTrueMatchResults(self):
if self.importWindow is not None:
del self.importWindow
self.importWindow = LoadTrueMatchResults(self.particleContainer, self)
@QtCore.pyqtSlot()
def applyHQIThresholdToResults(self):
"""
The in the hqiSpinBox indicated value is applied to all spectra in the dataset.
All plots are updated, as the assignment of each particle can have changed.
:return:
"""
hqi = self.hqiSpinBox.value()
self.particleContainer.applyHQITresholdToParticles(hqi)
self.dataset.resultParams['minHQI'] = hqi
self.dataset.save()
self.updateWidgetContents()
self.updatePlotsAndContours()
def updatePlotsAndContours(self):
"""
All plots are updated, the colors of the contourItems in the sampleview are updated
:return:
"""
self.updateTypeHistogram()
self.updateSizeHistogram()
self.updateContourColors()
self.updateLegend()
@QtCore.pyqtSlot()
def updatedPlotsAndContoursSelectively(self):
"""
ONLY if a selection of polymers is to be displayed, TypeHistogram, contours and legend are updated.
Otherwise only the sizeHistogram needs to be updated
:return:
"""
self.updateSizeHistogram()
if self.selOverlayAct.isChecked():
self.updateTypeHistogram()
self.updateContourColors()
self.updateLegend()
def updateWidgetContents(self):
"""
The content of all child widgets is updated to a new status in the particleContainer
:return:
"""
uniquePolymers = self.particleContainer.getUniquePolymers()
self.resultCheckBoxes.updatePolymerCheckBoxes(uniquePolymers)
self.navigationToolbar.updateWidgets()
self.expExcelAct.setDisabled(False)
if sqlEnabled:
self.expSQLAct.setDisabled(False)
def initializeSpecPlot(self):
self.specPlot.loadSpectraAndInitializeSpecPlot()
self.updateSpecPlot()
def updateTypeHistogram(self):
if self.particleContainer.getNumberOfParticles() > 0:
if self.selOverlayAct.isChecked():
histogramData = self.getSelectedTypeHistogram()
else:
histogramData = self.particleContainer.getTypeHistogram()
self.typeHistogramPlot.updateTypeHistogram(histogramData)
def updateSizeHistogram(self):
if self.particleContainer.getNumberOfParticles() > 0:
listOfSizeHistograms = []
if self.resultCheckBoxes.showAllCheckBox.isChecked():
totalHist = analysisplots.SizeHistogramData('total', self.particleContainer.getSizesOfAllParticles())
listOfSizeHistograms.append(totalHist)
for polymType in self.resultCheckBoxes.getSelectedPolymers():
polymHist = analysisplots.SizeHistogramData(polymType, self.particleContainer.getSizesOfParticleType(polymType))
listOfSizeHistograms.append(polymHist)
self.sizeHistogramPlot.drawHistograms(listOfSizeHistograms)
def updateSpecPlot(self):
particle = self.particleContainer.getParticleOfIndex(self.currentParticleIndex)
if len(particle.measurements) > 0:
hqi = self.particleContainer.getHQIOfSpectrumIndex(self.currentSpectrumIndex)
assignment = self.particleContainer.getParticleAssignmentByIndex(self.currentParticleIndex)
self.specPlot.updateParticleSpectrum(self.currentSpectrumIndex, assignment, hqi)
else:
self.specPlot.clearParticleSpectrum()
if self.refSelector.isEnabled():
if self.refSelector.currentText() != '':
refID = self.dbWin.activeDatabase.spectraNames.index(self.refSelector.currentText())
ref = self.dbWin.activeDatabase.spectra[refID]
self.specPlot.updateReferenceSpectrum(ref[:, 0], ref[:, 1])
else:
self.specPlot.clearReferenceSpectrum()
def updateContourColors(self):
"""
Updates the contour colors in the sampleview
:return:
"""
contours = self.viewparent.contourItems
alpha = (64 if self.transpAct.isChecked() else 255)
selectedPolymers = self.resultCheckBoxes.getSelectedPolymers()
for particleIndex, contour in enumerate(contours):
assignment = self.particleContainer.getParticleAssignmentByIndex(particleIndex)
hidden = self.noOverlayAct.isChecked() or (not self.fullOverlayAct.isChecked() and assignment not in selectedPolymers)
color = getColorFromNameWithSeed(assignment, self.dataset.colorSeed)
color = QtGui.QColor(color[0], color[1], color[2], alpha=alpha)
contour.setHidden(hidden)
contour.setColor(color)
contour.update()
def updateLegend(self):
"""
Updates the color legend in the sampleview
:return:
"""
if not self.noOverlayAct.isChecked():
legendItems = []
selectedPolymers = self.resultCheckBoxes.getSelectedPolymers()
for polymer in self.particleContainer.getUniquePolymers():
if self.fullOverlayAct.isChecked() or polymer in selectedPolymers:
color = getColorFromNameWithSeed(polymer, self.dataset.colorSeed)
color = QtGui.QColor(color[0], color[1], color[2], 255)
legendItems.append((polymer, color))
self.viewparent.updateLegend(legendItems)
def getSelectedTypeHistogram(self):
"""
returns the typeHistogram, filtered by the selection of the result CheckBoxes
:return:
"""
totalHist = self.particleContainer.getTypeHistogram()
histogramData = {}
selectedPolymers = self.resultCheckBoxes.getSelectedPolymers()
for polymType in totalHist:
if polymType in selectedPolymers:
histogramData[polymType] = totalHist[polymType]
return histogramData
@QtCore.pyqtSlot()
def updateFromNavigationToolbar(self):
"""
Retrieves values from the navigation toolbar and updates the SpecPlot accordingly
:return:
"""
self.currentParticleIndex = self.navigationToolbar.currentParticleIndex
self.currentSpectrumIndex = self.navigationToolbar.currentSpectrumIndex
self.updateSpecPlot()
@QtCore.pyqtSlot()
def jumpToIndicatedSpectrum(self):
"""
Make the sampleView jump to the indicated spectrum number and upates the SpecPlot accordingly
:return:
"""
self.currentSpectrumIndex = self.navigationToolbar.specNumberSelector.value()-1
self.currentParticleIndex = self.particleContainer.getParticleIndexContainingSpecIndex(self.currentSpectrumIndex)
self.viewparent.centerOnRamanIndex(self.currentSpectrumIndex)
self.viewparent.highLightContour(self.currentParticleIndex)
self.updateSpecPlot()
def darkenBackground(self):
"""
Darkens the background of the sampleview to make the overlay easier visible
:return:
"""
self.viewparent.darkenPixmap = self.darkenAct.isChecked()