diff --git a/__init__.py b/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/gepard.py b/__main__.py
similarity index 97%
rename from gepard.py
rename to __main__.py
index a53e9ac1874476c5d4b6b0f41206187223993ee0..774c5fece8d63561453704ac406cbbde04fc8a75 100644
--- a/gepard.py
+++ b/__main__.py
@@ -18,13 +18,13 @@ 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, QtGui
-from sampleview import SampleView
-from scalebar import ScaleBar
-from ramancom.ramancontrol import defaultPath
-from ramancom.ramanSwitch import RamanSwitch
-from analysis.colorlegend import ColorLegend
import os
+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
class GEPARDMainWindow(QtWidgets.QMainWindow):
def __init__(self, logpath):
@@ -37,7 +37,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.view.imparent = self
self.view.ScalingChanged.connect(self.scalingChanged)
self.scalebar = ScaleBar(self)
- self.legend = ColorLegend()
+ self.legend = ColorLegend(self)
self.ramanSwitch = RamanSwitch(self)
self.view.ScalingChanged.connect(self.scalebar.updateScale)
@@ -126,7 +126,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
def createActions(self):
fname = os.path.join(os.path.split(__file__)[0],
- os.path.join("data","brand.png"))
+ os.path.join('data', 'brand.png'))
self.aboutAct = QtWidgets.QAction(QtGui.QIcon(fname),
"About Particle Measurment", self)
self.aboutAct.triggered.connect(self.about)
@@ -177,7 +177,7 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.opticalScanAct = QtWidgets.QAction("Optical Scan", self)
self.opticalScanAct.setEnabled(False)
- self.opticalScanAct.setCheckable(True)# self.importWindow.exec()
+ self.opticalScanAct.setCheckable(True)
self.opticalScanAct.triggered.connect(QtCore.pyqtSlot()(lambda :self.view.switchMode("OpticalScan")))
self.detectParticleAct = QtWidgets.QAction("Detect Particles", self)
@@ -360,4 +360,4 @@ if __name__ == '__main__':
gepard.showMaximized()
ret = app.exec_()
if fp is not None:
- fp.close()
\ No newline at end of file
+ fp.close()
diff --git a/analysis/database.py b/analysis/database.py
index 91ad8de5399506332ffbacd6fd2527b26655ee03..0f3989b2691ec1bedff9c560cdc4e03d59cafc4f 100644
--- a/analysis/database.py
+++ b/analysis/database.py
@@ -46,7 +46,7 @@ class DataBaseWindow(QtWidgets.QMainWindow):
self.path = os.path.join(logpath, 'databases')
self.importPath = self.path
if not os.path.exists(self.path):
- os.mkdir(self.path)
+ os.makedirs(self.path)
self.activeDatabase = None
self.activeSpectrum = None
self.activeSpectrumName = None
diff --git a/analysis/particleCharacterization.py b/analysis/particleCharacterization.py
index 9e443322f65919ce6df456ef92d288907f9fc65b..2ac899589c397a8a5d0b3df2516dd1bd86abc96f 100644
--- a/analysis/particleCharacterization.py
+++ b/analysis/particleCharacterization.py
@@ -26,8 +26,8 @@ from copy import deepcopy
from .particleClassification.colorClassification import ColorClassifier
from .particleClassification.shapeClassification import ShapeClassifier
-from segmentation import closeHolesOfSubImage
-from errors import InvalidParticleError
+from ..segmentation import closeHolesOfSubImage
+from ..errors import InvalidParticleError
class ParticleStats(object):
longSize = None
diff --git a/analysis/particleClassification/shapeClassification.py b/analysis/particleClassification/shapeClassification.py
index 46190c5eff23db2be53213191296ceacc21e2766..c036bc85c00d3664f447a288fb6d714b41145ef9 100644
--- a/analysis/particleClassification/shapeClassification.py
+++ b/analysis/particleClassification/shapeClassification.py
@@ -20,7 +20,7 @@ along with this program, see COPYING.
If not, see .
"""
import cv2
-from errors import InvalidParticleError
+from ...errors import InvalidParticleError
class ShapeClassifier(object):
def __init__(self):
diff --git a/analysis/particleContainer.py b/analysis/particleContainer.py
index cb4dfadabd54b4bbe03a4b1873c388837743a2e9..4e6d0acccc07b959c03aa9d2cdd365d7db809bb1 100644
--- a/analysis/particleContainer.py
+++ b/analysis/particleContainer.py
@@ -23,8 +23,8 @@ import operator
import os
from PyQt5 import QtWidgets
-from analysis import importSpectra
-from analysis.particleAndMeasurement import Particle, Measurement
+from . import importSpectra
+from .particleAndMeasurement import Particle, Measurement
class ParticleContainer(object):
diff --git a/analysis/particleEditor.py b/analysis/particleEditor.py
index 46ec15e9454aecd3a6d46e9830930ef41e234a4b..1148b8711da609a601197d91ef38a148289bbd30 100644
--- a/analysis/particleEditor.py
+++ b/analysis/particleEditor.py
@@ -23,8 +23,8 @@ import numpy as np
from PyQt5 import QtWidgets, QtCore
from .particlePainter import ParticlePainter
-import analysis.particleCharacterization as pc
-from errors import NotConnectedContoursError
+from . import particleCharacterization as pc
+from ..errors import NotConnectedContoursError
class ParticleContextMenu(QtWidgets.QMenu):
combineParticlesSignal = QtCore.pyqtSignal(list, str)
diff --git a/dataset.py b/dataset.py
index 12a9c34bc9aac55610c1378f2960786ead1e649e..81546be7b0564925cb80ba162567a44d73415035 100644
--- a/dataset.py
+++ b/dataset.py
@@ -22,10 +22,15 @@ import os
import pickle
import numpy as np
import cv2
-from helperfunctions import cv2imread_fix, cv2imwrite_fix
+import sys
+from .helperfunctions import cv2imread_fix, cv2imwrite_fix
from copy import copy
-from analysis.particleContainer import ParticleContainer
-from legacyConvert import legacyConversion, currentVersion
+from .analysis.particleContainer import ParticleContainer
+from .legacyConvert import legacyConversion, currentVersion
+# for legacy pickle import the old module name dataset must be found
+# (no relative import)
+from . import dataset
+sys.modules['dataset'] = dataset
def loadData(fname):
retds = None
@@ -122,6 +127,9 @@ class DataSet(object):
self.zpositions = [] # z-positions for optical scan
self.heightmap = None
self.zvalimg = None
+ self.coordinatetransform = None # if imported form extern source coordinate system may be rotated
+ self.signx = 1.
+ self.signy = -1.
# parameters specifically for raman scan
self.pshift = None # shift of raman scan position relative to image center
@@ -181,8 +189,15 @@ class DataSet(object):
def getZval(self, pixelpos):
assert self.zvalimg is not None
- zp = self.zvalimg[round(pixelpos[1]), round(pixelpos[0])]
- z0, z1 = self.zpositions.min(), self.zpositions.max()
+ i, j = int(round(pixelpos[1])), int(round(pixelpos[0]))
+ if i>=self.zvalimg.shape[0]:
+ print('error in getZval:', self.zvalimg.shape, i, j)
+ i = self.zvalimg.shape[0]-1
+ if j>=self.zvalimg.shape[1]:
+ print('error in getZval:', self.zvalimg.shape, i, j)
+ j = self.zvalimg.shape[1]-1
+ zp = self.zvalimg[i,j]
+ z0, z1 = self.zpositions[0], self.zpositions[-1]
return zp/255.*(z1-z0) + z0
def mapHeight(self, x, y):
@@ -195,44 +210,59 @@ class DataSet(object):
assert not self.readin
p0 = copy(self.lastpos)
+ if self.coordinatetransform is not None:
+ z = 0. if len(p)<3 else p[2]
+ T, pc = self.coordinatetransform
+ p = (np.dot(np.array([p[0], p[1], z])-pc, T.T))
+
if mode == 'df':
- p0[0] -= self.imagedim_df[0]/2
- p0[1] += self.imagedim_df[1]/2
- return (p[0] - p0[0])/self.pixelscale_df, (p0[1] - p[1])/self.pixelscale_df
+ p0[0] -= self.signx*self.imagedim_df[0]/2
+ p0[1] -= self.signy*self.imagedim_df[1]/2
+ x, y = self.signx*(p[0] - p0[0])/self.pixelscale_df, self.signy*(p[1] - p0[1])/self.pixelscale_df
elif mode == 'bf':
- p0[0] -= self.imagedim_bf[0]/2
- p0[1] += self.imagedim_bf[1]/2
- return (p[0] - p0[0])/self.pixelscale_bf, (p0[1] - p[1])/self.pixelscale_bf
+ p0[0] -= self.signx*self.imagedim_bf[0]/2
+ p0[1] -= self.signy*self.imagedim_bf[1]/2
+ x, y = self.signx*(p[0] - p0[0])/self.pixelscale_bf, self.signy*(p[1] - p0[1])/self.pixelscale_bf
+
else:
- print('mapToPixelMode not understood')
- return
-
- def mapToLength(self, pixelpos, mode='df', force=False):
+ raise ValueError(f'mapToPixel mode: {mode} not understood')
+
+ return x, y
+
+ def mapToLength(self, pixelpos, mode='df', force=False, returnz=False):
if not force:
assert not self.readin
p0 = copy(self.lastpos)
p0[0] += self.coordOffset[0]
p0[1] += self.coordOffset[1]
-
if mode == 'df':
- p0[0] -= self.imagedim_df[0]/2
- p0[1] += self.imagedim_df[1]/2
- return (pixelpos[0]*self.pixelscale_df + p0[0]), (p0[1] - pixelpos[1]*self.pixelscale_df)
+ p0[0] -= self.signx*self.imagedim_df[0]/2
+ p0[1] -= self.signy*self.imagedim_df[1]/2
+ x, y = (self.signx*pixelpos[0]*self.pixelscale_df + p0[0]), (p0[1] + self.signy*pixelpos[1]*self.pixelscale_df)
elif mode == 'bf':
- p0[0] -= self.imagedim_bf[0]/2
- p0[1] += self.imagedim_bf[1]/2
- return (pixelpos[0]*self.pixelscale_bf + p0[0]), (p0[1] - pixelpos[1]*self.pixelscale_bf)
+ p0[0] -= self.signx*self.imagedim_bf[0]/2
+ p0[1] -= self.signy*self.imagedim_bf[1]/2
+ x, y = (self.signx*pixelpos[0]*self.pixelscale_bf + p0[0]), (p0[1] + self.signy*pixelpos[1]*self.pixelscale_bf)
else:
raise ValueError(f'mapToLength mode: {mode} not understood')
-
- def mapToLengthRaman(self, pixelpos, microscopeMode='df', noz=False):
- p0x, p0y = self.mapToLength(pixelpos, mode = microscopeMode)
- x, y = p0x + self.pshift[0], p0y + self.pshift[1]
+
z = None
- if not noz:
+ if (returnz and self.zvalimg is not None) or self.coordinatetransform is not None:
z = self.mapHeight(x, y)
z += self.getZval(pixelpos)
+
+ if self.coordinatetransform is not None:
+ T, pc = self.coordinatetransform
+ x, y, z = (np.dot(np.array([x,y,z]), T) + pc)
+
+ if returnz:
+ return x, y, z
+ return x, y
+
+ def mapToLengthRaman(self, pixelpos, microscopeMode='df', noz=False):
+ p0x, p0y, z = self.mapToLength(pixelpos, mode=microscopeMode, returnz=True)
+ x, y = p0x + self.pshift[0], p0y + self.pshift[1]
return x, y, z
def newProject(self, fname):
diff --git a/detectionview.py b/detectionview.py
index 14bf1f6738e5e1bfbe758f2ca6d4c11ec1e45d2a..cd564f1b8c096fa29f791f10cbdbe7ec772e1a3e 100644
--- a/detectionview.py
+++ b/detectionview.py
@@ -20,7 +20,7 @@ If not, see .
"""
import numpy as np
from PyQt5 import QtCore, QtWidgets, QtGui
-from segmentation import Segmentation
+from .segmentation import Segmentation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
import matplotlib.pyplot as plt
from threading import Thread
diff --git a/helperfunctions.py b/helperfunctions.py
index 38bffa4c1578e3893a903f1a9934d289356d10e0..3447a5a842b6277359b3db1c894dc674c7c16fb0 100644
--- a/helperfunctions.py
+++ b/helperfunctions.py
@@ -23,7 +23,16 @@ import numpy as np
import cv2
import os
+try:
+ from skimage.io import imread as skimread
+ from skimage.io import imsave as skimsave
+except ImportError:
+ skimread = None
+ skimsave = None
+
def cv2imread_fix(fname, flags=cv2.IMREAD_COLOR):
+ if skimread is not None:
+ return skimread(fname, as_gray=(flags==cv2.IMREAD_GRAYSCALE))
with open(fname, "rb") as fp:
cont = fp.read()
img = cv2.imdecode(np.fromstring(cont, dtype=np.uint8), flags)
@@ -31,6 +40,8 @@ def cv2imread_fix(fname, flags=cv2.IMREAD_COLOR):
return None
def cv2imwrite_fix(fname, img, params=None):
+ if skimsave is not None:
+ skimsave(fname, img)
pathname, ext = os.path.splitext(fname)
if params is None:
ret, data = cv2.imencode(ext, img)
diff --git a/legacyConvert.py b/legacyConvert.py
index f8f3c5142c551fd5176f0a6eae45298cf370afa8..b44dc1cb08df6401359465a7ec7516d362e1a687 100644
--- a/legacyConvert.py
+++ b/legacyConvert.py
@@ -23,10 +23,10 @@ import numpy as np
import cv2
import os
-from helperfunctions import cv2imread_fix, cv2imwrite_fix
-from analysis.particleContainer import ParticleContainer
-from analysis import particleCharacterization as pc
-from errors import InvalidParticleError
+from .helperfunctions import cv2imread_fix, cv2imwrite_fix
+from .analysis.particleContainer import ParticleContainer
+from .analysis import particleCharacterization as pc
+from .errors import InvalidParticleError
currentVersion = 4
diff --git a/opticalbackground.py b/opticalbackground.py
index 75d8cafbed2a96ef1199b33feacae8308916374c..47635c0b6c9c952d8032a76a9e19bcd544f2d4db 100644
--- a/opticalbackground.py
+++ b/opticalbackground.py
@@ -24,7 +24,7 @@ import cv2
import numpy as np
import os
-from helperfunctions import cv2imread_fix
+from .helperfunctions import cv2imread_fix
class BackGroundManager(QtWidgets.QWidget):
managerClosed = QtCore.pyqtSignal()
diff --git a/opticalscan.py b/opticalscan.py
index adca3dc5732315aa858cdc2f13a0a74b5aa72086..6ab990f4f28f08a773629847deb092fadd5171fb 100644
--- a/opticalscan.py
+++ b/opticalscan.py
@@ -23,14 +23,14 @@ from PyQt5 import QtCore, QtWidgets
import numpy as np
from multiprocessing import Process, Queue, Event
import queue
-from imagestitch import imageStacking
+from .imagestitch import imageStacking
import os
import cv2
-from helperfunctions import cv2imread_fix, cv2imwrite_fix
+from .helperfunctions import cv2imread_fix, cv2imwrite_fix
from time import time
import datetime
import sys
-from opticalbackground import BackGroundManager
+from .opticalbackground import BackGroundManager
def scan(path, sol, zpositions, grid, controlclass, dataqueue,
stopevent, logpath='', ishdr=False):
@@ -691,4 +691,4 @@ if __name__ == "__main__":
ds = DataSet('Test')
optscan = OpticalScan(SimulatedRaman(), ds)
optscan.show()
- sys.exit(app.exec_())
\ No newline at end of file
+ sys.exit(app.exec_())
diff --git a/ramancom/ramancontrol.py b/ramancom/ramancontrol.py
index 7d0174ec982a7b6c4384663b107fb99060e8ff6d..6aa0424ceb1929aba1d499139ee3935a4ca3afa0 100644
--- a/ramancom/ramancontrol.py
+++ b/ramancom/ramancontrol.py
@@ -43,19 +43,19 @@ except KeyError:
pass
if interface == "SIMULATED_RAMAN_CONTROL":
- from ramancom.simulatedraman import SimulatedRaman
+ from .simulatedraman import SimulatedRaman
RamanControl = SimulatedRaman
print("WARNING: using only simulated raman control!")
simulatedRaman = True
elif interface == "WITEC_CONTROL":
- from ramancom.WITecCOM import WITecCOM
+ from .WITecCOM import WITecCOM
RamanControl = WITecCOM
RamanControl.magn = int(config["General Microscope Setup"]["magnification"]) # not yet implemented in WITecCOM, but would probably be a good idea...
simulatedRaman = False
elif interface == "RENISHAW_CONTROL":
- from ramancom.renishawcom import RenishawCOM
+ from .renishawcom import RenishawCOM
RamanControl = RenishawCOM
RamanControl.magn = int(config["General Microscope Setup"]["magnification"])
try:
diff --git a/ramancom/simulatedraman.py b/ramancom/simulatedraman.py
index 836a0853c8d2459bb5d95d7072df5b361ff7944e..e4f0210b78b0cc753b0d687c76760f9cb4e0bfc3 100644
--- a/ramancom/simulatedraman.py
+++ b/ramancom/simulatedraman.py
@@ -21,10 +21,12 @@ If not, see .
Simualted Raman interface module for testing without actual raman system connected
"""
+import sys
+stdout = sys.stdout
from time import sleep
import numpy as np
from shutil import copyfile
-from ramancom.ramanbase import RamanBase
+from .ramanbase import RamanBase
class SimulatedRaman(RamanBase):
@@ -36,9 +38,9 @@ class SimulatedRaman(RamanBase):
self.currentpos = None, 0., 0.
self.currentZ = 0.
# some plausible data to simulate consecutively changing positions
- self.positionlist = np.array([[ 1526. , -1379.9, -131. ],
- [ 3762.5, -1197.7, -138.1],
- [ 2313.7, -2627.2, -138.1],
+ self.positionlist = np.array([[ -12012, 13716, -1290],
+ [ -11955, -9200, -1279],
+ [ 10978, -9254, -1297],
[ 2704.1, -1788.2, -138.1],
[ 3884. , -2650.8, -138.1]])
self.znum = 4
@@ -78,6 +80,7 @@ class SimulatedRaman(RamanBase):
def moveToAbsolutePosition(self, x, y, z=None, epsxy=0.11, epsz=0.011, debugReturn=False, measurementRunning=False):
assert self.connected
+ print('moving to:', x, y, z, file=stdout)
if z is None:
self.currentpos = x, y, self.currentpos[2]
else:
@@ -118,4 +121,4 @@ class SimulatedRaman(RamanBase):
print("Scan number:", num)
sleep(.1)
if num==self.timeseries-1:
- self.timeseries = False
\ No newline at end of file
+ self.timeseries = False
diff --git a/ramanscanui.py b/ramanscanui.py
index 73337a00487c03c0a2a31e14cfcefc2f4cfbeffd..7b683f80ae95b51670a97169f6a315791103d9fc 100644
--- a/ramanscanui.py
+++ b/ramanscanui.py
@@ -24,7 +24,7 @@ import numpy as np
from multiprocessing import Process, Queue, Event
import queue
from time import time
-from external import tsp
+from .external import tsp
import datetime
import sys
import os
@@ -286,4 +286,4 @@ class RamanScanUI(QtWidgets.QWidget):
self.close()
return
self.timer.start(100.)
-
\ No newline at end of file
+
diff --git a/sampleview.py b/sampleview.py
index bed70f29c3ac8f60aa4e441641933751d19912e5..ff73ff768e34e620cae674ff2f946a5bea67c85b 100644
--- a/sampleview.py
+++ b/sampleview.py
@@ -23,20 +23,18 @@ import numpy as np
import os
import cv2
import time
-
-from dataset import DataSet, loadData
-from ramancom.ramancontrol import RamanControl, simulatedRaman
-from opticalscan import OpticalScan
-from ramanscanui import RamanScanUI
-from detectionview import ParticleDetectionView
-from analysis.analysisview import ParticleAnalysis
-from zeissimporter import ZeissImporter
-from viewitems import FitPosIndicator, Node, Edge, ScanIndicator, RamanScanIndicator, SegmentationContour, ParticleInfo
-from analysis.colorlegend import getColorFromNameWithSeed
-from helperfunctions import polygoncovering, cv2imread_fix
-from ramancom.configRaman import RamanConfigWin
-from analysis.particleEditor import ParticleEditor
-
+from .dataset import DataSet, loadData
+from .ramancom.ramancontrol import RamanControl, simulatedRaman
+from .opticalscan import OpticalScan
+from .ramanscanui import RamanScanUI
+from .detectionview import ParticleDetectionView
+from .analysis.analysisview import ParticleAnalysis
+from .zeissimporter import ZeissImporter
+from .viewitems import FitPosIndicator, Node, Edge, ScanIndicator, RamanScanIndicator, SegmentationContour, ParticleInfo
+from .helperfunctions import polygoncovering, cv2imread_fix
+from .analysis.colorlegend import getColorFromNameWithSeed
+from .analysis.particleEditor import ParticleEditor
+from .ramancom.configRaman import RamanConfigWin
class SampleView(QtWidgets.QGraphicsView):
ScalingChanged = QtCore.pyqtSignal(float)
@@ -178,7 +176,12 @@ class SampleView(QtWidgets.QGraphicsView):
if mode is None:
return
assert mode in ["OpticalScan", "ParticleDetection", "RamanScan", "ParticleAnalysis"]
+ print("switching to mode:", mode, flush=True)
self.oscanwidget.setVisible(False)
+ if self.detectionwidget is not None:
+ self.detectionwidget.close()
+ self.detectionwidget.destroy()
+ self.detectionwidget = None
self.ramanwidget.setVisible(False)
self.mode = mode
self.loadPixmap(self.microscopeMode)
diff --git a/segmentation.py b/segmentation.py
index 0028a93d796fc1d2bd4bea0a7eb8b717de4ba5ba..e003d933682c545e86cda273f7e24d31f53f6610 100644
--- a/segmentation.py
+++ b/segmentation.py
@@ -28,7 +28,7 @@ from skimage.feature import peak_local_max
from skimage.morphology import watershed
from random import random
-from errors import InvalidParticleError
+from .errors import InvalidParticleError
def closeHolesOfSubImage(subimg):
diff --git a/viewitems.py b/viewitems.py
index 716d521ef4e2360c44b9d3ffcdb6caf516c089cd..0a56d92401c4d0ef437c62bd5b8783035db98b6d 100644
--- a/viewitems.py
+++ b/viewitems.py
@@ -18,10 +18,9 @@ You should have received a copy of the GNU General Public License
along with this program, see COPYING.
If not, see .
"""
-#import numpy as np
from PyQt5 import QtCore, QtWidgets, QtGui
-from analysis.particleEditor import ParticleContextMenu
-from analysis.particleCharacterization import getParticleCenterPoint
+from .analysis.particleEditor import ParticleContextMenu
+from .analysis.particleCharacterization import getParticleCenterPoint
class SegmentationContour(QtWidgets.QGraphicsItem):
def __init__(self, viewparent, contourData, pos=(0,0)):
diff --git a/zeissimporter.py b/zeissimporter.py
index 761e697960416c84cf89865457f48e3dc076e459..7b6e42523c255e1ab3293d4a5aed8c8a69d6b280 100644
--- a/zeissimporter.py
+++ b/zeissimporter.py
@@ -1,270 +1,272 @@
-# -*- 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 .
-"""
-import os
-from PyQt5 import QtCore, QtWidgets
-from zeissxml import ZeissHandler, make_parser
-from opticalscan import PointCoordinates
-from helperfunctions import cv2imread_fix, cv2imwrite_fix
-from ramancom.ramancontrol import defaultPath
-from dataset import DataSet
-from scipy.optimize import least_squares
-from itertools import permutations
-import cv2
-import numpy as np
-
-class ZeissImporter(QtWidgets.QDialog):
- def __init__(self, fname, ramanctrl, parent=None):
- super().__init__(parent)
-
- if not ramanctrl.connect() or not self.readImportData(fname):
- msg = QtWidgets.QMessageBox()
- msg.setText('Connection failed! Please enable remote control.')
- msg.exec()
- self.validimport = False
- return
- else:
- self.validimport = True
-
- self.ramanctrl = ramanctrl
-
- vbox = QtWidgets.QVBoxLayout()
- pointgroup = QtWidgets.QGroupBox('Marker coordinates at Raman spot [µm]', self)
- self.points = PointCoordinates(len(self.markers), self.ramanctrl, self,
- names = [m.name for m in self.markers])
- self.points.pimageOnly.setVisible(False)
- pointgroup.setLayout(self.points)
- self.points.readPoint.connect(self.takePoint)
-
- self.pconvert = QtWidgets.QPushButton('Convert', self)
- self.pexit = QtWidgets.QPushButton('Cancel', self)
- self.pconvert.released.connect(self.convert)
- self.pexit.released.connect(self.reject)
- self.pconvert.setEnabled(False)
-
- btnLayout = QtWidgets.QHBoxLayout()
- btnLayout.addStretch()
- btnLayout.addWidget(self.pconvert)
- btnLayout.addWidget(self.pexit)
-
- label = QtWidgets.QLabel("Z-Image blur radius", self)
- self.blurspinbox = QtWidgets.QSpinBox(self)
- self.blurspinbox.setMinimum(3)
- self.blurspinbox.setMaximum(99)
- self.blurspinbox.setSingleStep(2)
- self.blurspinbox.setValue(5)
- blurlayout = QtWidgets.QHBoxLayout()
- blurlayout.addWidget(label)
- blurlayout.addWidget(self.blurspinbox)
- blurlayout.addStretch()
-
- vbox.addWidget(pointgroup)
- vbox.addLayout(blurlayout)
- vbox.addLayout(btnLayout)
- self.setLayout(vbox)
-
- def readImportData(self, fname):
- path = os.path.split(fname)[0]
- self.zmapimgname = os.path.join(path, '3D.tif')
- self.edfimgname = os.path.join(path, 'EDF.tif')
- xmlname = os.path.join(path, '3D.tif_metadata.xml')
-
- errmsges = []
- if not os.path.exists(self.zmapimgname):
- errmsges.append('Depth map image not found: 3D.tif')
- if not os.path.exists(self.edfimgname):
- errmsges.append('EDF image not found: EDF.tif')
- if not os.path.exists(xmlname):
- errmsges.append('XML metadata not found: 3D.tif_metadata.xml')
- else:
- parser = make_parser()
- z = ZeissHandler()
- parser.setContentHandler(z)
- parser.parse(xmlname)
-
- if len(z.markers)<3:
- errmsges.append('Fewer than 3 markers found to adjust coordinates!')
- if None in [z.region.centerx, z.region.centery,
- z.region.width, z.region.height]:
- errmsges.append('Image dimensions incomplete or missing!')
- if None in [z.zrange.z0, z.zrange.zn, z.zrange.dz]:
- errmsges.append('ZStack information missing or incomplete!')
-
- if len(errmsges)>0:
- QtWidgets.QMessageBox.error(self, 'Error!',
- '\n'.join(errmsges),
- QtWidgets.QMessageBox.Ok,
- QtWidgets.QMessageBox.Ok)
- return False
-
- self.region = z.region
- self.zrange = z.zrange
- self.markers = z.markers
- return True
-
- @QtCore.pyqtSlot(float, float, float)
- def takePoint(self, x, y, z):
- points = self.points.getPoints()
- if len(points)>=3:
- self.pconvert.setEnabled(True)
-
- @QtCore.pyqtSlot()
- def convert(self):
- fname = QtWidgets.QFileDialog.getSaveFileName(self,
- 'Create New GEPARD Project', defaultPath, '*.pkl')[0]
- if fname=='':
- return
- dataset = DataSet(fname, newProject=True)
- T, pc, zpc = self.getTransform()
- imgshape, warp_mat = self.convertZimg(dataset, T, pc, zpc)
- self.convertImage(dataset, warp_mat)
- dataset.save()
- self.gepardname = dataset.fname
- self.accept()
-
- def convertImage(self, dataset, warp_mat):
- img = cv2imread_fix(self.edfimgname)
- img = cv2.warpAffine(img, warp_mat, img.shape[:2][::-1])
- cv2imwrite_fix(dataset.getImageName(), img)
-
-
- def convertZimg(self, dataset, T, pc, zpc):
- N = int(round((self.zrange.zn-self.zrange.z0)/self.zrange.dz))
- dataset.zpositions = np.linspace(self.zrange.z0,
- self.zrange.zn, N)-zpc[2]+pc[2]
- zimg = cv2imread_fix(self.zmapimgname, cv2.IMREAD_GRAYSCALE)
- zmdist = zimg.mean()
- zm = zmdist/255.*(self.zrange.zn-self.zrange.z0) + self.zrange.z0
-
- radius = self.blurspinbox.value()
- blur = cv2.GaussianBlur(zimg, (radius, radius), 0)
-
- pshift = self.ramanctrl.getRamanPositionShift()
- dataset.pshift = pshift
- pixelscale = self.region.width/zimg.shape[1]
-
- # use input image as single image aquired in one shot
- dataset.imagedim_df = (self.region.width, self.region.height, 0.0)
- dataset.pixelscale_df = pixelscale
-
- dataset.imagedim_bf = (self.region.width, self.region.height, 0.0)
- dataset.pixelscale_bf = pixelscale
-
- # set image center as reference point in data set (transform from Zeiss)
- p0 = np.dot((np.array([self.region.centerx,
- self.region.centery,zm])-zpc),T)[:2] + pc[:2]
- dataset.readin = False
- dataset.lastpos = p0
- dataset.maxdim = p0 + p0
-
- # pixel triangle for coordinate warping transformation
- srcTri = np.array( [[0, 0], [zimg.shape[1] - 1, 0],
- [0, zimg.shape[0] - 1]] ).astype(np.float32)
- # upper left point (0,0) in Zeiss coordinates:
- z0 = np.array([self.region.centerx - self.region.width/2,
- self.region.centery + self.region.height/2])
- # transform pixel data to Zeiss coordinates
- dstTri = np.array([[p[0]*pixelscale + z0[0],
- z0[1] - p[1]*pixelscale, zm] for p in srcTri]).astype(np.double)-zpc
-
- # transform to Raman coordinates
- dstTri = np.dot(dstTri,T) + pc[np.newaxis,:]
-
- # tilt blur image based on transformend z and adapt zpositions
- x = np.linspace(0,1,blur.shape[1])
- y = np.linspace(0,1,blur.shape[0])
- x, y = np.meshgrid(x,y)
- zmap = x*(dstTri[1,2]-dstTri[0,2]) + y*(dstTri[2,2]-dstTri[0,2]) + \
- (zimg * ((self.zrange.zn-self.zrange.z0)/255.) - \
- zmdist*((self.zrange.zn-self.zrange.z0)/255.))
- zmin, zmax = zmap.min(), zmap.max()
- dataset.zpositions = np.array([zmap.min(), zmap.max()])
- blur = (zmap-zmin)*(255./(zmax-zmin))
- blur[blur>255.] = 255.
- blur = np.uint8(blur)
- # transform triangle back to pixel
- dstTri = np.array([dataset.mapToPixel(p[:2]) for p in dstTri]).astype(np.float32)
-
- warp_mat = cv2.getAffineTransform(srcTri, dstTri)
-
- blur = cv2.warpAffine(blur, warp_mat, zimg.shape[::-1])
- zimgname = dataset.getZvalImageName()
- cv2imwrite_fix(zimgname, blur)
- return zimg.shape, warp_mat
-
- def getTransform(self):
- points = self.points.getPoints()
- pshift = self.ramanctrl.getRamanPositionShift()
- points[:,0] -= pshift[0]
- points[:,1] -= pshift[1]
- zpoints = np.array([m.getPos() for m in self.markers], dtype=np.double)
- pc = points.mean(axis=0)
- zpc = zpoints.mean(axis=0)
-
- points -= pc[np.newaxis,:]
- zpoints -= zpc[np.newaxis,:]
-
- def getRotMat(angles):
- c1, s1 = np.cos(angles[0]), np.sin(angles[0])
- c2, s2 = np.cos(angles[1]), np.sin(angles[1])
- c3, s3 = np.cos(angles[2]), np.sin(angles[2])
- return np.mat([[c1*c3-s1*c2*s3, -c1*s3-s1*c2*c3, s1*s2],
- [s1*c3+c1*c2*s3, -s1*s3+c1*c2*c3, -c1*s2],
- [s1*s3, s2*c3, c2]])
-
- # find the transformation matrix with best fit for small angles in
- # [-45°,45°] for all permutation of markers
- permbest = None
- pointsbest = None
- for perm in permutations(range(points.shape[0])):
- ppoints = points[perm,:]
-
- def err(angles_shift):
- T = getRotMat(angles_shift[:3]).T.A
- return (np.dot(zpoints, T) - angles_shift[np.newaxis,3:] \
- - ppoints).ravel()
-
- angle = np.zeros(3)
- opt = least_squares(err, np.concatenate((angle, np.zeros(3))),
- bounds=(np.array([-np.pi/4]*3+[-np.inf]*3),
- np.array([np.pi/4]*3+[np.inf]*3)),
- method='dogbox')
-
- if permbest is None or \
- permbest.cost>opt.cost:
- print("Current best permutation:", perm, flush=True)
- permbest = opt
- pointsbest = ppoints
-
- optangles = permbest.x[:3]
- shift = permbest.x[3:]
- T = getRotMat(optangles).T.A
- e = (np.dot(zpoints, T)-shift[np.newaxis,:]-pointsbest)
- print("Transformation angles:", optangles, flush=True)
- print("Transformation shift:", shift, flush=True)
- print("Transformation err:", e, flush=True)
- d = np.linalg.norm(e, axis=1)
- if np.any(d>1.):
- QtWidgets.QMessageBox.warning(self, 'Warning!',
- f'Transformation residuals are large:{d}',
- QtWidgets.QMessageBox.Ok,
- QtWidgets.QMessageBox.Ok)
- return T, pc-shift, zpc
-
\ No newline at end of file
+# -*- 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 .
+"""
+import os
+from PyQt5 import QtCore, QtWidgets
+from .zeissxml import ZeissHandler, make_parser
+from .opticalscan import PointCoordinates
+from .helperfunctions import cv2imread_fix, cv2imwrite_fix
+from .ramancom.ramancontrol import defaultPath
+from .dataset import DataSet
+from scipy.optimize import least_squares
+from itertools import permutations
+import cv2
+import numpy as np
+
+class ZeissImporter(QtWidgets.QDialog):
+ def __init__(self, fname, ramanctrl, parent=None):
+ super().__init__(parent)
+
+ if not ramanctrl.connect() or not self.readImportData(fname):
+ msg = QtWidgets.QMessageBox()
+ msg.setText('Connection failed! Please enable remote control.')
+ msg.exec()
+ self.validimport = False
+ return
+ else:
+ self.validimport = True
+
+ self.ramanctrl = ramanctrl
+
+ vbox = QtWidgets.QVBoxLayout()
+ pointgroup = QtWidgets.QGroupBox('Marker coordinates at Raman spot [µm]', self)
+ self.points = PointCoordinates(len(self.markers), self.ramanctrl, self,
+ names = [m.name for m in self.markers])
+ self.points.pimageOnly.setVisible(False)
+ pointgroup.setLayout(self.points)
+ self.points.readPoint.connect(self.takePoint)
+
+ self.pconvert = QtWidgets.QPushButton('Convert', self)
+ self.pexit = QtWidgets.QPushButton('Cancel', self)
+ self.pconvert.released.connect(self.convert)
+ self.pexit.released.connect(self.reject)
+ self.pconvert.setEnabled(False)
+
+ self.xinvert = QtWidgets.QCheckBox('Invert x-axis')
+ self.yinvert = QtWidgets.QCheckBox('Invert y-axis')
+ self.zinvert = QtWidgets.QCheckBox('Invert z-axis')
+
+ btnLayout = QtWidgets.QHBoxLayout()
+ btnLayout.addStretch()
+ btnLayout.addWidget(self.pconvert)
+ btnLayout.addWidget(self.pexit)
+
+ label = QtWidgets.QLabel("Z-Image blur radius", self)
+ self.blurspinbox = QtWidgets.QSpinBox(self)
+ self.blurspinbox.setMinimum(3)
+ self.blurspinbox.setMaximum(99)
+ self.blurspinbox.setSingleStep(2)
+ self.blurspinbox.setValue(5)
+ blurlayout = QtWidgets.QHBoxLayout()
+ blurlayout.addWidget(label)
+ blurlayout.addWidget(self.blurspinbox)
+ blurlayout.addStretch()
+
+ vbox.addWidget(pointgroup)
+ vbox.addLayout(blurlayout)
+ vbox.addWidget(self.xinvert)
+ vbox.addWidget(self.yinvert)
+ vbox.addWidget(self.zinvert)
+ vbox.addLayout(btnLayout)
+ self.setLayout(vbox)
+
+ def readImportData(self, fname):
+ path = os.path.split(fname)[0]
+ self.edfimgname, self.zmapimgname, xmlname = '', '', ''
+ for name in os.listdir(path):
+ if name.lower().endswith('_meta.xml'):
+ xmlname = os.path.join(path, name)
+ elif name.lower().endswith('_c1.tif'):
+ self.edfimgname = os.path.join(path, name)
+ elif name.lower().endswith('_c2.tif'):
+ self.zmapimgname = os.path.join(path, name)
+
+ errmsges = []
+ if not os.path.exists(self.zmapimgname):
+ errmsges.append('Depth map image not found: NAME_c2.tif')
+ if not os.path.exists(self.edfimgname):
+ errmsges.append('EDF image not found: NAME_c1.tif')
+ if not os.path.exists(xmlname):
+ errmsges.append('XML metadata not found: NAME_meta.xml')
+ else:
+ parser = make_parser()
+ z = ZeissHandler()
+ parser.setContentHandler(z)
+ parser.parse(xmlname)
+
+ if len(z.markers)<3:
+ errmsges.append('Fewer than 3 markers found to adjust coordinates!')
+ if None in [z.region.centerx, z.region.centery,
+ z.region.width, z.region.height]:
+ errmsges.append('Image dimensions incomplete or missing!')
+ if None in [z.zrange.z0, z.zrange.zn, z.zrange.dz]:
+ errmsges.append('ZStack information missing or incomplete!')
+
+ if len(errmsges)>0:
+ QtWidgets.QMessageBox.critical(self, 'Error!',
+ '\n'.join(errmsges),
+ QtWidgets.QMessageBox.Ok,
+ QtWidgets.QMessageBox.Ok)
+ return False
+
+ self.region = z.region
+ self.zrange = z.zrange
+ self.markers = z.markers
+ print(self.region)
+ print(self.zrange)
+ print(self.markers, flush=True)
+ return True
+
+ @QtCore.pyqtSlot(float, float, float)
+ def takePoint(self, x, y, z):
+ points = self.points.getPoints()
+ if len(points)>=3:
+ self.pconvert.setEnabled(True)
+
+ @QtCore.pyqtSlot()
+ def convert(self):
+ T, pc, zpc, accept = self.getTransform()
+ if accept:
+ fname = QtWidgets.QFileDialog.getSaveFileName(self,
+ 'Create New GEPARD Project', defaultPath, '*.pkl')[0]
+ if fname=='':
+ return
+ dataset = DataSet(fname, newProject=True)
+ self.convertZimg(dataset, T, pc, zpc)
+ self.convertImage(dataset)
+ dataset.save()
+ self.gepardname = dataset.fname
+ self.accept()
+
+ def convertImage(self, dataset):
+ img = cv2imread_fix(self.edfimgname)
+ cv2imwrite_fix(dataset.getImageName(), img)
+
+ def convertZimg(self, dataset, T, pc, zpc):
+ N = int(round(abs(self.zrange.zn-self.zrange.z0)/self.zrange.dz))
+ z0, zn = self.zrange.z0, self.zrange.zn
+ if zn255.] = 255.
+ blur[np.isnan(blur)] = 0.
+ blur = np.uint8(blur)
+
+ zimgname = dataset.getZvalImageName()
+ cv2imwrite_fix(zimgname, blur)
+ dataset.zvalimg = "saved"
+
+ def getTransform(self):
+ points = self.points.getPoints()
+ pshift = self.ramanctrl.getRamanPositionShift()
+ points[:,0] -= pshift[0]
+ points[:,1] -= pshift[1]
+ Parity = np.mat(np.diag([-1. if self.xinvert.isChecked() else 1.,
+ -1. if self.yinvert.isChecked() else 1.,
+ -1. if self.zinvert.isChecked() else 1.]))
+ zpoints = np.array([m.getPos() for m in self.markers], dtype=np.double)
+ pc = points.mean(axis=0)
+ zpc = zpoints.mean(axis=0)
+
+ points -= pc[np.newaxis,:]
+ zpoints -= zpc[np.newaxis,:]
+
+ def getRotMat(angles):
+ c1, s1 = np.cos(angles[0]), np.sin(angles[0])
+ c2, s2 = np.cos(angles[1]), np.sin(angles[1])
+ c3, s3 = np.cos(angles[2]), np.sin(angles[2])
+ return np.mat([[c1*c3-s1*c2*s3, -c1*s3-s1*c2*c3, s1*s2],
+ [s1*c3+c1*c2*s3, -s1*s3+c1*c2*c3, -c1*s2],
+ [s1*s3, s2*c3, c2]])
+
+ # find the transformation matrix with best fit for small angles in
+ # [-45°,45°] for all permutation of markers
+ permbest = None
+ pointsbest = None
+ ppoints = points[:,:].copy()
+
+ def err(angles_shift):
+ T = (getRotMat(angles_shift[:3]).T*Parity).A
+ return (np.dot(zpoints, T) - angles_shift[np.newaxis,3:] \
+ - ppoints).ravel()
+
+ angle = np.zeros(3)
+ opt = least_squares(err, np.concatenate((angle, np.zeros(3))),
+ bounds=(np.array([-np.pi/4]*3+[-np.inf]*3),
+ np.array([np.pi/4]*3+[np.inf]*3)),
+ method='dogbox')
+ permbest = opt
+ pointsbest = ppoints
+
+ optangles = permbest.x[:3]
+ shift = permbest.x[3:]
+ T = (getRotMat(optangles).T*Parity).A
+ e = (np.dot(zpoints, T)-shift[np.newaxis,:]-pointsbest)
+ print("Transformation angles:", optangles, flush=True)
+ print("Transformation shift:", shift, flush=True)
+ print("Transformation err:", e, flush=True)
+ d = np.linalg.norm(e, axis=1)
+ accept = True
+ if np.any(d>1.):
+ ret = QtWidgets.QMessageBox.warning(self, 'Warning!',
+ f'Transformation residuals are large:{d}',
+ QtWidgets.QMessageBox.Ok|QtWidgets.QMessageBox.Cancel,
+ QtWidgets.QMessageBox.Ok)
+ if ret==QtWidgets.QMessageBox.Cancel:
+ accept = False
+ return T, pc-shift, zpc, accept
+
diff --git a/zeissxml.py b/zeissxml.py
index 2abb80843b1d231fd2123ed44ec801a9d05e7482..6dbe249b855df7faf3529704564d2f13ac9412bd 100644
--- a/zeissxml.py
+++ b/zeissxml.py
@@ -39,13 +39,15 @@ class Region:
def __init__(self):
self.centerx, self.centery = None, None
self.width, self.height = None, None
+ self.scalex, self.scaley = None, None
def __repr__(self):
return str(self)
def __str__(self):
return f'Region center: {self.centerx, self.centery} µm\n' + \
- f'Region size: {self.width, self.height} µm'
+ f'Region size: {self.width, self.height} µm\n' + \
+ f'Scale: {self.scalex, self.scaley} µm/pixel'
class ZRange:
def __init__(self):
@@ -65,6 +67,7 @@ class ZeissHandler(handler.ContentHandler):
self.zrange = ZRange()
self.intag = False
self.subtag = ''
+ self.scaledim = ''
def characters(self, content):
if self.intag:
@@ -75,20 +78,27 @@ class ZeissHandler(handler.ContentHandler):
self.markers.append(Marker(attrs['Id'], attrs['StageXPosition'],
attrs['StageYPosition'],
attrs['FocusPosition']))
- elif name == 'TileRegion' or name == 'ZStackSetup':
+ elif name == 'TileRegion' or name == 'ZStackSetup' or name == 'Scaling':
self.intag = True
if self.intag:
self.content = ''
- if name in ['First','Last','Interval']:
+ if self.subtag == 'Items' and name == 'Distance':
+ self.scaledim = attrs['Id']
+ if name in ['First','Last','Interval','Items']:
self.subtag = name
def endElement(self, name):
- if name == 'TileRegion' or name == 'ZStackSetup':
+ if name == 'TileRegion' or name == 'ZStackSetup' or name == 'Scaling':
self.intag = False
if self.intag and name == 'CenterPosition':
self.region.centerx, self.region.centery = map(float, self.content.split(','))
elif self.intag and name == 'ContourSize':
self.region.width, self.region.height = map(float, self.content.split(','))
+ elif self.intag and self.subtag == 'Items' and name == 'Value':
+ if self.scaledim == 'X':
+ self.region.scalex = float(self.content)
+ elif self.scaledim == 'Y':
+ self.region.scaley = float(self.content)
elif self.intag and name == 'Value' and \
self.subtag in ['First','Last','Interval']:
attrmap = {'First':'z0','Last':'zn','Interval':'dz'}