From 31eb4bb97e831bdf4e4481e8d4c562bdf82886ad Mon Sep 17 00:00:00 2001 From: Josef Brandt Date: Mon, 31 Aug 2020 09:35:56 +0200 Subject: [PATCH] FakeFilter stores generated particles on disk plus some bugfixes --- ramancom/simulated/imageGenerator.py | 72 +++++++++++++++-- ramancom/simulated/simulatedStage.py | 1 + ramancom/simulated/simulatedraman.py | 2 +- ramancom/specHandler.py | 22 +++--- sampleview.py | 7 +- unittests/test_simulatedStage.py | 78 ++++++++++++++++++- ..._specHandler.py => test_spectraHandler.py} | 8 +- workmodes.py | 3 +- 8 files changed, 162 insertions(+), 31 deletions(-) rename unittests/{test_specHandler.py => test_spectraHandler.py} (92%) diff --git a/ramancom/simulated/imageGenerator.py b/ramancom/simulated/imageGenerator.py index 2ed8b84..0473495 100644 --- a/ramancom/simulated/imageGenerator.py +++ b/ramancom/simulated/imageGenerator.py @@ -20,10 +20,14 @@ If not, see . Image Generator for fake images for testing with simulated instrument interfaces """ +import os +import sys +from PyQt5 import QtWidgets, QtCore import cv2 import numpy as np import noise from copy import deepcopy +import pickle from typing import Tuple, List @@ -127,6 +131,11 @@ class FakeCamera: self.currentImage: np.ndarray = np.zeros((self.imgDims[1], self.imgDims[0], 3)) self.fakeFilter: FakeFilter = FakeFilter() + def close(self) -> None: + self.fakeFilter.saveToFile() + if self.fakeFilter.app is not None: + self.fakeFilter.app.deleteLater() + def updateImageAtPosition(self, position: List[float]) -> None: """ Centers the camera at the given µm coordinates and returns the camera image centered at this position. @@ -184,19 +193,66 @@ class FakeCamera: class FakeFilter: - numIndParticles: int = 1 # number of individual particles presets to generate - numBlurSteps: int = 1 # number of blur steps to simulate (additionally to image without any blur) - maxBlurSize: int = 51 # highest blur radius - baseImageSize: int = 10 - def __init__(self): self.numParticles: int = 500 self.xRange: Tuple[float, float] = (-3000, 3000) # min and max of x Dimensions (in µm) self.yRange: Tuple[float, float] = (-3000, 3000) # min and max of x Dimensions (in µm) self.particleSizeRange: Tuple[float, float] = (5, 50) # min and max of particle radius (in µm) - self.presetParticles: List[dict] = self._generagePresetParticles() + self.numIndParticles: int = 10 # number of individual particles presets to generate + self.numBlurSteps: int = 7 # number of blur steps to simulate (additionally to image without any blur) + self.maxBlurSize: int = 51 # highest blur radius + self.baseImageSize: int = 100 + self.presetParticles: List[dict] = [] self.particles: List[FakeParticle] = [] - self._generateParticles() + self.filePath: str = '' + self.app: QtWidgets.QApplication = None + self._updateFromFile() + + def _updateFromFile(self) -> None: + def generateNew() -> None: + self.presetParticles = self._generatePresetParticles() + self._generateParticles() + + path: str = self._getFilePath() + if not os.path.exists(path): + generateNew() + else: + with open(path, "rb") as fp: + savedFilter: FakeFilter = pickle.load(fp) + if self._isDifferentTo(savedFilter): + generateNew() + else: + self.particles = savedFilter.particles + self.presetParticles = savedFilter.presetParticles + + def _isDifferentTo(self, otherFilter) -> bool: + isDifferent: bool = False + otherFilter: FakeFilter = otherFilter + for key in otherFilter.__dict__.keys(): + if key not in ['app', 'presetParticles', 'particles']: + if otherFilter.__dict__[key] != self.__dict__[key]: + isDifferent = True + break + + return isDifferent + + def saveToFile(self) -> None: + fpath: str = self._getFilePath() + # QApplication cannot be pickled, so we replace it temporarily with None + origApp = self.app + self.app = None + with open(fpath, "wb") as fp: + pickle.dump(self, fp, protocol=-1) + self.app = origApp + + def _getFilePath(self) -> str: + if self.filePath == '': + if self.app is None: + self.app = QtWidgets.QApplication(sys.argv) # has to be an instance attribute :/ + self.app.setApplicationName("GEPARD") + path: str = QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.AppLocalDataLocation) + self.filePath = os.path.join(path, 'fakeFilter.pkl') + return self.filePath def getParticleImage(self, particle: FakeParticle, blurRadius: int = 0) -> np.ndarray: def getSizeCorrectedBlurRadius(blurRad: int, scaling: float) -> int: @@ -219,7 +275,7 @@ class FakeFilter: return cv2.resize(img, None, fx=scaleFac, fy=scaleFac) - def _generagePresetParticles(self) -> List[dict]: + def _generatePresetParticles(self) -> List[dict]: """ The list contains a dict for each particle variation, containing the image in different blur stages. The key of that dicts is the used blur radius diff --git a/ramancom/simulated/simulatedStage.py b/ramancom/simulated/simulatedStage.py index 0b39d34..a6fdd88 100644 --- a/ramancom/simulated/simulatedStage.py +++ b/ramancom/simulated/simulatedStage.py @@ -67,6 +67,7 @@ class SimulatedStage(object): self.saveConfigToFile() if self.uiEnabled: self.ui.setEnabled(False) + self.camera.close() def updateConfigFromFile(self) -> None: if os.path.exists(self.filepath): diff --git a/ramancom/simulated/simulatedraman.py b/ramancom/simulated/simulatedraman.py index 5acf4ce..d9efdb0 100644 --- a/ramancom/simulated/simulatedraman.py +++ b/ramancom/simulated/simulatedraman.py @@ -34,7 +34,7 @@ class SimulatedRaman(RamanBase): ramanParameters = {} simulatedInterface = True - def __init__(self, logger, ui: bool = True): + def __init__(self, logger, ui: bool = False): super().__init__() self.name = 'SimulatedRaman' self.logger = logger diff --git a/ramancom/specHandler.py b/ramancom/specHandler.py index 561f596..4864c45 100644 --- a/ramancom/specHandler.py +++ b/ramancom/specHandler.py @@ -160,19 +160,18 @@ class SpectraHandler(object): 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 + if self.wavenumbers is None: + self.wavenumbers = spec[:, 0] + np.save(self.getPathForWavenumbers(), self.wavenumbers) + + 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]) + for i in range(corrSpec.shape[0]): + corrSpec[i, 1] = getIntensityClosestToWavenumber(corrSpec[i, 0]) - spec = corrSpec + spec = corrSpec return spec @@ -205,7 +204,6 @@ class SpectraHandler(object): 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') diff --git a/sampleview.py b/sampleview.py index 1b5b6fa..4646563 100644 --- a/sampleview.py +++ b/sampleview.py @@ -56,8 +56,11 @@ class SampleView(QtWidgets.QGraphicsView): self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse) self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorViewCenter) - self.ramanctrl = RamanControl(self.logger) - self.simulatedRaman = simulatedRaman + self.simulatedRaman = simulatedRaman + if simulatedRaman: + self.ramanctrl = RamanControl(self.logger, ui=True) + else: + self.ramanctrl = RamanControl(self.logger) self.ramanSwitchNeeded = False if self.ramanctrl.name == 'RenishawCOM': #determine, if ramanSwitch is needed: diff --git a/unittests/test_simulatedStage.py b/unittests/test_simulatedStage.py index 5bbeb3e..3a693d6 100644 --- a/unittests/test_simulatedStage.py +++ b/unittests/test_simulatedStage.py @@ -1,21 +1,23 @@ from unittest import TestCase +from unittest.mock import MagicMock import os import json import numpy as np +import tempfile from typing import Tuple +from copy import deepcopy from ..ramancom.simulated.simulatedStage import SimulatedStage from ..ramancom.simulated.imageGenerator import FakeCamera, FakeFilter, FakeParticle, OverlapRange, getParticleOverlapRanges class TestSimulatedStage(TestCase): def setUp(self) -> None: - FakeFilter.numBlurSteps = 1 - FakeFilter.numIndParticles = 1 - FakeFilter.maxBlurSize = 1 - FakeFilter.baseImageSize = 10 self.simulatedStage: SimulatedStage = SimulatedStage(ui=False) self.simulatedStage.saveConfigToFile() + def tearDown(self) -> None: + self.simulatedStage.disconnect() + def test_config_was_saved(self): self.assertTrue(os.path.exists(self.simulatedStage.filepath)) with open(self.simulatedStage.filepath, 'r') as fp: @@ -129,3 +131,71 @@ class Test_FakeCamera(TestCase): self.assertEqual(ranges.subEndY, 50) verifyShapeConsistency(fullImg, subImg, ranges) + +class TestFakeFilter(TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.tmpDir: tempfile.TemporaryDirectory = tempfile.TemporaryDirectory() + + def setUp(self) -> None: + self.fakeFilter: FakeFilter = FakeFilter() + self.fakeFilter.filePath = os.path.join(self.tmpDir.name, 'fakeFilter.pkl') + + def tearDown(self) -> None: + if self.fakeFilter.app is not None: + self.fakeFilter.app.deleteLater() + + @classmethod + def tearDownClass(cls) -> None: + cls.tmpDir.cleanup() + + def test_saveConfig(self): + filterPath: str = self.fakeFilter._getFilePath() + self.fakeFilter.saveToFile() + self.assertTrue(os.path.exists(filterPath)) + + def test_updateFromFile(self): + self.fakeFilter.app = None + origFilter: FakeFilter = deepcopy(self.fakeFilter) + origParticleList: list = [1, 2, 3] + newParticleList: list = [4, 5, 6] + self.fakeFilter.particles = origParticleList + self.fakeFilter.saveToFile() + self.fakeFilter._generateParticles = MagicMock() + self.fakeFilter._generatePresetParticles = MagicMock() + + self.fakeFilter.particles = newParticleList + self.fakeFilter._updateFromFile() + self.fakeFilter._generateParticles.assert_not_called() + self.fakeFilter._generatePresetParticles.assert_not_called() + self.assertEqual(self.fakeFilter.particles, origParticleList) # i.e., the particle List from the saved filte was loaded + + self.fakeFilter.numBlurSteps += 1 + self.fakeFilter._updateFromFile() + self.fakeFilter._generateParticles.assert_called_once() + self.fakeFilter._generatePresetParticles.assert_called_once() + + origFilter.saveToFile() + + def test_isDifferentTo(self): + otherFilter: FakeFilter = FakeFilter() + otherFilter.filePath = os.path.join(self.tmpDir.name, 'fakeFilter.pkl') + self.assertFalse(self.fakeFilter._isDifferentTo(otherFilter)) + + otherFilter.numBlurSteps += 1 + self.assertTrue(self.fakeFilter._isDifferentTo(otherFilter)) + otherFilter.numBlurSteps -= 1 + + otherFilter.numIndParticles += 1 + self.assertTrue(self.fakeFilter._isDifferentTo(otherFilter)) + otherFilter.numIndParticles -= 1 + + otherFilter.numParticles += 1 + self.assertTrue(self.fakeFilter._isDifferentTo(otherFilter)) + otherFilter.numParticles -= 1 + + otherFilter.maxBlurSize += 1 + self.assertTrue(self.fakeFilter._isDifferentTo(otherFilter)) + otherFilter.maxBlurSize -= 1 + + self.assertFalse(self.fakeFilter._isDifferentTo(otherFilter)) diff --git a/unittests/test_specHandler.py b/unittests/test_spectraHandler.py similarity index 92% rename from unittests/test_specHandler.py rename to unittests/test_spectraHandler.py index e711026..75dad9d 100644 --- a/unittests/test_specHandler.py +++ b/unittests/test_spectraHandler.py @@ -3,14 +3,15 @@ import logging import numpy as np import os import shutil -from ramancom.thermoFTIRCom import ThermoFTIRCom -from ramancom.specHandler import SpectraHandler +from ..ramancom.simulated.simulatedraman import SimulatedRaman +from ..ramancom.specHandler import SpectraHandler class TestSpecHandler(unittest.TestCase): def setUp(self) -> None: logger = logging.getLogger('TestLogger') - self.specHandler: SpectraHandler = SpectraHandler(ThermoFTIRCom(logger)) + self.simRaman: SimulatedRaman = SimulatedRaman(logger) + self.specHandler: SpectraHandler = SpectraHandler(self.simRaman) self.specHandler.setDatasetPath(os.getcwd()) self.assertEqual(self.specHandler.dsetpath, os.getcwd()) specPath: str = os.path.join(os.getcwd(), 'spectra') @@ -19,6 +20,7 @@ class TestSpecHandler(unittest.TestCase): def tearDown(self) -> None: shutil.rmtree(self.specHandler.tmpspecpath) + self.simRaman.disconnect() def test_map_spectra(self): wavenumbers: np.ndarray = np.arange(100) diff --git a/workmodes.py b/workmodes.py index a9fd5e7..cbce41c 100644 --- a/workmodes.py +++ b/workmodes.py @@ -142,7 +142,8 @@ class ModeHandler(object): def closeAll(self) -> None: for mode in [self.oscanMode, self.detectMode, self.specScanMode]: - mode.widget.close() + if mode.widget is not None: + mode.widget.close() def mousePressed(self, mouseEvent: QtGui.QMouseEvent) -> bool: """ -- GitLab