# -*- coding: utf-8 -*- """ GEPARD - Gepard-Enabled PARticle Detection Copyright (C) 2018 Lars Bittrich and Josef Brandt, Leibniz-Institut für Polymerforschung Dresden e. V. 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 . """ from PyQt5 import QtCore, QtWidgets import numpy as np from multiprocessing import Process, Queue, Event import queue import os import logging import logging.handlers import cv2 from time import localtime, strftime from typing import List, TYPE_CHECKING from ..imagestitch import imageStacking from ..helperfunctions import cv2imread_fix, cv2imwrite_fix, getInstrumentControl, positionWidgetOnScreen from ..opticalbackground import BackGroundManager from .uielements import TimeEstimateProgressbar from .zlevelsetter import ZLevelSetter from ..scenePyramid import ScenePyramid from ..gepardlogging import setDefaultLoggingConfig if TYPE_CHECKING: from ..sampleview import SampleView def scan(path, sol, zpositions, grid, controlclass, dataqueue, stopevent, logpath, ishdr=False): if ishdr: merge_mertens = cv2.createMergeMertens() logger = logging.getLogger() logger.addHandler( logging.handlers.RotatingFileHandler( logpath, maxBytes=5 * (1 << 20), backupCount=10) ) setDefaultLoggingConfig(logger) logger.info('starting new optical scan') try: instrctrl = controlclass(logger) instrctrl.updateImageConfig(os.path.dirname(logpath)) instrctrl.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())) instrctrl.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)): instrctrl.setBrightness(val) logger.info(f'writing hdr image to {fname}') instrctrl.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: img_path = os.path.join(path, name) logger.info(f'writing non hdr image to {img_path}') instrctrl.saveImage(img_path) if stopevent.is_set(): instrctrl.disconnect() return dataqueue.put(i) instrctrl.disconnect() except: logger.exception('Fatal error in optical scan') from ..errors import showErrorMessageAsWidget showErrorMessageAsWidget('See opticalScanLog in project directory for information') def subtractBackground(image, background): avg = np.mean(background) subtracted = np.clip(np.array(image - background + avg, dtype=np.uint8), 0, 255) return subtracted def legacyLoadAndPasteImage(srcnames, fullimage, fullzval, width, height, rotationvalue, p0, p1, p, background=None): colimgs = [] for name in srcnames: if os.path.isfile(name): curImg = cv2imread_fix(name) if background is not None: curImg = subtractBackground(curImg, background) colimgs.append(curImg) img, zval = imageStacking(colimgs) x, y = p Nx, Ny = int((p1[0] - p0[0] + width) / width * img.shape[1]), int((p0[1] - p1[1] + height) / height * img.shape[ 0]) + 10 # + 10 because of rotation and hopefully it will be small c, s = np.cos(np.radians(rotationvalue)), np.sin(np.radians(rotationvalue)) dx, dy = (x - p0[0]) / width * img.shape[1], (p0[1] - y) / height * img.shape[0] M = np.float32([[c, s, dx], [-s, c, dy]]) if fullimage is not None: cv2.warpAffine(img, M, (Nx, Ny), fullimage, borderMode=cv2.BORDER_TRANSPARENT) cv2.warpAffine(zval, M, (Nx, Ny), fullzval, borderMode=cv2.BORDER_TRANSPARENT) dst = fullimage zval = fullzval else: dst = cv2.warpAffine(img, M, (Nx, Ny)) zval = cv2.warpAffine(zval, M, (Nx, Ny)) return dst, zval def removeSrcTiles(names, path): files = os.listdir(path) for fpath in names: file = os.path.basename(fpath) if file in files: os.remove(fpath) def loadAndPasteImage(srcnames: List[str], pyramid, fullzval: np.ndarray, width: float, height: float, rotationvalue: float, p0: List[float], p1: List[float], p: List[float], logger, background=None): """ :param list of str srcnames: list of stacked scan files to merge :param ScenePyramid pyramid: the scene pyramid :param numpy.ndarray fullzval: full size zval image :param float width: width of camera :param float height: height camera :param float rotationvalue: angle of camera :param list of float p0: (min x; max y) of scan tile positions :param list of float p1: (max x; min y) of scan tile positions :param list of float p: position of current scan tile :param logger: the logger to use :param numpy.ndarray background: :return: """ colimgs = [] for name in srcnames: if os.path.isfile(name): curImg = cv2imread_fix(name) if background is not None: curImg = subtractBackground(curImg, background) colimgs.append(curImg) if 0 == len(colimgs): logger.warning(f'no src image(s) found to paste in loadAndPasteImage()') return None, fullzval img, zval = imageStacking(colimgs) x, y = p Nx, Ny = int((p1[0]-p0[0]+width)/width*img.shape[1]), int((p0[1]-p1[1]+height)/height*img.shape[0]) + 10 # + 10 because of rotation and hopefully it will be small c, s = np.cos(np.radians(rotationvalue)), np.sin(np.radians(rotationvalue)) dx, dy = (x-p0[0])/width*img.shape[1], (p0[1]-y)/height*img.shape[0] M = np.float32([[c, s, dx], [-s, c, dy]]) dst = None if pyramid is not None: if fullzval is not None: cv2.warpAffine(zval, M, (Nx, Ny), fullzval, borderMode=cv2.BORDER_TRANSPARENT) zval = fullzval else: zval = cv2.warpAffine(zval, M, (Nx, Ny)) pyramid.addSrcTile( img, np.float32([[c, s, 0], [-s, c, 0]]), (dx, dy), (Nx, Ny) ) return dst, zval class PointCoordinates(QtWidgets.QGridLayout): readPoint = QtCore.pyqtSignal(float, float, float, name='readPoint') def __init__(self, N, instrctrl, parent=None, names=None): super().__init__(parent) self.validpoints = [False]*N self.dswidgets = [] self.N = 0 self.instrctrl = instrctrl self.names = names self.pimageOnly = QtWidgets.QPushButton("Image") self.addWidget(self.pimageOnly, 0, 6, QtCore.Qt.AlignRight) self.pimageOnly.released.connect(QtCore.pyqtSlot()(lambda : self.read(-1))) self.createWidgets(N) @QtCore.pyqtSlot() def createWidgets(self, N, pointsgiven=[]): points = np.zeros((N, 3)) def connect(button, index): button.released.connect(QtCore.pyqtSlot()(lambda: self.read(index))) for i in range(self.N, min(N, len(self.dswidgets))): self.itemAtPosition(i+1, 0).setVisible(True) self.itemAtPosition(i+1, 1).setVisible(True) self.itemAtPosition(i+1, 2).setVisible(True) self.itemAtPosition(i+1, 3).setVisible(True) self.itemAtPosition(i+1, 4).setVisible(True) self.itemAtPosition(i+1, 5).setVisible(True) self.itemAtPosition(i+1, 6).setVisible(True) for i in range(self.N, N): if self.names is not None: lx = QtWidgets.QLabel(f"{self.names[i]} -> x:") else: lx = QtWidgets.QLabel(f"{i+1} -> x:") ly = QtWidgets.QLabel("y:") lz = QtWidgets.QLabel("z:") wx = QtWidgets.QDoubleSpinBox() wy = QtWidgets.QDoubleSpinBox() wz = QtWidgets.QDoubleSpinBox() for index, spinbox in enumerate([wx, wy, wz]): spinbox.setDecimals(1) spinbox.setRange(-500_000, 500_000) spinbox.setValue(points[i, index]) spinbox.setMaximumWidth(75) self.addWidget(lx, i+1, 0, QtCore.Qt.AlignLeft) self.addWidget(wx, i+1, 1, QtCore.Qt.AlignRight) self.addWidget(ly, i+1, 2, QtCore.Qt.AlignLeft) self.addWidget(wy, i+1, 3, QtCore.Qt.AlignRight) self.addWidget(lz, i+1, 4, QtCore.Qt.AlignLeft) self.addWidget(wz, i+1, 5, QtCore.Qt.AlignRight) pread = QtWidgets.QPushButton("read") connect(pread, i) self.addWidget(pread, i+1, 6, QtCore.Qt.AlignRight) self.dswidgets.append([wx, wy, wz]) for i in range(N, len(self.dswidgets)): self.itemAtPosition(i+1, 0).setVisible(False) self.itemAtPosition(i+1, 1).setVisible(False) self.itemAtPosition(i+1, 2).setVisible(False) self.itemAtPosition(i+1, 3).setVisible(False) self.itemAtPosition(i+1, 4).setVisible(False) self.itemAtPosition(i+1, 5).setVisible(False) self.itemAtPosition(i+1, 6).setVisible(False) self.N = N for i, p in pointsgiven: wx, wy, wz = self.dswidgets[i] x, y, z = p wx.setValue(x) wy.setValue(y) wz.setValue(z) self.validpoints[i] = True for i in range(len(pointsgiven), N): wx, wy, wz = self.dswidgets[i] wx.setValue(0) wy.setValue(0) wz.setValue(0) self.update() def read(self, index): x, y, z = self.instrctrl.getPosition() z = self.instrctrl.getUserZ() if index >= 0: wx, wy, wz = self.dswidgets[index] wx.setValue(x) wy.setValue(y) wz.setValue(z) self.validpoints[index] = True self.readPoint.emit(x, y, z) def getPoints(self): points = np.zeros((self.N, 3), dtype=np.double) for i in range(self.N): if self.validpoints[i]: wx, wy, wz = self.dswidgets[i] points[i, 0] = wx.value() points[i, 1] = wy.value() points[i, 2] = wz.value() else: points[i, :] = np.nan return points class OpticalScanUI(QtWidgets.QWidget): imageUpdate = QtCore.pyqtSignal(str, name='imageUpdate') # str = 'df' (= darkfield) or 'bf' (=bright field) boundaryUpdate = QtCore.pyqtSignal() backGroundSavedToPath = QtCore.pyqtSignal(int, str) 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() pointgroup = QtWidgets.QGroupBox("Point coordinates [µm]", self) self.instrctrl = sampleview.instrctrl self.dataset = sampleview.dataset self.pyramid: ScenePyramid = None self.positions = [] self.process = None self.points = PointCoordinates(5, self.instrctrl, self) pointgroup.setLayout(self.points) self.points.readPoint.connect(self.takePoint) bkggroup = QtWidgets.QGroupBox('Manage Background Images') self.enableBackGround = QtWidgets.QCheckBox('Enable BackgroundSubtraction') self.enableBackGround.setChecked(False) self.enableBackGround.stateChanged.connect(self.enableDisableBackground) self.backGroundManager = BackGroundManager(self) self.backGroundManager.managerClosed.connect(self.managerWasClosed) self.backGroundManager.readBackground.connect(self.readBackground) self.showBgkManagerBtn = QtWidgets.QPushButton('Show Background Manager Window') self.showBgkManagerBtn.setDisabled(True) self.showBgkManagerBtn.clicked.connect(self.showHideBackgroundWindow) bkglayout = QtWidgets.QVBoxLayout() bkglayout.addWidget(self.enableBackGround) bkglayout.addWidget(self.showBgkManagerBtn) bkggroup.setLayout(bkglayout) self.pareaselect = QtWidgets.QPushButton("Area select", self) label = QtWidgets.QLabel("Size increase:", self) self.radiusincreaseedit = QtWidgets.QDoubleSpinBox(self) self.radiusincreaseedit.setMinimum(-1000) self.radiusincreaseedit.setMaximum(1000) self.radiusincreaseedit.setDecimals(0) self.radiusincreaseedit.setSingleStep(20) self.radiusincreaseedit.setMaximumWidth(100) self.radiusincreaseedit.valueChanged.connect(self.areaSelect) self.hdrcheck = QtWidgets.QCheckBox("High dynamic range", self) self.hdrcheck.setChecked(False) self.zLevelSetter = ZLevelSetter() self.zLevelSetter.sizeChanged.connect(self._updateSize) self.prun = QtWidgets.QPushButton("Run", self) self.pexit = QtWidgets.QPushButton("Cancel", self) self.pareaselect.released.connect(self.areaSelect) self.prun.released.connect(self.run) self.pexit.released.connect(self.cancelScan) self.prun.setEnabled(False) self.progressbar = TimeEstimateProgressbar() self.progressbar.disable() self.startMsgBox: QtWidgets.QMessageBox = QtWidgets.QMessageBox() radioGroup = QtWidgets.QGroupBox('Shape') radioLayout = QtWidgets.QHBoxLayout() self.circlerad = QtWidgets.QRadioButton("Circle") self.circlerad.clicked.connect(self.areaSelect) self.rectanglerad = QtWidgets.QRadioButton("Rectangle") self.rectanglerad.setChecked(True) self.rectanglerad.clicked.connect(self.areaSelect) radioLayout.addWidget(self.circlerad) radioLayout.addWidget(self.rectanglerad) radioGroup.setLayout(radioLayout) micModeGroup = QtWidgets.QGroupBox('Mode for Image Acquisition') micModeLayout = QtWidgets.QHBoxLayout() self.df_btn = QtWidgets.QRadioButton('Darkfield') self.df_btn.setChecked(True) self.df_btn.clicked.connect(self.areaSelect) self.bf_btn = QtWidgets.QRadioButton('Brightfield') self.bf_btn.clicked.connect(self.areaSelect) micModeLayout.addWidget(self.df_btn) micModeLayout.addWidget(self.bf_btn) micModeGroup.setLayout(micModeLayout) self.areaOptionsGroup = QtWidgets.QGroupBox('Area Select Options') areaLayout = QtWidgets.QFormLayout() areaLayout.addRow(radioGroup) areaLayout.addRow(label, self.radiusincreaseedit) areaLayout.addRow(micModeGroup) if not self.view.lightModeSwitchNeeded: micModeGroup.setDisabled(True) areaLayout.addRow(self.pareaselect) self.areaOptionsGroup.setLayout(areaLayout) self.areaOptionsGroup.setDisabled(True) furtherOptionsGroup = QtWidgets.QGroupBox('Further Options') furtherOptionsLayout = QtWidgets.QFormLayout() furtherOptionsLayout.addRow(self.hdrcheck) furtherOptionsGroup.setLayout(furtherOptionsLayout) btnLayout = QtWidgets.QHBoxLayout() btnLayout.addWidget(self.prun) btnLayout.addWidget(self.pexit) btnLayout.addStretch() vbox1 = QtWidgets.QVBoxLayout() vbox2 = QtWidgets.QVBoxLayout() vbox1.addWidget(pointgroup) vbox1.addWidget(self.zLevelSetter) vbox1.addStretch() vbox2.addWidget(self.areaOptionsGroup) vbox2.addWidget(bkggroup) vbox2.addWidget(furtherOptionsGroup) vbox2.addStretch() optionsLayout = QtWidgets.QHBoxLayout() optionsLayout.addLayout(vbox1) optionsLayout.addLayout(vbox2) mainLayout.addLayout(optionsLayout) mainLayout.addWidget(self.progressbar) mainLayout.addLayout(btnLayout) self.setLayout(mainLayout) positionWidgetOnScreen(self) self.setVisible(False) def enableDisableBackground(self): self.showBgkManagerBtn.setEnabled(self.enableBackGround.isChecked()) if self.enableBackGround.isChecked(): self.backGroundManager.calculateAverageImage() else: self.deleteBackGroundImage() def showHideBackgroundWindow(self): if self.backGroundManager.isHidden(): self.backGroundManager.show() self.showBgkManagerBtn.setText('Hide Background Manager Window') else: self.backGroundManager.hide() self.showBgkManagerBtn.setText('Show Background Manager Window') def managerWasClosed(self): self.showBgkManagerBtn.setText('Show Background Manager Window') @QtCore.pyqtSlot() def _updateSize(self): self.adjustSize() @QtCore.pyqtSlot() 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?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: 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() self.close() @QtCore.pyqtSlot() def areaSelect(self): magn = self.instrctrl.magn if self.circlerad.isChecked() == True: xym, r = cv2.minEnclosingCircle(np.array([p[:2] for p in self.dataset.fitpoints], dtype=np.float32)) r += self.radiusincreaseedit.value() phi = np.linspace(0, 2*np.pi, magn, endpoint=False) self.dataset.boundary = [[xym[0]+r*np.cos(phii), xym[1]+r*np.sin(phii)] for phii in phi] else: da = self.radiusincreaseedit.value() x0, x1 = self.dataset.fitpoints[:,0].min()-da, self.dataset.fitpoints[:,0].max()+da y0, y1 = self.dataset.fitpoints[:,1].min()-da, self.dataset.fitpoints[:,1].max()+da a = 2*(y1-y0 + x1-x0) nx, ny = max(int(np.round((x1-x0)/a*magn)),2), max(int(np.round((y1-y0)/a*magn)),2) x, dx = np.linspace(x0, x1, nx, endpoint=False, retstep=True) y, dy = np.linspace(y0, y1, ny, endpoint=False, retstep=True) self.dataset.boundary = [[xi, yi] for xi, yi in zip(x,y0*np.ones_like(x))] + \ [[xi, yi] for xi, yi in zip(x1*np.ones_like(y),y)] + \ [[xi, yi] for xi, yi in zip(x[::-1]+dx,y1*np.ones_like(x))] + \ [[xi, yi] for xi, yi in zip(x0*np.ones_like(y),y[::-1]+dy)] self.boundaryUpdate.emit() self.prun.setEnabled(True) def setPyramid(self, pyramid): self.pyramid = pyramid def resetDataset(self, ds): self.dataset = ds self.points.createWidgets(5, list(zip(ds.fitindices,ds.fitpoints))) if len(self.dataset.fitindices)>1: self.areaOptionsGroup.setEnabled(True) softwarez = self.instrctrl.getSoftwareZ() if abs(softwarez) > 0.1 and self.instrctrl.name == 'WITecCOM': reply = QtWidgets.QMessageBox.critical(self, 'Software z position nonzero', "The software z position needs to be set to zero."\ " Moving z for %4.0f µm relative to current position. Counteract manually before proceeding!"%(-softwarez), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Abort, QtWidgets.QMessageBox.Abort) if reply == QtWidgets.QMessageBox.Yes: self.instrctrl.moveZto(0.0) else: QtWidgets.QMessageBox.information(self, "Information", 'Scan may stop if the software z position gets to high!', QtWidgets.QMessageBox.Ok) @QtCore.pyqtSlot(float, float, float) def takePoint(self, x, y, z): if self.dataset.heightmap is not None: reply = QtWidgets.QMessageBox.critical(self, 'Dataset already contains optical scan data', "Continuation will invalidate all previous results! Continue anyway?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: return self.instrctrl.saveImage(self.dataset.getTmpImageName()) width, height, rotationvalue = self.instrctrl.getImageDimensions(self.view.microscopeMode) pshift = self.instrctrl.getRamanPositionShift() self.dataset.pshift = pshift img = cv2imread_fix(self.dataset.getTmpImageName()) self.dataset.imagedim_bf = self.instrctrl.getImageDimensions('bf') self.dataset.pixelscale_bf = self.dataset.imagedim_bf[0]/img.shape[1] #=imagedim_width/shape[1] self.dataset.imagedim_df = self.instrctrl.getImageDimensions('df') self.dataset.pixelscale_df = self.dataset.imagedim_df[0]/img.shape[1] #=imagedim_width/shape[1] points = self.points.getPoints() ind = np.isfinite(points[:,0]) self.dataset.fitindices = np.arange(points.shape[0])[ind] points = points[ind,:].copy() self.dataset.fitpoints = points if len(points)>1: self.areaOptionsGroup.setEnabled(True) points = np.concatenate(([[x,y,z]], points), axis=0) p0 = [points[:,0].min(), points[:,1].max()] p1 = [points[:,0].max(), points[:,1].min()] if self.dataset.maxdim is not None: p0 = [min(p0[0], self.dataset.maxdim[0]), max(p0[1], self.dataset.maxdim[1])] p1 = [max(p1[0], self.dataset.maxdim[2]), min(p1[1], self.dataset.maxdim[3])] Nx, Ny = int((p1[0]-p0[0]+width)/width*img.shape[1]), int((p0[1]-p1[1]+height)/height*img.shape[0]) + 10 # + 10 because of rotation and hopefully it will be small c, s = np.cos(np.radians(rotationvalue)), np.sin(np.radians(rotationvalue)) dx, dy = (x-p0[0])/width*img.shape[1], (p0[1]-y)/height*img.shape[0] M = np.float32([[c, s, dx], [-s, c, dy]]) #dst = cv2.warpAffine(img, M, (Nx, Ny)) M_rot = np.float32([[c, s, 0], [-s, c, 0]]) img_rot = cv2.warpAffine(img, M_rot, (img.shape[1], img.shape[0] + 10)) # calc new pixel start coords for tile # @see https://stackoverflow.com/a/43166421/9880753 c_dest = cv2.transform(np.float32([[(0, 0)]]), np.float32([[c, s, dx], [-s, c, dy]])) self.pyramid.addSrcTileSimple( img_rot, (c_dest[0][0][0], c_dest[0][0][1]), p0 ) #if self.view.imgdata is not None and self.dataset.lastpos is not None: # lp = self.dataset.lastpos # dx, dy = (lp[0]-p0[0])/width*img.shape[1], (p0[1]-lp[1])/height*img.shape[0] # full = self.view.imgdata # M = np.float32([[1,0,dx],[0,1,dy]]) # try: # full = cv2.warpAffine(full, M, (Nx, Ny)) #fails, if image dimensions are >32767x32767px... # dst = cv2.max(full, dst) # except: # QtWidgets.QMessageBox.critical(self, 'Error', 'Image is too large\nSelect smaller region.') # raise # return #self.view.imgdata = dst self.dataset.lastpos = p0 self.dataset.maxdim = p0 + p1 self.dataset.readin = False self.imageUpdate.emit(self.view.microscopeMode) @QtCore.pyqtSlot(int) def readBackground(self, indexOfCallingImage): tmp_path = self.dataset.getTmpImageName() self.instrctrl.saveImage(tmp_path) self.backGroundSavedToPath.emit(indexOfCallingImage, tmp_path) def writeBackGroundImage(self, backgroundImg): cv2imwrite_fix(self.dataset.getBackgroundImageName(), cv2.cvtColor(backgroundImg, cv2.COLOR_RGB2BGR)) def deleteBackGroundImage(self): if os.path.exists(self.dataset.getBackgroundImageName()): os.remove(self.dataset.getBackgroundImageName()) @QtCore.pyqtSlot() def run(self): ramanPoints = self.dataset.particleContainer.getMeasurementPixelCoords() if len(ramanPoints) != 0: reply = QtWidgets.QMessageBox.critical(self, 'Dataset already contains raman scan points', "Continuation will invalidate all previous results! Continue anyway?", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: return 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 if os.path.exists(self.dataset.getBackgroundImageName()): reply = QtWidgets.QMessageBox.critical(self, 'Background correction info.', "A background image was saved. All acquired images will be corrected. Continue?\nOtherwise delete images in background manager", QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No, QtWidgets.QMessageBox.Yes) if reply != QtWidgets.QMessageBox.Yes: return self.view.imparent.lightModeSwitch.df_btn.setChecked(self.df_btn.isChecked()) self.view.imparent.lightModeSwitch.setDisabled(True) self.view.setMicroscopeMode() if self.df_btn.isChecked(): self.view.dataset.imagescanMode = 'df' else: self.view.dataset.imagescanMode = 'bf' points = np.float32(self.dataset.fitpoints) # convert z to software z, which is relative to current user z softwarez = self.instrctrl.getSoftwareZ() # get current software z points[:,2] += softwarez-self.instrctrl.getUserZ() try: zrange = self.zLevelSetter.getZLevels() except ValueError: return zmaxstack = max(zrange) if len(zrange) == 1: zmaxstack = 0.0 self.dataset.zpositions = np.array([0.0]) else: # self.dataset.zpositions = np.linspace(0, zmaxstack, Nz) self.dataset.zpositions = zrange width, height, rotationvalue = self.dataset.imagedim_df 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 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 zmin, zmax = None, None for i, p in enumerate(self.dataset.grid): x, y = p z = sol[0]*x + sol[1]*y + sol[2] if i==0: zmin, zmax = z, z else: if zmin>z: zmin = z if zmax= 0: Ngrid = len(self.dataset.grid) names = [] for k in range(len(self.dataset.zpositions)): names.append(os.path.join(self.dataset.getScanPath(), f"image_{i}_{k}.bmp")) width, height, rotationvalue = (self.dataset.imagedim_df if self.view.imparent.lightModeSwitch.df_btn.isChecked() else self.dataset.imagedim_bf) p = self.dataset.grid[i] p0, p1 = self.dataset.maxdim[:2], self.dataset.maxdim[2:] if os.path.exists(self.dataset.getBackgroundImageName()): background_img = cv2imread_fix(self.dataset.getBackgroundImageName()) else: background_img = None self.view.imgdata, self.dataset.zvalimg = loadAndPasteImage( names, self.pyramid, self.dataset.zvalimg, width, height, rotationvalue, p0, p1, p, self.logger, background=background_img ) removeSrcTiles(names, self.dataset.getScanPath()) self.progressbar.setValue(i+1) self.imageUpdate.emit(self.view.microscopeMode) if i == Ngrid-1: self.dataset.saveZvalImg() self.process.join() self.dataqueue.close() self.dataqueue.join_thread() self.pyramid.toDataset() self.instrctrl.connect() self.view.saveDataSet() self.view.unblockUI() self.view.imparent.activateMaxMode() self.progressbar.resetTimerAndCounter() self.progressbar.disable() self.close() return self.timer.start(100) def closeEvent(self, event): self.backGroundManager.close() event.accept()