Commit 69c1bd38 authored by Josef Brandt's avatar Josef Brandt

Export FTIR Apertures as .slf file

parent 31d83caf
......@@ -308,6 +308,9 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if self.view.simulatedRaman:
self.exportSLFAct: QtWidgets.QAction = QtWidgets.QAction("&Export FTIR Apertures to .slf")
self.testAct: QtWidgets.QAction = QtWidgets.QAction("&Run Automated Gepard Test")
......@@ -357,6 +360,8 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.toolsMenu = QtWidgets.QMenu("&Tools")
self.dispMenu = QtWidgets.QMenu("&Display", self)
......@@ -22,7 +22,7 @@ If not, see <>.
import numpy as np
from scipy import optimize
import cv2
# from ..cythonModules.getMaxRect import findMaxRect # TODO: UNCOMMENT!!!
from ..cythonModules.getMaxRect import findMaxRect # TODO: UNCOMMENT!!!
......@@ -23,6 +23,7 @@ If not, see <>.
import numpy as np
import cv2
from copy import deepcopy
from typing import TYPE_CHECKING
from .particleClassification.colorClassification import ColorClassifier
from .particleClassification.shapeClassification import ShapeClassifier
......@@ -31,17 +32,21 @@ from ..segmentation import closeHolesOfSubImage
from ..errors import InvalidParticleError
from ..helperfunctions import cv2imread_fix
from ..analysis.particleAndMeasurement import Particle
from ..scenePyramid import ScenePyramid
class FTIRAperture(object):
Configuration for an FTIR aperture. CenterCoords, width and height in Pixels
centerX: float = None
centerY: float = None
centerZ: float = None
width: float = None
height: float = None
angle: float = None
centerX: float = None # in px
centerY: float = None # in px
centerZ: float = None # in px
width: float = None # in px
height: float = None # in px
angle: float = None # in degree
rectCoords: list = None
......@@ -118,26 +123,39 @@ def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, scenePyrami
newStats.shortSize *= pixelscale
partImg = None
assert scenePyramid is not None or fullimage is not None
if scenePyramid is None and fullimage is not None:
partImg, extrema = getParticleImageFromFullimage(cnt, fullimage)
elif scenePyramid is not None and fullimage is None:
partImg, extrema = getParticleImageFromScenePyramid(cnt, scenePyramid)
assert partImg is not None, "error in getting particle image"
newStats.color = getParticleColor(partImg)
if ftir:
padding: int = int(2)
imgforAperture = np.zeros((partImg.shape[0]+2*padding, partImg.shape[1]+2*padding))
imgforAperture[padding:partImg.shape[0]+padding, padding:partImg.shape[1]+padding] = np.mean(partImg, axis=2)
newStats.aperture = getApertureObjectForParticleImage(partImg, extrema, newStats.height)
return newStats
def calculateFTIRAperturesForParticle(particle: 'Particle', pyramid: 'ScenePyramid') -> 'FTIRAperture':
partImg, extrema = getParticleImageFromScenePyramid(particle.contour, pyramid)
aperture: 'FTIRAperture' = getApertureObjectForParticleImage(partImg, extrema, particle.height)
return aperture
def getApertureObjectForParticleImage(particleImage: np.ndarray, extrema: tuple,
particleHeight: float, padding: int = 2) -> 'FTIRAperture':
imgforAperture = np.zeros((particleImage.shape[0] + 2 * padding, particleImage.shape[1] + 2 * padding))
imgforAperture[padding:particleImage.shape[0] + padding, padding:particleImage.shape[1] + padding] = np.mean(particleImage, axis=2)
imgforAperture[imgforAperture > 0] = 1 # convert to binary image
imgforAperture = np.uint8(1 - imgforAperture) # background has to be 1, particle has to be 0
aperture: FTIRAperture = getFTIRAperture(imgforAperture)
aperture.centerX = aperture.centerX + extrema[0] - padding
aperture.centerY = aperture.centerY + extrema[2] - padding
aperture.centerZ = newStats.height
newStats.aperture = aperture
return newStats
aperture.centerZ = particleHeight
return aperture
def getFibreDimension(contour):
......@@ -25,6 +25,7 @@ from PyQt5 import QtWidgets
from typing import List, TYPE_CHECKING
from .particleAndMeasurement import Particle, Measurement
from .particleCharacterization import calculateFTIRAperturesForParticle
specImportEnabled: bool = True
from ..analysis import importSpectra
......@@ -33,6 +34,7 @@ except ModuleNotFoundError:
from ..dataset import DataSet
from ..scenePyramid import ScenePyramid
class ParticleContainer(object):
......@@ -122,6 +124,22 @@ class ParticleContainer(object):
for index, particle in enumerate(self.particles):
def updateFTIRApertures(self, pyramid: 'ScenePyramid', progressbar: QtWidgets.QProgressDialog = None) -> None:
Calculates FTIR Apertures if they were not already calculated...
if progressbar is not None:
progressbar.setWindowTitle("Calculating Particle Apertures")
for i, particle in enumerate(self.particles):
if not hasattr(particle, 'aperture'):
particle.aperture = calculateFTIRAperturesForParticle(particle, pyramid)
elif particle.aperture is None:
particle.aperture = calculateFTIRAperturesForParticle(particle, pyramid)
def testForInconsistentParticleAssignments(self):
# Find particles that have multiple measurements with different assignments
self.inconsistentParticles = []
......@@ -284,7 +284,8 @@ class DataSet(object):
z0, z1 = self.zpositions[0], self.zpositions[-1]
return zp / 255. * (z1 - z0) + z0
def mapHeight(self, x, y):
def mapHeight(self, x, y, force=False):
if not force:
assert not self.readin
assert self.heightmap is not None
return self.heightmap[0] * x + self.heightmap[1] * y + self.heightmap[2]
......@@ -325,7 +326,7 @@ class DataSet(object):
z = None
if (returnz and self.zvalimg is not None) or self.coordinatetransform is not None:
z = self.mapHeight(x, y)
z = self.mapHeight(x, y, force=force)
z += self.getZval(pixelpos)
if self.coordinatetransform is not None:
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
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 xml.etree.cElementTree as ET
import numpy as np
import os
from typing import *
from PyQt5 import QtWidgets
from .dataset import DataSet
from .analysis.particleContainer import ParticleContainer
from .analysis.particleCharacterization import FTIRAperture
def exportSLFFile(dataset: 'DataSet', progressBar: 'QtWidgets.QProgressDialog', minAptSize: float = 3,
maxAptSize: float = 1000) -> str:
Writes an SLF file for import in the Perkin Elmer Spotlight Instrument.
:param dataset: Dataset of the currently loaded experimetn
:param progressBar: Statusbar for updating progress
:param minAptSize: Minimal Aperture Size (in µm)
:param maxAptSize: Maximum Aperture Size (in µm)
:returns: Filename of saved file
partContainer: 'ParticleContainer' = dataset.particleContainer
progressBar.setWindowTitle("Creating SLF File")
progressBar.setMaximum(partContainer.getNumberOfParticles() - 1)
pxScale: float = dataset.getPixelScale() # µm/px
# prepare xml file
root = ET.Element("configuration")
shapes = ET.SubElement(root, "shapes")
for i, particle in enumerate(partContainer.particles):
apt: 'FTIRAperture' = particle.aperture
width = np.clip(apt.width * pxScale, minAptSize, maxAptSize)
height = np.clip(apt.height * pxScale, minAptSize, maxAptSize)
x, y, z = dataset.mapToLength((apt.centerX, apt.centerY), force=True, returnz=True)
x = round(x, 5)
y = round(y, 5)
z = round(z, 5)
shape = ET.SubElement(shapes, "shape", type="Pki.MolSpec.MSOverlayControls.Marker", hasAperture="True")
ET.SubElement(shape, "dimensions", x=str(x), y=str(y), z=str(z), width="0", height="0", color="4294901760")
ET.SubElement(shape, "aperture", width=str(width), height=str(height), rotation=str(apt.angle))
ET.SubElement(shape, "sample", id="Marker", measured="false")
# append last entry to slf file:
shape = ET.SubElement(shapes, "shape", type="Pki.MolSpec.MSOverlayControls.CrosshairCursor", hasAperture="False")
ET.SubElement(shape, "dimensions", x=str(3856), y=str(-739), z=str(-1643), width="0", height="0",
ET.SubElement(shape, "aperture", width=str(width), height=str(height), rotation=str(apt.angle))
ET.SubElement(shape, "sample", id="Background", measured="true")
# write slf-file
savename = os.path.join(dataset.path,
tree = ET.ElementTree(root)
inc = 1
filename = savename + '.slf'
while os.path.exists(savename):
filename = savename + '(' + str(inc) + ').slf'
inc += 1
return os.path.basename(filename)
......@@ -20,6 +20,7 @@ If not, see <>.
from PyQt5 import QtWidgets, QtCore, QtGui
from copy import deepcopy
from typing import TYPE_CHECKING
import numpy as np
from ...analysis.particleEditor import ParticleContextMenu
from ...analysis.particleCharacterization import FTIRAperture
......@@ -184,7 +185,7 @@ class FTIRApertureIndicator(QtWidgets.QGraphicsItem):
def paint(self, painter, option, widget):
if not self.hidden:
color: QtGui.QColor =
color: QtGui.QColor = QtGui.QColor(0, 255, 0, 255)
if self.transparent:
......@@ -34,6 +34,8 @@ from .gui.configInstrumentUI import InstrumentConfigWin
from .scenePyramid import ScenePyramid
from .gepardlogging import setDefaultLoggingConfig
from .gui.viewItemHandler import ViewItemHandler
from .exportSLF import exportSLFFile
from __main__ import GEPARDMainWindow
......@@ -125,6 +127,16 @@ class SampleView(QtWidgets.QGraphicsView):
self.configWin = InstrumentConfigWin(self)
def exportAptsToSLF(self) -> None:
progress: QtWidgets.QProgressDialog = QtWidgets.QProgressDialog()
self.dataset.particleContainer.updateFTIRApertures(self.pyramid, progressbar=progress)
fname = exportSLFFile(self.dataset, progress)
QtWidgets.QMessageBox.about(self, "Done", f"Particle and Aperture data saved to {fname} in project directory")
def saveDataSet(self):
if self.dataset is not None:
import logging
import numpy as np
from typing import TYPE_CHECKING
from ..dataset import DataSet
from ..analysis.particleContainer import ParticleContainer
from ..analysis.particleAndMeasurement import Particle, Measurement
from ..__main__ import GEPARDMainWindow
from ..sampleview import SampleView
......@@ -31,3 +34,18 @@ def getDefaultMainWin() -> GEPARDMainWindow:
def getDefaultSampleview() -> 'SampleView':
gepard: GEPARDMainWindow = getDefaultMainWin()
return gepard.view
def getDefaultParticleContainer(numParticles: int = 4) -> ParticleContainer:
partContainer: ParticleContainer = ParticleContainer()
contours: list = []
for i in range(numParticles):
x = 10*i
contours.append(np.array([[[x, 0]], [[x+10, 0]], [[x+10, 10]], [[x, 10]]], dtype=np.int32))
partContainer.particles[0].color = 'red'
partContainer.particles[1].color = 'blue'
partContainer.particles[2].color = 'green'
partContainer.particles[3].color = 'transparent'
return partContainer
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment