Commit fa8cd07f authored by Josef Brandt's avatar Josef Brandt

AdvancedLogging, RepeatCom-Decorator in WITec-Com

parent 40f9393a
......@@ -18,22 +18,26 @@ 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 logging
import traceback
import os
from io import StringIO
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
from .gepardlogging import setDefaultLoggingConfig
class GEPARDMainWindow(QtWidgets.QMainWindow):
def __init__(self, logpath):
def __init__(self, logger):
super(GEPARDMainWindow, self).__init__()
self.setWindowTitle("GEPARD")
self.resize(900, 700)
self.view = SampleView(logpath)
self.view = SampleView(logger)
self.view.imparent = self
self.view.ScalingChanged.connect(self.scalingChanged)
self.scalebar = ScaleBar(self)
......@@ -332,6 +336,31 @@ if __name__ == '__main__':
import sys
from time import localtime, strftime
def excepthook(excType, excValue, tracebackobj):
"""
Global function to catch unhandled exceptions.
@param excType exception type
@param excValue exception value
@param tracebackobj traceback object
:return:
"""
tbinfofile = StringIO()
traceback.print_tb(tracebackobj, None, tbinfofile)
tbinfofile.seek(0)
tbinfo = tbinfofile.read()
logging.critical("Fatal error in program excecution!")
logging.critical(tbinfo)
from .errors import showErrorMessageAsWidget
showErrorMessageAsWidget(tbinfo)
sys.exit(1)
sys.excepthook = excepthook
logger = logging.getLogger(__name__)
setDefaultLoggingConfig(logger)
app = QtWidgets.QApplication(sys.argv)
app.setApplicationName("GEPARD") # appname needed for logpath
......@@ -342,14 +371,11 @@ if __name__ == '__main__':
if not os.path.exists(logpath):
os.mkdir(logpath)
logname = os.path.join(logpath, 'logfile.txt')
fp = open(logname, "a")
sys.stderr = fp
sys.stdout = fp
print("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime()), flush=True)
logger.addHandler(logging.FileHandler(logname))
gepard = GEPARDMainWindow(logpath)
logger.info("starting GEPARD at: " + strftime("%d %b %Y %H:%M:%S", localtime()))
gepard = GEPARDMainWindow(logger)
gepard.showMaximized()
ret = app.exec_()
if fp is not None:
fp.close()
......@@ -19,6 +19,8 @@ along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>.
"""
import numpy as np
import os
import logging
from PyQt5 import QtCore, QtWidgets, QtGui
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
......@@ -26,9 +28,10 @@ from threading import Thread
from .segmentation import Segmentation
from .analysis.particleCharacterization import getParticleStatsWithPixelScale, loadZValImageFromDataset
from .errors import InvalidParticleError
from .errors import InvalidParticleError, showErrorMessageAsWidget
from .uielements import TimeEstimateProgressbar
from .scenePyramid import ScenePyramid
from .gepardlogging import setDefaultLoggingConfig
Nscreen = 1000
......@@ -283,6 +286,11 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.threadrunning = False
self.view = parent
logPath = os.path.join(self.dataset.path, 'detectionlog.txt')
self.logger = logging.getLogger('detection')
self.logger.addHandler(logging.FileHandler(logPath))
setDefaultLoggingConfig(self.logger)
vbox = QtWidgets.QVBoxLayout()
hbox = QtWidgets.QHBoxLayout()
......@@ -657,13 +665,24 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.seg.setParameters(**kwargs)
seedradius = self.seedradiusedit.value()
if showname is not None:
stepImg, imgtype = self.seg.apply2Image(img, self.imglabel.seedpoints, self.imglabel.seeddeletepoints,
seedradius, self.dataset, return_step=showname)
self.imglabel.showStep(stepImg, imgtype)
try:
stepImg, imgtype = self.seg.apply2Image(img, self.imglabel.seedpoints, self.imglabel.seeddeletepoints,
seedradius, self.dataset, self.logger, return_step=showname)
self.imglabel.showStep(stepImg, imgtype)
except:
showErrorMessageAsWidget('Fatal error in particle detection, see detectionlog for info')
self.logger.exception('Fatal error in particle detection')
else:
measurementpoints, contours = self.seg.apply2Image(img, self.imglabel.seedpoints, self.imglabel.seeddeletepoints,
seedradius, self.dataset)
self.imglabel.updateDetectionResults(contours, measurementpoints)
try:
measurementpoints, contours = self.seg.apply2Image(img, self.imglabel.seedpoints, self.imglabel.seeddeletepoints,
seedradius, self.dataset, self.logger)
self.imglabel.updateDetectionResults(contours, measurementpoints)
except:
showErrorMessageAsWidget('Fatal error in particle detection, see detectionlog for info')
self.logger.exception('Fatal error in particle detection')
@QtCore.pyqtSlot()
def clearDetection(self):
......@@ -707,6 +726,7 @@ class ParticleDetectionView(QtWidgets.QWidget):
Detect all particles
:return:
"""
self.logger.info('Starting particle detection')
self.saveDetectParams(self.dataset)
if self.thread is not None and self.thread.is_alive():
self.cancelThread()
......@@ -757,6 +777,11 @@ class ParticleDetectionView(QtWidgets.QWidget):
self.progressbar.setValue(newVal)
def _worker(self):
logPath = os.path.join(self.dataset.path, 'detectionlog.txt')
detectLogger = logging.getLogger('detection_worker')
detectLogger.addHandler(logging.FileHandler(logPath))
setDefaultLoggingConfig(detectLogger)
kwargs = {}
seedpoints, deletepoints = [], []
if self.dataset is not None:
......@@ -766,8 +791,11 @@ class ParticleDetectionView(QtWidgets.QWidget):
kwargs[name] = valuefunc()
seedradius = self.seedradiusedit.value()
self.seg.setParameters(**kwargs)
measurementPoints, contours= self.seg.apply2Image(self.img, seedpoints, deletepoints, seedradius, self.dataset)
try:
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')
if measurementPoints is None: # computation was canceled
return
......
......@@ -31,3 +31,9 @@ class NotConnectedContoursError(Exception):
class TileSizeError(Exception):
pass
def showErrorMessageAsWidget(errorMessage):
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv) #an app is needed to create and show QWidgets..
QtWidgets.QMessageBox.critical(QtWidgets.QWidget(), 'Fatal Error', errorMessage)
# -*- 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 logging
def setDefaultLoggingConfig(logger: logging.Logger):
"""
Applies a default configuration to the specified logger
Parameters
----------
logger : logging.Logger
DESCRIPTION.
Returns
-------
None.
"""
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.WARNING)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
for handler in logger.handlers:
handler.setFormatter(formatter)
\ No newline at end of file
......@@ -24,64 +24,65 @@ import numpy as np
from multiprocessing import Process, Queue, Event
import queue
from .imagestitch import imageStacking
import sys, os
import os, sys
import logging
import cv2
from .helperfunctions import cv2imread_fix, cv2imwrite_fix
from time import time
from time import time, localtime, strftime
from .opticalbackground import BackGroundManager
from .uielements import TimeEstimateProgressbar
from .zlevelsetter import ZLevelSetter
from .scenePyramid import ScenePyramid
from .gepardlogging import setDefaultLoggingConfig
def scan(path, sol, zpositions, grid, controlclass, dataqueue,
stopevent, logpath='', ishdr=False):
stopevent, logpath, ishdr=False):
if ishdr:
merge_mertens = cv2.createMergeMertens()
fp = None
if logpath != '':
try:
fp = open(os.path.join(logpath, 'scanlog.txt'), 'a')
sys.stderr = fp
sys.stdout = fp
except IOError:
print('separate logging failed', flush=True)
pass
print('starting new optical scan', flush=True)
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(logging.FileHandler(logpath))
logger.info('starting new optical scan')
setDefaultLoggingConfig(logger)
try:
ramanctrl = controlclass(logger)
ramanctrl.connect()
zlist = list(enumerate(zpositions))
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]):
name = f"image_{i}_{k}.bmp"
logger.info(f'taking image {name}, time: ' + strftime("%d %b %Y %H:%M:%S", localtime()))
zik = z+zk
assert not np.isnan(zik)
logger.info(f"moving to: {x}, {y}, {zik}, time: " + strftime("%d %b %Y %H:%M:%S", localtime()))
ramanctrl.moveToAbsolutePosition(x, y, zik)
if ishdr:
img_list = []
fname = os.path.join(path, f"tmp.bmp")
values = [5.,25.,100.]
for j, val in enumerate(values if (i%2+k%2)%2==0 else reversed(values)):
ramanctrl.setBrightness(val)
ramanctrl.saveImage(fname)
img_list.append(cv2imread_fix(fname))
res_mertens = merge_mertens.process(img_list)
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')
cv2imwrite_fix(os.path.join(path,name), res_mertens_8bit)
else:
ramanctrl.saveImage(os.path.join(path,name))
if stopevent.is_set():
ramanctrl.disconnect()
return
dataqueue.put(i)
ramanctrl.disconnect()
except:
logger.exception('Fatal error in optical scan')
from .errors import showErrorMessageAsWidget
showErrorMessageAsWidget('See opticalScanLog in project directory for information')
ramanctrl = controlclass()
ramanctrl.connect()
zlist = list(enumerate(zpositions))
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]):
name = f"image_{i}_{k}.bmp"
print("time:", time(), flush=True)
zik = z+zk
assert not np.isnan(zik)
print("moving to:", x, y, zik, flush=True)
ramanctrl.moveToAbsolutePosition(x, y, zik)
if ishdr:
img_list = []
fname = os.path.join(path,f"tmp.bmp")
values = [5.,25.,100.]
for j, val in enumerate(values if (i%2+k%2)%2==0 else reversed(values)):
ramanctrl.setBrightness(val)
ramanctrl.saveImage(fname)
img_list.append(cv2imread_fix(fname))
res_mertens = merge_mertens.process(img_list)
res_mertens_8bit = np.clip(res_mertens*255, 0, 255).astype('uint8')
cv2imwrite_fix(os.path.join(path,name), res_mertens_8bit)
else:
ramanctrl.saveImage(os.path.join(path,name))
if stopevent.is_set():
ramanctrl.disconnect()
return
dataqueue.put(i)
ramanctrl.disconnect()
if fp is not None:
fp.close()
def subtractBackground(image, background):
avg = np.mean(background)
......@@ -284,9 +285,9 @@ class OpticalScan(QtWidgets.QWidget):
boundaryUpdate = QtCore.pyqtSignal()
backGroundSavedToPath = QtCore.pyqtSignal(int, str)
def __init__(self, ramanctrl, dataset, logpath='', parent=None):
def __init__(self, ramanctrl, dataset, logger, parent=None):
super().__init__(parent, QtCore.Qt.Window)
self.logpath = logpath
self.logger = logger
self.view: QtWidgets.QGraphicsView = parent
mainLayout = QtWidgets.QVBoxLayout()
......@@ -638,17 +639,17 @@ class OpticalScan(QtWidgets.QWidget):
self.dataset.zpositions = zrange
width, height, rotationvalue = self.dataset.imagedim_df
print("Width, height, rotation:", width, height, rotationvalue)
print("Points x:", points[:,0].min(), points[:,0].max())
print("Points y:", points[:,1].min(), points[:,1].max())
print("Points z:", points[:,2].min(), points[:,2].max())
self.logger.debug(f"Width: {width}, height: {height}, rotation: {rotationvalue}")
self.logger.debug(f"Points x: {points[:,0].min(), points[:,0].max()}")
self.logger.debug(f"Points y: {points[:,1].min(), points[:,1].max()}")
self.logger.debug(f"Points z: {points[:,2].min(), points[:,2].max()}")
A = np.ones((points.shape[0],3))
A[:,:2] = points[:,:2]
b = points[:,2]
sol = np.linalg.lstsq(A, b, rcond=None)[0]
self.dataset.heightmap = sol
print("Fit deviation:", sol[0]*points[:,0]+sol[1]*points[:,1]+sol[2] -points[:,2] )
self.logger.info(f"Fit deviation: {sol[0]*points[:,0]+sol[1]*points[:,1]+sol[2] -points[:,2]}")
path = self.dataset.getScanPath()
# get zmin and zmax in absolut software z coordinates
......@@ -676,10 +677,11 @@ class OpticalScan(QtWidgets.QWidget):
self.ramanctrl.disconnect()
self.processstopevent = Event()
self.dataqueue = Queue()
logpath = os.path.join(self.dataset.path, 'opticalScanLog.txt')
self.process = Process(target=scan, args=(path, sol, self.dataset.zpositions,
self.dataset.grid, self.ramanctrl.__class__,
self.dataqueue, self.processstopevent,
self.logpath, self.hdrcheck.isChecked()))
logpath, self.hdrcheck.isChecked()))
self.process.start()
self.progressbar.enable()
self.progressbar.resetTimerAndCounter()
......
......@@ -36,9 +36,54 @@ except ModuleNotFoundError:
from ramanbase import RamanBase
from configRaman import RamanSettingParam
from socket import gethostname
from ..errors import showErrorMessageAsWidget
class WITecCOM(RamanBase):
def comErrorRepeater(comCallFunction):
"""
A wrapper function for handling rarely occuring com errors.
If a com 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():
success = False
for _ in range(3):
try:
comCallFunction(*args, **kwargs)
success = True
break
except pythoncom.com_error:
sleep(1.)
return success
comObj = args[0] #self is always passed as first argument
functionSucceeded = tryFunctionThreeTimes()
if not functionSucceeded:
comObj.disconnect()
sleep(1.)
comObj.connect()
functionSucceeded = tryFunctionThreeTimes()
if not functionSucceeded:
showErrorMessageAsWidget(f'Com error on function {comCallFunction.__name__}')
raise pythoncom.com_error
return wrapper
CLSID = "{C45E77CE-3D66-489A-B5E2-159F443BD1AA}"
magn = 20
......@@ -46,7 +91,7 @@ class WITecCOM(RamanBase):
ramanParameters = [RamanSettingParam('IntegrationTime (s)', 'double', default=0.5, minVal=0.01, maxVal=100),
RamanSettingParam('Accumulations', 'int', default=5, minVal=1, maxVal=100)]
def __init__(self, hostname=None):
def __init__(self, logger, hostname=None):
super().__init__()
self.name = 'WITecCOM'
if hostname is None:
......@@ -55,6 +100,7 @@ class WITecCOM(RamanBase):
clsctx=pythoncom.CLSCTX_REMOTE_SERVER)
self.advancedInterface = False
self.doAutoFocus = False
self.logger = logger
def connect(self):
if not self.IBUCSAccess.RequestWriteAccess(True):
......@@ -73,7 +119,7 @@ class WITecCOM(RamanBase):
try:
vnr = float(vnr)
except ValueError:
print("WITec: unknown version format:", version)
self.logger.critical(f"WITec: unknown version format: {version}")
vnr = 0.0 # version format unknown
self.version = vnr
......@@ -208,10 +254,12 @@ class WITecCOM(RamanBase):
self.ramanParameters.append(RamanSettingParam('Autofocus', 'checkBox', default=False))
self.ramanParameters.append(RamanSettingParam('Spectra Batch Size', 'int', default=1000, minVal=1, maxVal=1e6))
@comErrorRepeater
def getBrightness(self):
assert self.connected
return self.BrightnessMan.GetValue()
@comErrorRepeater
def setBrightness(self, newval):
if newval<0:
newval = 0.0
......@@ -220,6 +268,7 @@ class WITecCOM(RamanBase):
self.BrightnessMan.SetValue(newval)
sleep(.1)
@comErrorRepeater
def getRamanPositionShift(self):
rx = .5-self.RamanRelativeXMan.GetValue() # current assumption is, that image center is current position
ry = self.RamanRelativeYMan.GetValue()-.5
......@@ -231,6 +280,7 @@ class WITecCOM(RamanBase):
self.IBUCSAccess.RequestWriteAccess(False)
self.connected = False
@comErrorRepeater
def getPosition(self):
assert self.connected
self.PosXCurFloatMan.Update()
......@@ -241,17 +291,20 @@ class WITecCOM(RamanBase):
z = self.PosZCurFloatMan.GetSingleValueAsDouble()[1]
return x, y, z
@comErrorRepeater
def getSoftwareZ(self):
self.PosZCurFloatMan.Update()
z = self.PosZCurFloatMan.GetSingleValueAsDouble()[1]
return z
@comErrorRepeater
def getUserZ(self):
assert self.connected
self.PosZCurUserFloatMan.Update()
z = self.PosZCurUserFloatMan.GetSingleValueAsDouble()[1]
return z
@comErrorRepeater
def moveToAbsolutePosition(self, x, y, z=None, epsxy=0.11, epsz=0.011):
assert self.connected
initpos = self.getPosition()
......@@ -271,9 +324,9 @@ class WITecCOM(RamanBase):
numFails += 1
sleep(.1)
if numFails > 0:
print(f'{numFails} of max. {maxFails} unsuccessfull position submits to Position: {x}, {y}', flush=True)
self.logger.info(f'{numFails} of max. {maxFails} unsuccessfull position submits to Position: {x}, {y}')
if not positionSubmitted:
print(f'Error setting Position: {x}, {y}\nExpecting \"signal ignored\" warning', flush=True)
self.logger.warning(f'Error setting Position: {x}, {y}\nExpecting \"signal ignored\" warning')
# wait till position is found within accuracy of epsxy; check if position changes at all
distance = 2*epsxy
......@@ -281,15 +334,15 @@ class WITecCOM(RamanBase):
curpos = self.getPosition()
distance = max(abs(curpos[0]-x), abs(curpos[1]-y))
if ((time()-t0>0.5) and max(abs(curpos[0]-initpos[0]), abs(curpos[1]-initpos[1]))<epsxy) or (time()-t0>10.):
print("WARNING: signal ignored:", time()-t0, x, y, curpos, initpos)
sys.stdout.flush()
self.logger.warning(f"WARNING: signal ignored:, time: {time()-t0}, x: {x}, y: {y}, curPos: {curpos}, initpos: {initpos}")
break
sleep(.01)
sleep(.1)
initpos = self.getPosition()
if z is not None:
self.moveZto(z, epsz)
@comErrorRepeater
def moveZto(self, z, epsz=0.011):
assert self.connected
#z = round(z,2)
......@@ -303,21 +356,26 @@ class WITecCOM(RamanBase):
curpos = self.PosZCurFloatMan.GetSingleValueAsDouble()[1]
distance = abs(curpos-z)
if ((time()-t0>0.5) and abs(curpos-initpos)<epsz) or (time()-t0>10.):
print("WARNING: signal z ignored:", time()-t0)
sys.stdout.flush()
self.logger.warning(f"WARNING: signal z ignored after {time()-t0} seconds")
break
sleep(.01)
sleep(.1)
@comErrorRepeater
def saveImage(self, fname):
"""
Save current camera image to file name
:return:
"""
assert self.connected
self.ImageNameMan.SetValue(fname)
self.ImageSaveMan.OperateTrigger()
sleep(.1)
@comErrorRepeater
def getImageDimensions(self, mode = 'df'):
""" Get the image width and height in um and the orientation angle in degrees.
"""
Get the image width and height in um and the orientation angle in degrees.
"""
assert self.connected
width, height = self.ImageWidthMan.GetValue(), self.ImageHeightMan.GetValue()
......@@ -333,7 +391,9 @@ class WITecCOM(RamanBase):
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...
def initiateMeasurement(self, ramanSettings):
assert self.connected
if self.advancedInterface:
......@@ -343,14 +403,14 @@ class WITecCOM(RamanBase):
self.initateSilentSpectrumAcquisition(ramanSettings)
else:
self.initiateTimeSeriesMeasurement(ramanSettings)
def triggerMeasurement(self, num):
if self.advancedInterface and self.doAutoFocus:
self.doSpectralAutoFocus()
self.acquireSilentSpectrum(num)
else:
self.triggerTimeSeriesMeasurement(num)
def finishMeasurement(self, aborted=False):
if self.advancedInterface and self.doAutoFocus:
state = self.BeamPathState.GetValue()
......@@ -410,7 +470,7 @@ class WITecCOM(RamanBase):
else:
sleep(0.02)
if time()-t1>3.: