diff --git a/.gitignore b/.gitignore index 97bbc51565f5b9017f678c389a5c1a729486f808..b5150f329a7dcbe83b95759071e90182c4f1e92a 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,5 @@ instrumentcom/renishawtesting.py *.pyc *.jdx + +instrumentcom/thermoFTIRCom.py diff --git a/gepard_ConfigTemplate.cfg b/gepard_ConfigTemplate.cfg index 82ec72d6f89d8a8593d85ab855fb6a98a353cb7a..9e77d08174d2a30851b60b3d32bf1a50c6764bfc 100644 --- a/gepard_ConfigTemplate.cfg +++ b/gepard_ConfigTemplate.cfg @@ -11,6 +11,8 @@ raman_interface = SIMULATED_RAMAN_CONTROL [General Microscope Setup] magnification = 20 +# The scale factor allows downscaling the video image of the microscope camera in order to reduce final image size +videoImageScaleFactor = 1.0 [Renishaw] #information specific for renishaw control diff --git a/gui/opticalscanui.py b/gui/opticalscanui.py index 86dd5ae2db7dbdb890007783e676a823a5c57321..fa535002ee0a43adfa9f5f98b19e11c07a42c7c5 100644 --- a/gui/opticalscanui.py +++ b/gui/opticalscanui.py @@ -29,19 +29,20 @@ 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 ..imagestitch import imageStacking +from ..helperfunctions import cv2imread_fix, cv2imwrite_fix, positionWidgetOnScreen +from ..opticalbackground import BackGroundManager from ..scenePyramid import ScenePyramid from ..gepardlogging import setDefaultLoggingConfig +from ..instrumentcom.instrumentComBase import InstrumentComBase if TYPE_CHECKING: from ..sampleview import SampleView -def scan(path, sol, zpositions, grid, controlclass, dataqueue, +def scan(path, sol, zpositions, grid, instrctrlClass, videoScaleFac: float, dataqueue, stopevent, logpath, ishdr=False): if ishdr: merge_mertens = cv2.createMergeMertens() @@ -55,7 +56,8 @@ def scan(path, sol, zpositions, grid, controlclass, dataqueue, logger.info('starting new optical scan') try: - instrctrl = controlclass(logger) + instrctrl: InstrumentComBase = instrctrlClass(logger) + instrctrl.setVideoScaleFactor(videoScaleFac) instrctrl.updateImageConfig(os.path.dirname(logpath)) instrctrl.connect() zlist = list(enumerate(zpositions)) @@ -707,7 +709,7 @@ class OpticalScanUI(QtWidgets.QWidget): 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.instrctrl.__class__, + self.dataset.grid, self.instrctrl.__class__, self.instrctrl.videoScaleFactor, self.dataqueue, self.processstopevent, logpath, self.hdrcheck.isChecked())) self.process.start() diff --git a/helperfunctions.py b/helperfunctions.py index cabd017cbc99ad041fe2d77ef7df1435bdda45d4..533e27fb519cef22ca416ca8dc6fefc05e52583c 100644 --- a/helperfunctions.py +++ b/helperfunctions.py @@ -22,7 +22,6 @@ If not, see . import numpy as np import cv2 import os -import sys from PyQt5 import QtWidgets, QtCore try: @@ -32,8 +31,10 @@ except ImportError: skimread = None skimsave = None -from .instrumentcom.instrumentComBase import InstrumentComBase from logging import Logger +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from .instrumentcom.instrumentComBase import InstrumentComBase def cv2imread_fix(fname, flags=cv2.IMREAD_COLOR): @@ -105,11 +106,12 @@ def polygoncovering(boundary, wx, wy): return poslist -def getInstrumentControl(controlclass: InstrumentComBase, logger: Logger) -> InstrumentComBase: +def getInstrumentControl(controlclass: 'InstrumentComBase', logger: Logger) -> 'InstrumentComBase': simulatedInterface: bool = False if 'simulatedInterface' in controlclass.__dict__.keys(): if controlclass.__dict__['simulatedInterface'] == True: simulatedInterface = True + if simulatedInterface: instrctrl = controlclass(logger, ui=False) else: @@ -131,7 +133,7 @@ def needsFTIRAperture(sampleview) -> bool: return needsAperture -def lightModeSwitchNeeded(instrctrl: InstrumentComBase) -> bool: +def lightModeSwitchNeeded(instrctrl: 'InstrumentComBase') -> bool: switchNeeded: bool = False if instrctrl.name == 'RenishawCOM': instrctrl.connect() diff --git a/instrumentcom/WITecCOM.py b/instrumentcom/WITecCOM.py index a588640bb9a28cfb7e0ab6889393015afb173518..e922c74c4d7a1aad45ab90a878eda75075e814b6 100644 --- a/instrumentcom/WITecCOM.py +++ b/instrumentcom/WITecCOM.py @@ -380,6 +380,7 @@ class WITecCOM(InstrumentComBase): self.ImageNameMan.SetValue(fname) self.ImageSaveMan.OperateTrigger() sleep(.1) + self._scaleVideoImage(fname) @comErrorRepeater def getImageDimensions(self, mode = 'df'): diff --git a/instrumentcom/instrumentComBase.py b/instrumentcom/instrumentComBase.py index 4d168bd7c22f773dc654b5791973e093eb366897..0b43a4f3cd5c9ca9b533d87c34413dc7e8f23bd8 100644 --- a/instrumentcom/instrumentComBase.py +++ b/instrumentcom/instrumentComBase.py @@ -18,15 +18,32 @@ You should have received a copy of the GNU General Public License along with this program, see COPYING. If not, see . """ +import cv2 +from ..helperfunctions import cv2imread_fix, cv2imwrite_fix class InstrumentComBase(object): + videoScaleFactor: float = 1.0 + + @classmethod + def setVideoScaleFactor(cls, newFac) -> None: + cls.videoScaleFactor = float(newFac) + def __init__(self, logger=None): self.name = None self.connected = False self.timeseries = False self.logger = logger - + + def _scaleVideoImage(self, imagePath: str) -> None: + """ + For convenience.. Takes the image at the indicated path and scales it to the videoScaleFactor. + """ + img = cv2imread_fix(imagePath) + img = cv2.resize(img, None, fx=self.videoScaleFactor, fy=self.videoScaleFactor) + img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + cv2imwrite_fix(imagePath, img) + def getRamanPositionShift(self): """ Compute the shift between laser spot and image center""" raise NotImplementedError @@ -60,10 +77,6 @@ class InstrumentComBase(object): """ Get the image width and height in um and the orientation angle in degrees. """ raise NotImplementedError - - # IT IS NOT NEEDED, ISN'T IT?? - # def startSinglePointScan(self): - # raise NotImplementedError def initiateMeasurement(self, specScanSettings): raise NotImplementedError diff --git a/instrumentcom/instrumentConfig.py b/instrumentcom/instrumentConfig.py index 8dca512826a19e85f9fcc9ce2d8d8d958693b321..0bd140a4cc57581d47afb1bee43003d98b1ea5d8 100644 --- a/instrumentcom/instrumentConfig.py +++ b/instrumentcom/instrumentConfig.py @@ -29,6 +29,7 @@ config = configparser.ConfigParser() config.read(os.path.join(defaultPath, 'gepard.cfg')) interface = "SIMULATED_RAMAN_CONTROL" +videoImgScale = 1.0 class SpecScanParameter(object): @@ -63,7 +64,6 @@ class SpecScanParameter(object): try: defaultPath = config["Defaults"]["file_path"] - print('default Path is now:', defaultPath) except KeyError: pass @@ -72,6 +72,12 @@ try: except KeyError: pass +try: + videoImgScale = config["General Microscope Setup"]["videoImageScaleFactor"] +except KeyError: + pass + + if interface == "SIMULATED_RAMAN_CONTROL": from .simulated.simulatedraman import SimulatedRaman InstrumentControl = SimulatedRaman @@ -95,7 +101,6 @@ elif interface == "RENISHAW_CONTROL": InstrumentControl.cam_df_dims = [float(df_dims[0]), float(df_dims[1])] except: print('Invalid image dimensions in config file!') -# InstrumentControl.defaultMeasTemplate = config["Renishaw"]["defaultMeasTemplate"] InstrumentControl.measTemplatePath = config["Renishaw"]["measTemplatePath"] print(InstrumentControl.measTemplatePath) InstrumentControl.specScanParameters = InstrumentControl.updateSpecScanParameters(InstrumentControl, InstrumentControl.measTemplatePath) @@ -106,3 +111,5 @@ elif interface == "THERMO_FTIR": from .thermoFTIRCom import ThermoFTIRCom InstrumentControl = ThermoFTIRCom simulatedRaman = False + +InstrumentControl.setVideoScaleFactor(videoImgScale) diff --git a/instrumentcom/simulated/imageGenerator.py b/instrumentcom/simulated/imageGenerator.py index 419c055ee3e4b033dca348175aac94413b0370d7..869c1aa88f4c3a07910fdc4132d5ffc286262646 100644 --- a/instrumentcom/simulated/imageGenerator.py +++ b/instrumentcom/simulated/imageGenerator.py @@ -122,12 +122,8 @@ class FakeParticle: class FakeCamera: # TODO: Implement a small angle to simulate a tilted camera! def __init__(self): - self.imgDims: Tuple[int, int] = (500, 250) # width, height of camera imgage + self.imgDims: Tuple[int, int] = (600, 350) # width, height of camera imgage in pixel self.pixelscale: float = 1.0 # µm/px - self.sizeScale: float = 100.0 # smaller values make particles rendered smaller and vice versa - self.threshold: float = 0.7 # threshold for determining particles. Larger values -> smaller particles - self.numZLevels: int = 7 # number of z-levels cached for faking depth of field - self.maxZDiff: float = 100 # max difference in z that is simulated. self.currentImage: np.ndarray = np.zeros((self.imgDims[1], self.imgDims[0], 3)) self.fakeFilter: FakeFilter = FakeFilter() diff --git a/instrumentcom/simulated/simulatedStage.py b/instrumentcom/simulated/simulatedStage.py index 39562d7f3efa2d9415f3d07ae365fb5f7a8287df..8e83684f6fab801aac4477c2005089bf57355d6a 100644 --- a/instrumentcom/simulated/simulatedStage.py +++ b/instrumentcom/simulated/simulatedStage.py @@ -26,7 +26,7 @@ import cv2 import json import numpy as np from typing import List -from .imageGenerator import FakeCamera +from .imageGenerator import FakeCamera, FakeFilter from ..instrumentComBase import InstrumentComBase from ...helperfunctions import getAppFolder @@ -266,23 +266,23 @@ class SimulatedStageUI(QtWidgets.QWidget): self.stageParent.moveToPosition(newPos[0], newPos[1], newPos[2]) - def _moveToPresetPosition(self, btn: QtWidgets.QPushButton, stepSize: float = 1000) -> None: + def _moveToPresetPosition(self, btn: QtWidgets.QPushButton) -> None: label: str = btn.text() + filter: FakeFilter = self.stageParent.camera.fakeFilter + margin: float = 0.9 newPos: List[float] = [0.0, 0.0, 0.0] if label == 'UpperLeft': - newPos[0] -= stepSize - newPos[1] += stepSize + newPos[0] = filter.xRange[0] * margin + newPos[1] = filter.yRange[1] * margin elif label == 'UpperRight': - newPos[0] += stepSize - newPos[1] += stepSize + newPos[0] = filter.xRange[1] * margin + newPos[1] = filter.yRange[1] * margin elif label == 'LowerLeft': - newPos[0] -= stepSize - newPos[1] -= stepSize + newPos[0] = filter.xRange[0] * margin + newPos[1] = filter.yRange[0] * margin elif label == 'LowerRight': - newPos[0] += stepSize - newPos[1] -= stepSize - elif label == 'Center': - pass + newPos[0] = filter.xRange[1] * margin + newPos[1] = filter.yRange[0] * margin self.stageParent.moveToPosition(newPos[0], newPos[1], newPos[2]) diff --git a/instrumentcom/simulated/simulatedraman.py b/instrumentcom/simulated/simulatedraman.py index 3f58101ab59e81a3da2a7c16d2c86bc58f6b5fbe..4f79730e24df40b6097bb55da3314af8d9434975 100644 --- a/instrumentcom/simulated/simulatedraman.py +++ b/instrumentcom/simulated/simulatedraman.py @@ -43,7 +43,6 @@ class SimulatedRaman(InstrumentComBase): self.znum = 4 self.gridnum = 36 self.positionindex = 0 - self.imageindex = 0 def getRamanPositionShift(self): return 0., 0. @@ -51,7 +50,6 @@ class SimulatedRaman(InstrumentComBase): def connect(self): self.stage.connect() self.connected = True - self.imageindex = 0 return True def disconnect(self): @@ -84,7 +82,7 @@ class SimulatedRaman(InstrumentComBase): def saveImage(self, fname): assert self.connected cv2imwrite_fix(fname, self.stage.getCurrentCameraImage()) - self.imageindex = (self.imageindex+1) % (self.znum*self.gridnum) + self._scaleVideoImage(fname) def getImageDimensions(self, mode='df'): """ @@ -92,7 +90,9 @@ class SimulatedRaman(InstrumentComBase): """ assert self.connected camDims: tuple = self.stage.camera.imgDims - return camDims[0], camDims[1], 0 # TODO: RE-Implement a small angle to simulate a tilted camera! + # TODO: RE-Implement a small angle to simulate a tilted camera! + angle: float = 0.0 + return camDims[0], camDims[1], angle def startSinglePointScan(self): assert self.connected diff --git a/unittests/test_gepard.py b/unittests/test_gepard.py index 32734a3aaa7a29edbcedd8491aa332170feea0b7..1aa0bdbc051b8cc8cf93543a190a64705dd524fe 100644 --- a/unittests/test_gepard.py +++ b/unittests/test_gepard.py @@ -12,20 +12,18 @@ from typing import TYPE_CHECKING from ..sampleview import SampleView from ..dataset import DataSet from ..analysis.particleContainer import ParticleContainer -from ..analysis.particleAndMeasurement import Measurement from ..instrumentcom.instrumentComBase import InstrumentComBase -from ..instrumentcom.instrumentConfig import defaultPath +from ..instrumentcom.simulated.simulatedStage import SimulatedStage from ..gui.opticalscanui import OpticalScanUI, PointCoordinates from ..gui.detectionview import ParticleDetectionView, ImageView from ..gui.specscanui import SpecScanUI -from ..gui.viewItems.detectItems import SeedPoint from ..gui.viewItemHandler import ViewItemHandler from ..workmodes import ModeHandler, ModeOpticalScan, ModeParticleDetection, ModeSpectrumScan + if TYPE_CHECKING: from ..__main__ import GEPARDMainWindow - def testGepard(gepard): gepard.testCase = TestGepard(gepard) gepard.testCase.start_automated_test() @@ -67,17 +65,19 @@ class TestGepard(unittest.TestCase): # SET UP OPTICAL SCAN moveMargin: int = 300 - curPos: tuple = self.instrctrl.getPosition() self.assertEqual(type(self.modeHandler.activeMode), ModeOpticalScan) oscanWidget: OpticalScanUI = self.modeHandler.oscanMode.widget self.assertEqual(type(oscanWidget), OpticalScanUI) self.assertTrue(oscanWidget.isVisible()) oscanWidget.zLevelSetter.numLevelsSpinbox.setValue(3) + simulatedStage: SimulatedStage = self.sampleview.instrctrl.stage + simulatedStage.moveToPosition(0, 0, 0) + points: PointCoordinates = oscanWidget.points points.read(0) - self.instrctrl.moveToAbsolutePosition(curPos[0] + moveMargin, curPos[1] + moveMargin, curPos[2]) + self.instrctrl.moveToAbsolutePosition(moveMargin, moveMargin, 0) points.read(1) - self.instrctrl.moveToAbsolutePosition(curPos[0] - moveMargin, curPos[1] - moveMargin, curPos[2]) + self.instrctrl.moveToAbsolutePosition(-moveMargin, -moveMargin, 0) points.read(2) oscanWidget.pareaselect.click()