Commit 2ff8dd71 authored by Josef Brandt's avatar Josef Brandt

Basic Acquisition

parent 405a0850
......@@ -26,3 +26,5 @@ ramancom/renishawtesting.py
*.obj
*.pyc
*.jdx
......@@ -373,7 +373,7 @@ if __name__ == '__main__':
logging.handlers.RotatingFileHandler(
logname, maxBytes=5*(1 << 20), backupCount=10)
)
logger.setLevel(logging.DEBUG)
setDefaultLoggingConfig(logger)
logger.info("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime()))
......
......@@ -128,7 +128,8 @@ class Measurement(object):
self.assignment_orig = 'Not Evaluated'
self.assignment_afterHQI = None
self.hqi = 0
self.measurementResults: dict = {}
self.assignedParticle = None
def setAssignment(self, assignment):
......
......@@ -34,15 +34,15 @@ class ParticleContainer(object):
self.measurements = []
self.inconsistentParticles = []
def addEmptyMeasurement(self):
def addEmptyMeasurement(self) -> int:
newMeas = Measurement()
self.measurements.append(newMeas)
return self.measurements.index(newMeas)
def clearParticles(self):
def clearParticles(self) -> None:
self.particles = []
def clearMeasurements(self):
def clearMeasurements(self) -> None:
self.measurements = []
for particle in self.particles:
particle.measurements = []
......@@ -132,6 +132,13 @@ class ParticleContainer(object):
for meas in self.measurements:
scanIndex = meas.getScanIndex()
meas.setAssignment(assignmentList[scanIndex])
def applyDatabaseResultsToParticleMeasurements(self, resultDictList: list) -> None:
"""
The resultDictList has a dict for each measurement, containing multiple database results.
Key: HQI, Val: Resultname
"""
pass
def applyHQIListToParticleMeasurements(self, hqiList):
'''HQI-List is list of spectra hqis in order of spectra indices'''
......
......@@ -169,7 +169,11 @@ class DataSet(object):
if newProject:
self.fname = self.newProject(fname)
self.updatePath()
@property
def opticalScanDone(self) -> bool:
return os.path.exists(self.getZvalImageName())
def __eq__(self, other):
return recursiveDictCompare(self.__dict__, other.__dict__)
......
......@@ -52,7 +52,7 @@ def scan(path, sol, zpositions, grid, controlclass, dataqueue,
try:
ramanctrl = controlclass(logger)
ramanctrl.setupToDatasetPath(os.path.dirname(logpath))
ramanctrl.updateImageConfig(os.path.dirname(logpath))
ramanctrl.connect()
zlist = list(enumerate(zpositions))
for i, p in enumerate(grid):
......@@ -354,7 +354,7 @@ class OpticalScan(QtWidgets.QWidget):
self.pexit = QtWidgets.QPushButton("Cancel", self)
self.pareaselect.released.connect(self.areaSelect)
self.prun.released.connect(self.run)
self.pexit.released.connect(self.stopScan)
self.pexit.released.connect(self.cancelScan)
self.prun.setEnabled(False)
self.progressbar = TimeEstimateProgressbar()
......@@ -449,7 +449,7 @@ class OpticalScan(QtWidgets.QWidget):
self.adjustSize()
@QtCore.pyqtSlot()
def stopScan(self):
def cancelScan(self):
if self.process is not None and self.process.is_alive():
reply = QtWidgets.QMessageBox.question(self, 'Stop optical scan?',
"Do you want to terminate the running scan?",
......@@ -459,15 +459,13 @@ class OpticalScan(QtWidgets.QWidget):
self.progressbar.resetTimerAndCounter()
self.timer.stop()
self.processstopevent.set()
self.process.terminate()
self.process.join()
self.dataqueue.close()
self.dataqueue.join_thread()
self.view.unblockUI()
else:
return
self.close()
self.close()
@QtCore.pyqtSlot()
def areaSelect(self):
magn = self.ramanctrl.magn
......
......@@ -254,11 +254,11 @@ class WITecCOM(RamanBase):
self.BeamPathSetVideoState = win32com.client.CastTo(self.BeamPathSetVideoStateInterface, 'IBUCSTrigger')
try:
from .advancedWITec import AdvancedWITecSpectra
from .spechandler import SpectraAcquisition
except ModuleNotFoundError:
from advancedWITec import AdvancedWITecSpectra
from spechandler import SpectraAcquisition
self.advSpec = AdvancedWITecSpectra()
self.advSpec = SpectraAcquisition(self)
if 'Autofocus' not in [param.name for param in self.ramanParameters]:
self.ramanParameters.append(RamanSettingParam('Autofocus', 'checkBox', default=False))
self.ramanParameters.append(RamanSettingParam('Spectra Batch Size', 'int', default=1000, minVal=1, maxVal=1e6))
......@@ -390,16 +390,17 @@ class WITecCOM(RamanBase):
width, height = self.ImageWidthMan.GetValue(), self.ImageHeightMan.GetValue()
angle = self.ImageRotationMan.GetValue()
return width, height, angle
def startSinglePointScan(self):
assert self.connected
self.SequencerStartTrigger.OperateTrigger()
# Wait until sequencer has finished
while True:
self.SequencerBusyStatus.Update()
Busy = self.SequencerBusyStatus.GetSingleValueAsInt()[1]
if not Busy:
break
# IT IS NOT NEEDED, ISN'T IT??
# def startSinglePointScan(self):
# assert self.connected
# self.SequencerStartTrigger.OperateTrigger()
# # Wait until sequencer has finished
# while True:
# self.SequencerBusyStatus.Update()
# Busy = self.SequencerBusyStatus.GetSingleValueAsInt()[1]
# if not Busy:
# break
####TODO: should the below methods also get the comErrorRepeater?? Repeating these commands could mess up something...
......@@ -458,7 +459,7 @@ class WITecCOM(RamanBase):
self.doAutoFocus = ramanSettings['Autofocus']
self.advSpec.createTmpSpecFolder()
# self.advSpec.createTmpSpecFolder()
state = self.BeamPathState.GetValue()
if state == 'Video':
self.MicroscopeIdle.SetValue('BeamPath|SetStateRaman')
......
"""
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 time import sleep
try:
from ..errors import showErrorMessageAsWidget
except (ValueError, ImportError):
from errors import showErrorMessageAsWidget
def comErrorRepeater(comCallFunction):
"""
A wrapper function for handling rarely occuring dde errors.
If a dee error occurs, a sleep time of one second is done, than the function is repeated.
This is done three times. If still no success was achieved, the com interface is reconnected
and, again, the function is started three times at maximum.
If none of this worked, an error message is shown and an exception is raised.
Parameters
----------
comCallFunction : function object
The function to apply the wrapper to.
Returns
-------
wrapper : function object
The wrapped function
"""
def wrapper(*args, **kwargs):
def tryFunctionThreeTimes(c):
success = False
for _ in range(3):
curResult = None
c += 1
try:
curResult = comCallFunction(*args, **kwargs)
success = True
break
except Exception as exc:
comObj.logger.warning(
f'Unsuccessful {c}. attempt for COM call of function {comCallFunction.__name__}\n{type(exc)}'
)
sleep(1.)
return success, curResult, c
c = 0
comObj = args[0] # self is always passed as first argument
functionSucceeded, result, c = tryFunctionThreeTimes(c)
if not functionSucceeded:
comObj.logger.warning(f'Reconnecting for function {comCallFunction.__name__}')
comObj.disconnect()
sleep(1.)
comObj.connect()
functionSucceeded, result, c = tryFunctionThreeTimes(c)
if not functionSucceeded:
comObj.logger.warning(f'COM error: function {comCallFunction.__name__} could not be executed')
showErrorMessageAsWidget(f'COM error on function {comCallFunction.__name__}')
raise Exception
return result
return wrapper
\ No newline at end of file
......@@ -19,12 +19,13 @@ along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>.
"""
class RamanBase(object):
def __init__(self, logger=None):
self.name = None
self.connected = False
self.timeseries = False
self.logger = None
self.logger = logger
def getRamanPositionShift(self):
""" Compute the shift between laser spot and image center"""
......@@ -55,13 +56,14 @@ class RamanBase(object):
def saveImage(self, fname):
raise NotImplementedError
def getImageDimensions(self, mode = 'df'):
def getImageDimensions(self, mode: str = 'df'):
""" Get the image width and height in um and the orientation angle in degrees.
"""
raise NotImplementedError
def startSinglePointScan(self):
raise NotImplementedError
# IT IS NOT NEEDED, ISN'T IT??
# def startSinglePointScan(self):
# raise NotImplementedError
def initiateMeasurement(self, ramanSettings):
raise NotImplementedError
......@@ -72,10 +74,10 @@ class RamanBase(object):
def finishMeasurement(self, aborted=False):
raise NotImplementedError
def setupToDatasetPath(self, dsetpath: str) -> None:
def updateImageConfig(self, dsetpath: str) -> None:
"""
Can be overloaded if the ramancontrol needs to know where the current project is located
:param dsetpath: filepath to the dataset, i.e. the
Can be overloaded if the ramancontrol needs to update image config fro dataset folder
:param dsetpath: filepath to the dataset
:return:
"""
pass
\ No newline at end of file
pass
......@@ -28,9 +28,11 @@ import numpy as np
from shutil import copyfile
from .ramanbase import RamanBase
class SimulatedRaman(RamanBase):
magn = 20
ramanParameters = {}
def __init__(self, logger):
super().__init__()
self.name = 'SimulatedRaman'
......@@ -38,11 +40,11 @@ class SimulatedRaman(RamanBase):
self.currentpos = None, 0., 0.
self.currentZ = 0.
# some plausible data to simulate consecutively changing positions
self.positionlist = np.array([[ -1201, 1376, -1290],
[ -1195, -1200, -1279],
[ 1097, -1254, -1297],
[ 2704.1, 1288.2, -1381],
[ 1884. , -1500.8, -1381]])
self.positionlist = np.array([[-1201, 1376, -1290],
[-1195, -1200, -1279],
[1097, -1254, -1297],
[2704.1, 1288.2, -1381],
[1884., -1500.8, -1381]])
self.znum = 4
self.gridnum = 36
self.positionindex = 0
......@@ -106,19 +108,19 @@ class SimulatedRaman(RamanBase):
assert self.connected
width, height, angle = 463.78607177734375, 296.0336608886719, -0.04330849274992943
return width, height, angle
def startSinglePointScan(self):
assert self.connected
self.logger.info("Fake scan")
sleep(.3)
# IT IS NOT NEEDED, ISN'T IT??
# def startSinglePointScan(self):
# assert self.connected
# self.logger.info("Fake scan")
# sleep(.3)
def initiateMeasurement(self, ramanSettings):
assert self.connected
self.logger.info(f"Scanning {ramanSettings['numPoints']} particle positions")
self.timeseries = ramanSettings['numPoints']
sleep(.1)
def triggerMeasurement(self, num):
assert self.timeseries
self.logger.info(f"Scan number: {num}")
......
......@@ -21,50 +21,207 @@ If not, see <https://www.gnu.org/licenses/>.
import os
import numpy as np
class AdvancedWITecSpectra(object):
class SpectraHandler(object):
"""
Handles Spectra formatting and storage when using the advanced "silent spectrum" option in the WITec COM interface
Handles Spectra formatting and storage.
:return:
"""
def __init__(self):
super(AdvancedWITecSpectra, self).__init__()
def __init__(self, ramanctrl):
super(SpectraHandler, self).__init__()
self.ramanctrlname = ramanctrl.name
self.dsetpath = None
self.tmpspecpath = None
self.curSpecIndex = None
self.excitWavel = None
self.spectraBatchSize = None
self.wavenumbers: np.ndarray = None
if ramanctrl.name == 'WITecCOM':
self.specConverter = WITecSpecConverter()
else:
self.specConverter = None
def setDatasetPath(self, path):
self.dsetpath = path
self.createTmpSpecFolder()
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)
def registerBackground(self, backgroundid: int, spec: np.ndarray) -> None:
fname: str = self.getPathForBackroundOfID(backgroundid)
spec = self.mapSpecToWavenumbers(spec)
np.save(fname, spec)
def getBackGroundOfID(self, id: int) -> np.ndarray:
path: str = self.getPathForBackroundOfID(id)
assert os.path.exists(path), f'requested backgound file {path} does not exist!'
return np.load(path)
def registerNewSpectrum(self, spectrum, specIndex: int, deassamble: bool = False):
assert self.tmpspecpath is not None, 'Attempting to save spectrum but no specPath is set...'
if deassamble:
wavenumbers, intensities = self.specConverter.deassembleSpecString(spectrum)
else:
assert type(spectrum) == np.ndarray
wavenumbers, intensities = spectrum[:, 0], spectrum[:, 1]
if not np.all(np.isfinite(spectrum)):
spectrum = self.makeSpecFinite(spectrum)
if specIndex == 0:
fname = os.path.join(self.tmpspecpath, 'Wavenumbers.npy')
np.save(fname, wavenumbers)
np.save(self.getPathForWavenumbers(), wavenumbers)
fname = os.path.join(self.tmpspecpath, f'Spectrum ({specIndex}).npy')
np.save(fname, averaged_counts)
np.save(fname, intensities)
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):
wavelength: float = (self.specConverter.excitWavel if self.specConverter is not None else 0.0)
self.createTrueMatchTxt(allSpectra, wavelength)
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]
if self.spectraBatchSize is None: # if it was not explicitely set, it will be set to produce only one batch
self.spectraBatchSize = numSpectra
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 mapSpecToWavenumbers(self, spec: np.ndarray) -> np.ndarray:
"""
In case of Thermo FTIR, not all recoreded spectra are guaranteed to have the same wavenumber range.
To account for that, this function here checks wavenumbers and corrects, if necessary..
:param spec: np.array, shape [numWavenumbers, 2]
:return correctedSpec:
"""
def getIntensityClosestToWavenumber(wavenumber: float) -> float:
closestIndex = np.argmin(abs(spec[:, 0] - wavenumber))
return spec[closestIndex, 1]
if self.ramanctrlname == "ThermoFTIRCom":
if self.wavenumbers is None:
self.wavenumbers = spec[:, 0]
np.save(self.getPathForWavenumbers(), self.wavenumbers)
else:
if not np.array_equal(spec[:, 0], self.wavenumbers):
corrSpec: np.ndarray = np.zeros((self.wavenumbers.shape[0], 2))
corrSpec[:, 0] = self.wavenumbers
for i in range(corrSpec.shape[0]):
corrSpec[i, 1] = getIntensityClosestToWavenumber(corrSpec[i, 0])
spec = corrSpec
return spec
def makeSpecFinite(self, spectrum: np.ndarray) -> np.ndarray:
"""
Removes all np.inf and np.nan from the spec. The missing values are obtained by linear interpolation
between the closest valid values.
"""
def _get_lower_finite_index(invIndex: int, valInd: np.ndarray) -> int:
possIndices: np.ndarray = valInd[valInd < invIndex]
diff: np.ndarray = np.abs(possIndices-invIndex)
return possIndices[np.argmin(diff)]
def _get_higher_finite_index(invIndex: int, valInd: np.ndarray) -> int:
possIndices: np.ndarray = valInd[valInd > invIndex]
diff: np.ndarray = np.abs(possIndices-invIndex)
return possIndices[np.argmin(diff)]
_intensities: np.ndarray = spectrum[:, 1]
finite: np.ndarray = np.isfinite(_intensities)
validIndices: np.ndarray = np.where(finite == True)[0]
failindices: np.ndarray = np.where(finite == False)[0]
for ind in failindices:
lowerInd: int = _get_lower_finite_index(ind, validIndices)
lowerVal: float = _intensities[lowerInd]
higherInd: int = _get_higher_finite_index(ind, validIndices)
higherVal: float = _intensities[higherInd]
diffInt: int = higherInd-lowerInd
spectrum[ind, 1] = _intensities[lowerInd] + (ind-lowerInd) * (higherVal-lowerVal)/diffInt
return spectrum
def getPathForBackroundOfID(self, id: int) -> str:
assert self.tmpspecpath is not None, 'temp spec path not set for SpecHandler!'
return os.path.join(self.tmpspecpath, f'background {id}.npy')
def getPathForWavenumbers(self) -> str:
assert self.tmpspecpath is not None, 'temp spec path not set for SpecHandler!'
return os.path.join(self.tmpspecpath, 'Wavenumbers.npy')
class WITecSpecConverter:
def __init__(self):
self.excitWavel: int = -1
def deassembleSpecString(self, specString: str):
keywordLines = self.getKeyWordLines(specString)
try:
specSize = self.getSpecSize(specString, keywordLines['SpectrumSize'][0])
except:
......@@ -73,136 +230,77 @@ class AdvancedWITecSpectra(object):
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]': [],