Commit 5e79c241 authored by Josef Brandt's avatar Josef Brandt

Automated Test until Spectrum Scan

parent 31eb4bb9
......@@ -79,7 +79,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
closeAll()
@QtCore.pyqtSlot(float)
def scalingChanged(self, scale):
def scalingChanged(self):
self.zoomInAct.setEnabled(self.view.scaleFactor < 20.0)
self.zoomOutAct.setEnabled(self.view.scaleFactor > .01)
self.normalSizeAct.setEnabled(self.view.scaleFactor != 1.)
......@@ -91,7 +91,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if fileName:
self.fname = str(fileName)
self.view.open(self.fname)
self.scalingChanged(1.)
self.scalingChanged()
@QtCore.pyqtSlot()
def importProject(self, fileName=False):
......@@ -101,7 +101,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if fileName:
self.fname = str(fileName)
self.view.importProject(self.fname)
self.scalingChanged(1.)
self.scalingChanged()
@QtCore.pyqtSlot()
def new(self, fileName=False):
......@@ -113,7 +113,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if isValid:
self.fname = str(fileName)
self.view.new(self.fname)
self.scalingChanged(1.)
self.scalingChanged()
else:
QtWidgets.QMessageBox.critical(self, "Error", msg)
......
This diff is collapsed.
......@@ -299,7 +299,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.seg = Segmentation(self.dataset, self)
self.seg.detectionState.connect(self.updateDetectionState)
self.thread = None
self.threadrunning = False
self.detectThreadRunning = False
logPath = os.path.join(self.dataset.path, 'detectionlog.txt')
self.logger = logging.getLogger('detection')
......@@ -398,7 +398,6 @@ class ParticleDetectionView(QtWidgets.QWidget):
else:
grid.addWidget(paramui, i, 0, 1, 2, QtCore.Qt.AlignLeft)
if label is not None:
grid.addWidget(label, i, 0, QtCore.Qt.AlignLeft)
......@@ -448,7 +447,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
vbox.addWidget(self.hideSeedsInSampleViewBtn)
self.slider = QtWidgets.QSlider(self)
self.slider.setRange(0,100)
self.slider.setRange(0, 100)
self.slider.setValue(80)
self.slider.setOrientation(QtCore.Qt.Horizontal)
self.slider.sliderMoved.connect(self.imglabel.resetAlpha)
......@@ -654,7 +653,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.updateImageSeeds()
def detectShow(self, showname):
if not self.threadrunning:
if not self.detectThreadRunning:
self.saveDetectParams(self.dataset)
img = self.subimg.copy()
kwargs = {}
......@@ -699,7 +698,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.seg.cancelcomputation = True
self.thread.join()
self.seg.cancelcomputation = False
self.threadrunning = False
self.detectThreadRunning = False
def blockUI(self):
self.pdetectsub.setEnabled(False)
......@@ -731,7 +730,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
return
self.blockUI()
self.pdetectall.setText("Cancel")
self.threadrunning = True
self.detectThreadRunning = True
self.img = self.pyramid.getFullImage()
self.thread = Thread(target=self._worker)
self.thread.start()
......@@ -743,7 +742,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
@QtCore.pyqtSlot()
def checkOnComputation(self):
if self.thread is not None:
if not self.threadrunning:
if not self.detectThreadRunning:
self.thread = None
self.progressbar.disable()
self.unBlockUI()
......@@ -794,7 +793,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
seedradius = self.seedradiusedit.value()
self.seg.setParameters(**kwargs)
try:
measurementPoints, contours= self.seg.apply2Image(self.img, seedpoints, deletepoints, seedradius, self.dataset, detectLogger)
measurementPoints, contours = self.seg.apply2Image(self.img, seedpoints, deletepoints, seedradius, self.dataset, detectLogger)
except:
showErrorMessageAsWidget('Fatal error in particle detection, see detectionlog for info')
detectLogger.exception('Fatal error in particle detection')
......@@ -804,7 +803,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
if self.dataset is not None:
self.applyResultsToDataset(measurementPoints, contours)
self.threadrunning = False
self.detectThreadRunning = False
def applyResultsToDataset(self, measurementPoints, contours):
self.dataset.ramanscandone = False
......
......@@ -62,7 +62,7 @@ def scan(path, sol, zpositions, grid, controlclass, dataqueue,
for i, p in enumerate(grid):
x, y = p
z = sol[0]*x + sol[1]*y + sol[2]
for k, zk in (zlist if i%2==0 else zlist[::-1]):
for k, zk in (zlist if i % 2 == 0 else zlist[::-1]):
name = f"image_{i}_{k}.bmp"
logger.info(f'taking image {name}, time: ' + strftime("%d %b %Y %H:%M:%S", localtime()))
zik = z+zk
......@@ -72,7 +72,7 @@ def scan(path, sol, zpositions, grid, controlclass, dataqueue,
if ishdr:
img_list = []
fname = os.path.join(path, f"tmp.bmp")
values = [5.,25.,100.]
values = [5., 25., 100.]
for j, val in enumerate(values if (i%2+k%2)%2==0 else reversed(values)):
ramanctrl.setBrightness(val)
logger.info(f'writing hdr image to {fname}')
......@@ -309,6 +309,7 @@ class OpticalScanUI(QtWidgets.QWidget):
# def __init__(self, ramanctrl, dataset, logger, parent=None):
def __init__(self, sampleview: 'SampleView'):
super().__init__(sampleview, QtCore.Qt.Window)
self.timer = QtCore.QTimer(self)
self.logger = sampleview.logger
self.view: QtWidgets.QGraphicsView = sampleview
mainLayout = QtWidgets.QVBoxLayout()
......@@ -715,10 +716,9 @@ class OpticalScanUI(QtWidgets.QWidget):
self.dataset.maxdim = p0 + p1
self.dataset.mode = "opticalscan"
self.dataset.save()
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.checkOnScan)
self.timer.setSingleShot(True)
self.timer.start(1000.)
self.timer.start(1000)
@QtCore.pyqtSlot()
def checkOnScan(self):
......
......@@ -22,6 +22,8 @@ If not, see <https://www.gnu.org/licenses/>.
import numpy as np
import cv2
import os
import sys
from PyQt5 import QtWidgets, QtCore
try:
from skimage.io import imread as skimread
......@@ -123,3 +125,7 @@ def hasFTIRControl(sampleview) -> bool:
"""
ramanName: str = sampleview.ramanctrl.name.lower()
return ramanName.find('ftir') != -1
def getAppFolder() -> str:
return QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppLocalDataLocation)
This diff is collapsed.
......@@ -21,14 +21,13 @@ If not, see <https://www.gnu.org/licenses/>.
Image Generator for fake images for testing with simulated instrument interfaces
"""
import os
import sys
from PyQt5 import QtWidgets, QtCore
import cv2
import numpy as np
import noise
from copy import deepcopy
import pickle
from typing import Tuple, List
from ...helperfunctions import getAppFolder
def particleIsVisible(particle: np.ndarray, full: np.ndarray, pos: Tuple[int, int]) -> bool:
......@@ -133,8 +132,6 @@ class FakeCamera:
def close(self) -> None:
self.fakeFilter.saveToFile()
if self.fakeFilter.app is not None:
self.fakeFilter.app.deleteLater()
def updateImageAtPosition(self, position: List[float]) -> None:
"""
......@@ -205,7 +202,6 @@ class FakeFilter:
self.presetParticles: List[dict] = []
self.particles: List[FakeParticle] = []
self.filePath: str = ''
self.app: QtWidgets.QApplication = None
self._updateFromFile()
def _updateFromFile(self) -> None:
......@@ -238,20 +234,12 @@ class FakeFilter:
def saveToFile(self) -> None:
fpath: str = self._getFilePath()
# QApplication cannot be pickled, so we replace it temporarily with None
origApp = self.app
self.app = None
with open(fpath, "wb") as fp:
pickle.dump(self, fp, protocol=-1)
self.app = origApp
def _getFilePath(self) -> str:
if self.filePath == '':
if self.app is None:
self.app = QtWidgets.QApplication(sys.argv) # has to be an instance attribute :/
self.app.setApplicationName("GEPARD")
path: str = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppLocalDataLocation)
self.filePath = os.path.join(path, 'fakeFilter.pkl')
self.filePath = os.path.join(getAppFolder(), 'fakeFilter.pkl')
return self.filePath
def getParticleImage(self, particle: FakeParticle, blurRadius: int = 0) -> np.ndarray:
......
......@@ -21,16 +21,13 @@ If not, see <https://www.gnu.org/licenses/>.
Simulates a Microscope Stage with Camera.
"""
import os
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
import cv2
import json
import numpy as np
from typing import List
try:
from .imageGenerator import FakeCamera
except ImportError:
from imageGenerator import FakeCamera
from .imageGenerator import FakeCamera
from ...helperfunctions import getAppFolder
class SimulatedStage(object):
......@@ -91,9 +88,7 @@ class SimulatedStage(object):
json.dump(self._configToDict(), fp)
def _getFilePath(self) -> str:
self.app: QtWidgets.QApplication = QtWidgets.QApplication(sys.argv) # has to be an instance attribute :/
self.app.setApplicationName("GEPARD")
path: str = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppLocalDataLocation)
path: str = getAppFolder()
return os.path.join(path, 'simulatedStageConfig.txt')
def _configToDict(self) -> dict:
......
# -*- 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 QtCore, QtWidgets
import numpy as np
from multiprocessing import Process, Queue, Event
import queue
import os
import logging
import logging.handlers
from .external import tsp
from .uielements import TimeEstimateProgressbar
from .gepardlogging import setDefaultLoggingConfig
from .analysis.particleCharacterization import FTIRAperture
from .helperfunctions import getRamanControl
def reorder(points, N=20):
"""
Finds an efficient reordering of scan points in meandering horizontal
stripes.
Parameters
----------
points : list of scan points in 2D
N : integer, optional
The number of horizontal stripes in which the points should be aranged.
The default is 20.
Returns
-------
newind : index array
The new index array to reorder the scan points for efficient travel
between points.
"""
y0, y1 = points[:,1].min(), points[:,1].max()
y = np.linspace(y0,y1+.1,N+1)
allind = np.arange(points.shape[0])
newind = []
for i, yi in enumerate(y[:-1]):
yi1 = y[i+1]
indy = allind[(points[:,1]>=yi)&(points[:,1]<yi1)]
p = points[indy,:]
indx = p[:,0].argsort()
if i%2==1:
newind.append(indy[indx])
else:
newind.append(indy[indx[::-1]])
newind = np.concatenate(newind, axis=0)
assert np.unique(newind).shape[0]==allind.shape[0]
return newind
def scan(ramanSettings, positions, controlclass, dataqueue, stopevent,
logpath=''):
if logpath != '':
logger = logging.getLogger('RamanScanLogger')
logger.addHandler(
logging.handlers.RotatingFileHandler(
logpath, maxBytes=5 * (1 << 20), backupCount=10)
)
setDefaultLoggingConfig(logger)
try:
ramanctrl = getRamanControl(controlclass, logger)
ramanctrl.connect()
if ramanctrl.name == 'ThermoFTIRCom':
ramanSettings['Apertures'] = positions
ramanctrl.initiateMeasurement(ramanSettings)
logger.info(ramanctrl.name)
for i, p in enumerate(positions):
if not ramanctrl.name == 'ThermoFTIRCom':
x, y, z = p
else:
x, y, z = p.centerX, p.centerY, p.centerZ
width, height, angle = p.width, p.height, p.angle
logger.info(f'{width}, {height}, {angle}')
ramanctrl.setAperture(width, height, angle)
logger.info(f"position: {x}, {y}, {z}")
ramanctrl.moveToAbsolutePosition(x, y, z)
logger.info("move done")
ramanctrl.triggerMeasurement(i)
logger.info("trigger done")
if stopevent.is_set():
ramanctrl.disconnect()
return
dataqueue.put(i)
ramanctrl.finishMeasurement()
ramanctrl.disconnect()
except:
logger.exception('Fatal error in ramanscan')
from .errors import showErrorMessageAsWidget
showErrorMessageAsWidget('See ramanscanlog in project directory for information')
class RamanScanUI(QtWidgets.QWidget):
imageUpdate = QtCore.pyqtSignal(str, name='imageUpdate') #str = 'df' (= darkfield) or 'bf' (=bright field)
ramanscanUpdate = QtCore.pyqtSignal()
def __init__(self, ramanctrl, dataset, logger, parent=None):
super().__init__(parent, QtCore.Qt.Window)
self.view = parent
self.logger = logger
self.ramanctrl = ramanctrl
self.dataset = dataset
self.process = None
self.processstopevent = Event()
self.dataqueue = Queue()
self.timer = QtCore.QTimer(self)
vbox = QtWidgets.QVBoxLayout()
hbox = QtWidgets.QHBoxLayout()
self.params = []
self.paramsGroup = QtWidgets.QGroupBox("Raman settings")
self.paramsLayout = QtWidgets.QFormLayout()
self.prun = QtWidgets.QPushButton("Raman scan", self)
self.prun.released.connect(self.run)
self.paramsGroup.setLayout(self.paramsLayout)
self.updateRamanParameters()
self.pexit = QtWidgets.QPushButton("Cancel", self)
self.pexit.released.connect(self.cancelScan)
self.prun.setEnabled(False)
self.progressbar = TimeEstimateProgressbar()
self.progressbar.disable()
hbox.addStretch()
hbox.addWidget(self.pexit)
vbox.addWidget(self.paramsGroup)
vbox.addLayout(hbox)
vbox.addWidget(self.progressbar)
self.setLayout(vbox)
self.setWindowTitle("Raman Scan")
self.setVisible(False)
def updateRamanParameters(self):
"""
Update the raman parameters in the layout
:return:
"""
for index in reversed(range(self.paramsLayout.count())):
widget = self.paramsLayout.itemAt(index).widget()
self.paramsLayout.removeWidget(widget)
widget.setParent(None)
self.params = []
for param in self.ramanctrl.ramanParameters:
if param.dtype == 'int':
self.params.append(QtWidgets.QSpinBox())
self.params[-1].setMinimum(param.minVal)
self.params[-1].setMaximum(param.maxVal)
self.params[-1].setValue(param.value)
if param.dtype == 'double':
self.params.append(QtWidgets.QDoubleSpinBox())
self.params[-1].setMinimum(param.minVal)
self.params[-1].setMaximum(param.maxVal)
self.params[-1].setValue(param.value)
if param.dtype == 'combobox':
self.params.append(QtWidgets.QComboBox())
self.params[-1].addItems([str(i) for i in param.valList])
if param.dtype == 'checkBox':
self.params.append(QtWidgets.QCheckBox())
self.params[-1].setChecked(param.value)
for index, param in enumerate(self.params):
param.setMinimumWidth(70)
self.paramsLayout.addRow(QtWidgets.QLabel(self.ramanctrl.ramanParameters[index].name), param)
self.paramsLayout.addRow(self.prun)
self.paramsGroup.setLayout(self.paramsLayout)
def makeGetFnameLambda(self, msg, path, fileType, btn):
return lambda: self.getFName(msg, path, fileType, btn)
def getFName(self, msg, path, filetype, btn):
fname = QtWidgets.QFileDialog.getOpenFileName(self, msg, path, filetype)[0]
btn.setText(fname.split('\\')[-1])
btn.setMinimumSize(btn.sizeHint())
def resetDataset(self, ds):
self.dataset = ds
numParticles = self.dataset.particleContainer.getNumberOfParticles()
numMeasurements = self.dataset.particleContainer.getNumberOfMeasurements()
if numParticles>0:
self.prun.setEnabled(True)
self.setWindowTitle(f'{numParticles} Particles ({numMeasurements} Measurements)')
@QtCore.pyqtSlot()
def cancelScan(self):
if self.process is not None and self.process.is_alive():
reply = QtWidgets.QMessageBox.question(self, 'Stop raman scan?',
"Do you want to terminate the running scan?",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.timer.stop()
self.ramanctrl.finishMeasurement(aborted=True)
self.progressbar.resetTimerAndCounter()
self.processstopevent.set()
self.process.terminate()
self.process.join()
self.dataqueue.close()
self.dataqueue.join_thread()
self.paramsGroup.setEnabled(True)
self.view.unblockUI()
self.close()
@QtCore.pyqtSlot()
def run(self):
if self.dataset.readin:
reply = QtWidgets.QMessageBox.critical(self, 'Dataset is newly read from disk!',
"Coordinate systems might have changed since. Do you want to continue with saved coordinates?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.dataset.readin = False
else:
return
self.view.imparent.ramanSwitch.hide()
self.view.setMicroscopeMode()
if not self.ramanctrl.name == 'ThermoFTIRCom':
points = np.asarray(self.dataset.particleContainer.getMeasurementPixelCoords())
else:
points = np.asarray(self.dataset.particleContainer.getApertureCenterCoords())
numPoints: int = len(points)
ramanSettings = {'filename': self.dataset.name,
'numPoints': numPoints,
'path': self.dataset.path}
for index, param in enumerate(self.params):
try:
ramanSettings[self.ramanctrl.ramanParameters[index].name] = self.ramanctrl.ramanParameters[index].value_of(param)
except:
self.logger.critical('raman Parameter not found' + str(param))
lmin = None
for i in range(20, 41):
c = reorder(points, i)
l = np.sum(np.sqrt(np.sum(np.diff(points[c, :], axis=0)**2, axis=1)))
if lmin is None or l < lmin:
lmin = l
cmin = c
if len(points) < 20000:
cmin, T = tsp.tspcomp(np.double(points), np.int32(cmin))
assert np.all(np.sort(cmin) == np.arange(len(points), dtype=np.int32))
if not self.ramanctrl.name == 'ThermoFTIRCom':
scanpoints = np.array([self.dataset.mapToLengthRaman(p, microscopeMode=self.view.microscopeMode) for p in points[cmin, :]])
zmin, zmax = scanpoints[:, 2].min(), scanpoints[:, 2].max()
else:
apertures: list = []
maxApertureSize: float = 0.0
zmin, zmax = None, None
for index in cmin:
curParticle = self.dataset.particleContainer.getParticleOfIndex(index)
newAperture = FTIRAperture()
newAperture.__dict__.update(curParticle.aperture.__dict__)
p_px: tuple = (newAperture.centerX, newAperture.centerY)
p_microns: tuple = self.dataset.mapToLengthRaman(p_px, microscopeMode=self.view.microscopeMode)
newAperture.centerX = p_microns[0]
newAperture.centerY = p_microns[1]
newAperture.centerZ = p_microns[2]
newAperture.width *= self.dataset.getPixelScale(mode=self.view.microscopeMode)
newAperture.height *= self.dataset.getPixelScale(mode=self.view.microscopeMode)
apertures.append(newAperture)
if newAperture.width > maxApertureSize or newAperture.height > maxApertureSize:
maxApertureSize = max([newAperture.width, newAperture.height])
if zmin is None:
zmin = zmax = newAperture.centerZ
else:
if newAperture.centerZ < zmin:
zmin = newAperture.centerZ
elif newAperture.centerZ > zmax:
zmax = newAperture.centerZ
softwarez = self.ramanctrl.getSoftwareZ() # get current software z
zmin -= softwarez
zmax -= softwarez
reply = QtWidgets.QMessageBox.question(self, 'Starting Spectrum Acquisition',
"Please prepare instrument for spectrum acquisition, if required.Microscope will move"\
" (%4.0f,%4.0f) µm relative to current position. Proceed?"%(zmin, zmax),
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.dataset.mode = "ramanscan"
for ramanScanIndex, measIndex in enumerate(cmin):
self.dataset.particleContainer.setMeasurementScanIndex(measIndex, ramanScanIndex)
self.view.saveDataSet()
self.view.prepareAnalysis()
self.view.zoomDisplay(2.0)
if self.ramanctrl.name == 'RenishawCOM':
QtWidgets.QMessageBox.about(self, "Info", "Control is headed over to Renishaw Instrument, Gepard is stopping here")
ramanSettings['Points'] = scanpoints
self.ramanctrl.initiateMeasurement(ramanSettings)
self.ramanctrl.disconnect()
self.dataset.ramanscandone = True
self.view.saveDataSet()
self.close()
return
else:
self.view.highLightRamanIndex(0)
self.view.blockUI()
self.paramsGroup.setEnabled(False)
self.progressbar.enable()
self.progressbar.resetTimerAndCounter()
self.progressbar.setMaxValue(numPoints)
self.ramanctrl.disconnect()
logpath = os.path.join(self.dataset.path, 'ramanscanlog.txt')
if self.ramanctrl.name == 'ThermoFTIRCom':
scanpoints = apertures
QtWidgets.QMessageBox.about(self, 'Prepare for Background collection',
f'Please move the microscope to an empty position on the sample.\n'
f'Largest aperture needs {round(maxApertureSize)} µm of space.')