From b5b8af907270f803068a21ee60e3a5b21f585b98 Mon Sep 17 00:00:00 2001 From: Josef Brandt Date: Wed, 14 Oct 2020 12:55:51 +0200 Subject: [PATCH] Fix Cyclic import in instrumentConfig Plus fixes in unittests --- gui/specscanui.py | 114 +-------------- instrumentcom/WITecCOM.py | 11 +- instrumentcom/instrumentConfig.py | 32 +---- instrumentcom/simulated/simulatedraman.py | 6 +- specScan.py | 166 ++++++++++++++++++++++ unittests/runAllTests.py | 12 +- unittests/test_FTIRCom.py | 46 ------ unittests/test_getShortestPath.py | 6 +- unittests/testhelpers.py | 8 ++ 9 files changed, 196 insertions(+), 205 deletions(-) create mode 100644 specScan.py delete mode 100644 unittests/test_FTIRCom.py diff --git a/gui/specscanui.py b/gui/specscanui.py index 81ae65c..af6d6a4 100644 --- a/gui/specscanui.py +++ b/gui/specscanui.py @@ -24,125 +24,15 @@ import numpy as np from multiprocessing import Process, Queue, Event import queue import os -import logging -import logging.handlers -from typing import TYPE_CHECKING, List -from ..cythonModules import tsp +from typing import TYPE_CHECKING from .uielements import TimeEstimateProgressbar -from ..gepardlogging import setDefaultLoggingConfig +from ..specScan import scan, getShortestPath from ..analysis.particleCharacterization import FTIRAperture -from ..helperfunctions import getInstrumentControl if TYPE_CHECKING: from ..sampleview import SampleView -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 getShortestPath(_points: np.ndarray) -> List[int]: - """ - :param _points: (N, 2) shape array of x, y coordinates - :returns: list of indices of shortest path to visit all points - """ - numPoints: int = _points.shape[0] - shortestPath: List[int] = list(np.arange(numPoints, dtype=np.int)) - if numPoints >= 4: # the tsp needs at least four points (swap of two pairs of points concurrently) - 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 numPoints < 20000: - shortestPath, T = tsp.tspcomp(np.double(_points), np.int32(cmin)) - # assert np.all(np.sort(shortestPath) == np.arange(numPoints), dtype=np.int32) - assert np.array_equal(np.sort(shortestPath), np.arange(numPoints, dtype=np.int32)) - return list(shortestPath) - - -def scan(specScanSettings, 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: - instrctrl = getInstrumentControl(controlclass, logger) - instrctrl.connect() - if instrctrl.name == 'ThermoFTIRCom': - specScanSettings['Apertures'] = positions - - instrctrl.initiateMeasurement(specScanSettings) - logger.info(instrctrl.name) - for i, p in enumerate(positions): - if not instrctrl.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}') - instrctrl.setAperture(width, height, angle) - - logger.info(f"position: {x}, {y}, {z}") - instrctrl.moveToAbsolutePosition(x, y, z) - logger.info("move done") - instrctrl.triggerMeasurement(i) - logger.info("trigger done") - - if stopevent.is_set(): - instrctrl.disconnect() - return - - dataqueue.put(i) - - instrctrl.finishMeasurement() - instrctrl.disconnect() - except: - logger.exception('Fatal error in ramanscan') - from ..errors import showErrorMessageAsWidget - showErrorMessageAsWidget('See ramanscanlog in project directory for information') - - class SpecScanUI(QtWidgets.QWidget): imageUpdate = QtCore.pyqtSignal(str, name='imageUpdate') #str = 'df' (= darkfield) or 'bf' (=bright field) ramanscanUpdate = QtCore.pyqtSignal() diff --git a/instrumentcom/WITecCOM.py b/instrumentcom/WITecCOM.py index e922c74..c9ee29c 100644 --- a/instrumentcom/WITecCOM.py +++ b/instrumentcom/WITecCOM.py @@ -32,8 +32,7 @@ from socket import gethostname try: # when running the witectesting, the paths have to be differently, as it is in the same directory as the other raman com modules from .instrumentComBase import InstrumentComBase - # from .instrumentConfig import SpecScanParameter - from . import instrumentConfig as ic + from ..specScan import SpecScanParameter from ..errors import showErrorMessageAsWidget except ImportError: from instrumentComBase import InstrumentComBase @@ -98,8 +97,8 @@ class WITecCOM(InstrumentComBase): CLSID = "{C45E77CE-3D66-489A-B5E2-159F443BD1AA}" magn = 20 - specScanParameters = [ic.SpecScanParameter('IntegrationTime (s)', 'double', default=0.5, minVal=0.01, maxVal=100), - ic.SpecScanParameter('Accumulations', 'int', default=5, minVal=1, maxVal=100)] + specScanParameters = [SpecScanParameter('IntegrationTime (s)', 'double', default=0.5, minVal=0.01, maxVal=100), + SpecScanParameter('Accumulations', 'int', default=5, minVal=1, maxVal=100)] def __init__(self, logger, hostname=None): super().__init__() @@ -260,8 +259,8 @@ class WITecCOM(InstrumentComBase): self.advSpec: SpectraHandler = SpectraHandler(self) if 'Autofocus' not in [param.name for param in self.specScanParameters]: - self.specScanParameters.append(ic.SpecScanParameter('Autofocus', 'checkBox', default=False)) - self.specScanParameters.append(ic.SpecScanParameter('Spectra Batch Size', 'int', default=1000, minVal=1, maxVal=1e6)) + self.specScanParameters.append(SpecScanParameter('Autofocus', 'checkBox', default=False)) + self.specScanParameters.append(SpecScanParameter('Spectra Batch Size', 'int', default=1000, minVal=1, maxVal=1e6)) @comErrorRepeater def getBrightness(self): diff --git a/instrumentcom/instrumentConfig.py b/instrumentcom/instrumentConfig.py index f99ab1e..4fa4c21 100644 --- a/instrumentcom/instrumentConfig.py +++ b/instrumentcom/instrumentConfig.py @@ -21,7 +21,7 @@ If not, see . import os import configparser -__all__ = ["InstrumentControl", "defaultPath", "simulatedRaman", "SpecScanParameter"] +__all__ = ["InstrumentControl", "defaultPath", "simulatedRaman"] defaultPath = os.path.dirname(os.path.split(__file__)[0]) @@ -32,36 +32,6 @@ interface = "SIMULATED_RAMAN_CONTROL" videoImgScale = 1.0 -class SpecScanParameter(object): - def __init__(self, name, dtype, default=None, minVal=None, maxVal=None, valList=None, openFileType=None): - self.name = name - self.dtype = dtype - self.value = default - self.minVal = minVal - self.maxVal = maxVal - self.valList = valList - self.openFileType = openFileType - - if not self.hasValidType(): - print('erroreneous type in setting parameter:', self.dtype) - - def hasValidType(self): - if self.dtype in ['int', 'double', 'checkBox', 'combobox', 'selectBtn']: - return True - else: - return False - - def value_of(self, obj): - if self.dtype in ['int', 'double']: - return obj.value() - elif self.dtype == 'checkBox': - return obj.isChecked() - elif self.dtype == 'combobox': - return obj.currentText() - elif self.dtype == 'selectBtn': - return obj.text() - - try: defaultPath = config["Defaults"]["file_path"] except KeyError: diff --git a/instrumentcom/simulated/simulatedraman.py b/instrumentcom/simulated/simulatedraman.py index 4f79730..7e58d90 100644 --- a/instrumentcom/simulated/simulatedraman.py +++ b/instrumentcom/simulated/simulatedraman.py @@ -24,14 +24,16 @@ Simualted Raman interface module for testing without actual raman system connect import sys stdout = sys.stdout from time import sleep +from .simulatedStage import SimulatedStage from ..instrumentComBase import InstrumentComBase from ...helperfunctions import cv2imwrite_fix -from .simulatedStage import SimulatedStage +from ...specScan import SpecScanParameter class SimulatedRaman(InstrumentComBase): magn = 20 - specScanParameters = {} + specScanParameters = [SpecScanParameter('IntegrationTime (s)', 'double', default=0.5, minVal=0.01, maxVal=100), + SpecScanParameter('Accumulations', 'int', default=5, minVal=1, maxVal=100)] simulatedInterface = True def __init__(self, logger, ui: bool = False): diff --git a/specScan.py b/specScan.py new file mode 100644 index 0000000..792cc7e --- /dev/null +++ b/specScan.py @@ -0,0 +1,166 @@ +""" +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 . + +Simualted Raman interface module for testing without actual raman system connected +""" +from typing import List +import logging +import logging.handlers +import numpy as np +from .cythonModules import tsp +from .gepardlogging import setDefaultLoggingConfig +from .helperfunctions import getInstrumentControl + + +class SpecScanParameter(object): + def __init__(self, name, dtype, default=None, minVal=None, maxVal=None, valList=None, openFileType=None): + self.name = name + self.dtype = dtype + self.value = default + self.minVal = minVal + self.maxVal = maxVal + self.valList = valList + self.openFileType = openFileType + + if not self.hasValidType(): + print('erroreneous type in setting parameter:', self.dtype) + + def hasValidType(self): + if self.dtype in ['int', 'double', 'checkBox', 'combobox', 'selectBtn']: + return True + else: + return False + + def value_of(self, obj): + if self.dtype in ['int', 'double']: + return obj.value() + elif self.dtype == 'checkBox': + return obj.isChecked() + elif self.dtype == 'combobox': + return obj.currentText() + elif self.dtype == 'selectBtn': + return obj.text() + + +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 getShortestPath(_points: np.ndarray) -> List[int]: + """ + :param _points: (N, 2) shape array of x, y coordinates + :returns: list of indices of shortest path to visit all points + """ + print('SHORTEST PATH HERE') + numPoints: int = _points.shape[0] + shortestPath: List[int] = list(np.arange(numPoints, dtype=np.int)) + if numPoints >= 4: # the tsp needs at least four points (swap of two pairs of points concurrently) + 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 numPoints < 20000: + shortestPath, T = tsp.tspcomp(np.double(_points), np.int32(cmin)) + # assert np.all(np.sort(shortestPath) == np.arange(numPoints), dtype=np.int32) + assert np.array_equal(np.sort(shortestPath), np.arange(numPoints, dtype=np.int32)) + return list(shortestPath) + + +def scan(specScanSettings, 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: + instrctrl = getInstrumentControl(controlclass, logger) + instrctrl.connect() + if instrctrl.name == 'ThermoFTIRCom': + specScanSettings['Apertures'] = positions + + instrctrl.initiateMeasurement(specScanSettings) + logger.info(instrctrl.name) + for i, p in enumerate(positions): + if not instrctrl.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}') + instrctrl.setAperture(width, height, angle) + + logger.info(f"position: {x}, {y}, {z}") + instrctrl.moveToAbsolutePosition(x, y, z) + logger.info("move done") + instrctrl.triggerMeasurement(i) + logger.info("trigger done") + + if stopevent.is_set(): + instrctrl.disconnect() + return + + dataqueue.put(i) + + instrctrl.finishMeasurement() + instrctrl.disconnect() + except: + logger.exception('Fatal error in ramanscan') + from .errors import showErrorMessageAsWidget + showErrorMessageAsWidget('See ramanscanlog in project directory for information') + diff --git a/unittests/runAllTests.py b/unittests/runAllTests.py index 6e60f1f..da71052 100644 --- a/unittests/runAllTests.py +++ b/unittests/runAllTests.py @@ -6,18 +6,20 @@ imports work..). Then they have to be added to the allTests-List direclty below. import unittest import sys -sys.path.append(r'C:\Users\xbrjos\Desktop\Python') +import os +scriptFolder = os.path.realpath(__file__) +gepardParentFolder = os.path.dirname(os.path.dirname(os.path.dirname(scriptFolder))) # three levels up.. +sys.path.append(gepardParentFolder) + from gepard.unittests.test_coordTransform import TestCoordinateTransform -from gepard.unittests.test_FTIRCom import TestFTIRCom from gepard.unittests.test_getShortestPath import TestTSP from gepard.unittests.test_simulatedStage import Test_FakeCamera, TestSimulatedStage -from gepard.unittests.test_specscan import TestSpecScan from gepard.unittests.test_specscanui import TestSpecScanUI from gepard.unittests.test_spectraHandler import TestSpecHandler from gepard.unittests.test_workModes import TestWorkModes -allTests: list = [TestCoordinateTransform, TestFTIRCom, TestTSP, Test_FakeCamera, TestSimulatedStage, - TestSpecScan, TestSpecScanUI, TestSpecHandler, TestWorkModes] +allTests: list = [TestCoordinateTransform, TestTSP, Test_FakeCamera, TestSimulatedStage, + TestSpecScanUI, TestSpecHandler, TestWorkModes] def makeTestSuiteRunnable(suite: unittest.TestSuite): diff --git a/unittests/test_FTIRCom.py b/unittests/test_FTIRCom.py deleted file mode 100644 index ea72171..0000000 --- a/unittests/test_FTIRCom.py +++ /dev/null @@ -1,46 +0,0 @@ -import unittest -import tempfile -import numpy as np -import logging -import os -import csv -from ..instrumentcom.FTIRCom import FTIRCom - - -class TestFTIRCom(unittest.TestCase): - def setUp(self) -> None: - logger = logging.getLogger('TestLogger') - FTIRCom.simulatedInterface = True - self.ftirCom = FTIRCom(logger) - - def test_export_spec_as_csv(self) -> None: - testSpec: np.ndarray = np.zeros((10, 2)) - testSpec[:, 1] = np.arange(10) - with tempfile.TemporaryDirectory() as tmpdir: - fname: str = os.path.join(tmpdir, 'testSpec.csv') - self.ftirCom._export_spec_to_csv(fname, testSpec) - self.assertTrue(os.path.exists(fname)) - with open(fname, 'r') as csvfile: - index: int = 0 - for index, row in enumerate(csv.reader(csvfile, delimiter=';')): - self.assertEqual(len(row), 2) - self.assertEqual(float(row[0]), testSpec[index, 0]) - self.assertEqual(float(row[1]), testSpec[index, 1]) - self.assertEqual(index, testSpec.shape[0]-1) - -# # def test_evaluate_spectra(self) -> None: -# # mockSearch: MagicMock = MagicMock(name='search_library') -# # mockSearch.return_value = {75.0: 'PET', -# # 25.0: 'PTFE', -# # 3.0: 'unknown'} -# # self.ftirCom.dde.search_library = mockSearch -# # -# # mockGetSpec: MagicMock = MagicMock(name='getAllSpectra') -# # mockGetSpec.return_value = np.zeros((10, 2)) -# # self.ftirCom.specHandler.getAllSpectra = mockGetSpec -# # -# # partContainer: ParticleContainer = ParticleContainer() -# # partContainer.addEmptyMeasurement() -# # partContainer.setMeasurementScanIndex(0, 0) -# # -# # self.ftirCom.evaluateSpectra(partContainer) diff --git a/unittests/test_getShortestPath.py b/unittests/test_getShortestPath.py index a55d820..912cff2 100644 --- a/unittests/test_getShortestPath.py +++ b/unittests/test_getShortestPath.py @@ -1,11 +1,11 @@ import unittest import numpy as np -from ..gui.specscanui import getShortestPath +from ..specScan import getShortestPath class TestTSP(unittest.TestCase): - def test_tsp(self): + def test_shortestPath(self): for numPoints in range(10): if numPoints > 0: points: np.ndarray = np.array([[0, i] for i in range(numPoints)], dtype=np.float) @@ -15,4 +15,4 @@ class TestTSP(unittest.TestCase): if optPath[0] > optPath[-1]: optPath = optPath[::-1] - self.assertTrue(np.array_equal(np.array(optPath), np.arange(numPoints, dtype=np.int))) \ No newline at end of file + self.assertTrue(np.array_equal(np.array(optPath), np.arange(numPoints, dtype=np.int))) diff --git a/unittests/testhelpers.py b/unittests/testhelpers.py index 935f9cc..2649371 100644 --- a/unittests/testhelpers.py +++ b/unittests/testhelpers.py @@ -1,6 +1,9 @@ import logging +from typing import TYPE_CHECKING from ..dataset import DataSet from ..__main__ import GEPARDMainWindow +if TYPE_CHECKING: + from ..sampleview import SampleView def getDefaultLogger() -> logging.Logger: @@ -23,3 +26,8 @@ def getDefaultMainWin() -> GEPARDMainWindow: gepard.view.pyramid.fromDataset(gepard.view.dataset) gepard.view._setUpToDataset() return gepard + + +def getDefaultSampleview() -> 'SampleView': + gepard: GEPARDMainWindow = getDefaultMainWin() + return gepard.view -- GitLab