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): ...@@ -308,6 +308,9 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
if self.view.simulatedRaman: if self.view.simulatedRaman:
self.configRamanCtrlAct.setDisabled(True) self.configRamanCtrlAct.setDisabled(True)
self.exportSLFAct: QtWidgets.QAction = QtWidgets.QAction("&Export FTIR Apertures to .slf")
self.exportSLFAct.triggered.connect(self.view.exportAptsToSLF)
self.testAct: QtWidgets.QAction = QtWidgets.QAction("&Run Automated Gepard Test") self.testAct: QtWidgets.QAction = QtWidgets.QAction("&Run Automated Gepard Test")
self.testAct.setShortcut("Ctrl+T") self.testAct.setShortcut("Ctrl+T")
self.testAct.triggered.connect(runGepardTest(self)) self.testAct.triggered.connect(runGepardTest(self))
...@@ -357,6 +360,8 @@ class GEPARDMainWindow(QtWidgets.QMainWindow): ...@@ -357,6 +360,8 @@ class GEPARDMainWindow(QtWidgets.QMainWindow):
self.toolsMenu = QtWidgets.QMenu("&Tools") self.toolsMenu = QtWidgets.QMenu("&Tools")
self.toolsMenu.addAction(self.snapshotAct) self.toolsMenu.addAction(self.snapshotAct)
self.toolsMenu.addAction(self.configRamanCtrlAct) self.toolsMenu.addAction(self.configRamanCtrlAct)
self.toolsMenu.addAction(self.exportSLFAct)
self.toolsMenu.addSeparator()
self.toolsMenu.addAction(self.testAct) self.toolsMenu.addAction(self.testAct)
self.dispMenu = QtWidgets.QMenu("&Display", self) self.dispMenu = QtWidgets.QMenu("&Display", self)
......
...@@ -22,7 +22,7 @@ If not, see <https://www.gnu.org/licenses/>. ...@@ -22,7 +22,7 @@ If not, see <https://www.gnu.org/licenses/>.
import numpy as np import numpy as np
from scipy import optimize from scipy import optimize
import cv2 import cv2
# from ..cythonModules.getMaxRect import findMaxRect # TODO: UNCOMMENT!!! from ..cythonModules.getMaxRect import findMaxRect # TODO: UNCOMMENT!!!
#################################################### ####################################################
......
...@@ -23,6 +23,7 @@ If not, see <https://www.gnu.org/licenses/>. ...@@ -23,6 +23,7 @@ If not, see <https://www.gnu.org/licenses/>.
import numpy as np import numpy as np
import cv2 import cv2
from copy import deepcopy from copy import deepcopy
from typing import TYPE_CHECKING
from .particleClassification.colorClassification import ColorClassifier from .particleClassification.colorClassification import ColorClassifier
from .particleClassification.shapeClassification import ShapeClassifier from .particleClassification.shapeClassification import ShapeClassifier
...@@ -31,17 +32,21 @@ from ..segmentation import closeHolesOfSubImage ...@@ -31,17 +32,21 @@ from ..segmentation import closeHolesOfSubImage
from ..errors import InvalidParticleError from ..errors import InvalidParticleError
from ..helperfunctions import cv2imread_fix from ..helperfunctions import cv2imread_fix
if TYPE_CHECKING:
from ..analysis.particleAndMeasurement import Particle
from ..scenePyramid import ScenePyramid
class FTIRAperture(object): class FTIRAperture(object):
""" """
Configuration for an FTIR aperture. CenterCoords, width and height in Pixels Configuration for an FTIR aperture. CenterCoords, width and height in Pixels
""" """
centerX: float = None centerX: float = None # in px
centerY: float = None centerY: float = None # in px
centerZ: float = None centerZ: float = None # in px
width: float = None width: float = None # in px
height: float = None height: float = None # in px
angle: float = None angle: float = None # in degree
rectCoords: list = None rectCoords: list = None
...@@ -118,28 +123,41 @@ def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, scenePyrami ...@@ -118,28 +123,41 @@ def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, scenePyrami
newStats.shortSize *= pixelscale newStats.shortSize *= pixelscale
partImg = None partImg = None
assert scenePyramid is not None or fullimage is not None
if scenePyramid is None and fullimage is not None: if scenePyramid is None and fullimage is not None:
partImg, extrema = getParticleImageFromFullimage(cnt, fullimage) partImg, extrema = getParticleImageFromFullimage(cnt, fullimage)
elif scenePyramid is not None and fullimage is None: elif scenePyramid is not None and fullimage is None:
partImg, extrema = getParticleImageFromScenePyramid(cnt, scenePyramid) partImg, extrema = getParticleImageFromScenePyramid(cnt, scenePyramid)
assert partImg is not None, "error in getting particle image" assert partImg is not None, "error in getting particle image"
newStats.color = getParticleColor(partImg) newStats.color = getParticleColor(partImg)
if ftir: if ftir:
padding: int = int(2) newStats.aperture = getApertureObjectForParticleImage(partImg, extrema, newStats.height)
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)
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 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 = particleHeight
return aperture
def getFibreDimension(contour): def getFibreDimension(contour):
longSize = cv2.arcLength(contour, True)/2 longSize = cv2.arcLength(contour, True)/2
img = contoursToImg([contour])[0] img = contoursToImg([contour])[0]
......
...@@ -25,6 +25,7 @@ from PyQt5 import QtWidgets ...@@ -25,6 +25,7 @@ from PyQt5 import QtWidgets
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
from .particleAndMeasurement import Particle, Measurement from .particleAndMeasurement import Particle, Measurement
from .particleCharacterization import calculateFTIRAperturesForParticle
specImportEnabled: bool = True specImportEnabled: bool = True
try: try:
from ..analysis import importSpectra from ..analysis import importSpectra
...@@ -33,6 +34,7 @@ except ModuleNotFoundError: ...@@ -33,6 +34,7 @@ except ModuleNotFoundError:
if TYPE_CHECKING: if TYPE_CHECKING:
from ..dataset import DataSet from ..dataset import DataSet
from ..scenePyramid import ScenePyramid
class ParticleContainer(object): class ParticleContainer(object):
...@@ -121,7 +123,23 @@ class ParticleContainer(object): ...@@ -121,7 +123,23 @@ class ParticleContainer(object):
assert len(self.particles) == len(particlestats), f'numParticles = {len(self.particles)}, len partStats = {len(particlestats)}' assert len(self.particles) == len(particlestats), f'numParticles = {len(self.particles)}, len partStats = {len(particlestats)}'
for index, particle in enumerate(self.particles): for index, particle in enumerate(self.particles):
particle.__dict__.update(particlestats[index].__dict__) particle.__dict__.update(particlestats[index].__dict__)
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")
progressbar.setMaximum(len(self.particles)-1)
progressbar.setValue(0)
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)
progressbar.setValue(i)
def testForInconsistentParticleAssignments(self): def testForInconsistentParticleAssignments(self):
# Find particles that have multiple measurements with different assignments # Find particles that have multiple measurements with different assignments
self.inconsistentParticles = [] self.inconsistentParticles = []
......
...@@ -284,8 +284,9 @@ class DataSet(object): ...@@ -284,8 +284,9 @@ class DataSet(object):
z0, z1 = self.zpositions[0], self.zpositions[-1] z0, z1 = self.zpositions[0], self.zpositions[-1]
return zp / 255. * (z1 - z0) + z0 return zp / 255. * (z1 - z0) + z0
def mapHeight(self, x, y): def mapHeight(self, x, y, force=False):
assert not self.readin if not force:
assert not self.readin
assert self.heightmap is not None assert self.heightmap is not None
return self.heightmap[0] * x + self.heightmap[1] * y + self.heightmap[2] return self.heightmap[0] * x + self.heightmap[1] * y + self.heightmap[2]
...@@ -325,7 +326,7 @@ class DataSet(object): ...@@ -325,7 +326,7 @@ class DataSet(object):
z = None z = None
if (returnz and self.zvalimg is not None) or self.coordinatetransform is not 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) z += self.getZval(pixelpos)
if self.coordinatetransform is not None: 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. <bittrich-lars@ipfdd.de>
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 <https://www.gnu.org/licenses/>.
"""
import xml.etree.cElementTree as ET
import numpy as np
import os
from typing import *
if TYPE_CHECKING:
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")
progressBar.setValue(i)
# 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",
color="4294901760")
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, dataset.name)
tree = ET.ElementTree(root)
inc = 1
filename = savename + '.slf'
while os.path.exists(savename):
filename = savename + '(' + str(inc) + ').slf'
inc += 1
tree.write(filename)
return os.path.basename(filename)
...@@ -20,6 +20,7 @@ If not, see <https://www.gnu.org/licenses/>. ...@@ -20,6 +20,7 @@ If not, see <https://www.gnu.org/licenses/>.
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from copy import deepcopy from copy import deepcopy
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import numpy as np
from ...analysis.particleEditor import ParticleContextMenu from ...analysis.particleEditor import ParticleContextMenu
from ...analysis.particleCharacterization import FTIRAperture from ...analysis.particleCharacterization import FTIRAperture
if TYPE_CHECKING: if TYPE_CHECKING:
...@@ -184,7 +185,7 @@ class FTIRApertureIndicator(QtWidgets.QGraphicsItem): ...@@ -184,7 +185,7 @@ class FTIRApertureIndicator(QtWidgets.QGraphicsItem):
def paint(self, painter, option, widget): def paint(self, painter, option, widget):
if not self.hidden: if not self.hidden:
color: QtGui.QColor = QtCore.Qt.green color: QtGui.QColor = QtGui.QColor(0, 255, 0, 255)
if self.transparent: if self.transparent:
color.setAlpha(128) color.setAlpha(128)
painter.setPen(color) painter.setPen(color)
......
...@@ -34,6 +34,8 @@ from .gui.configInstrumentUI import InstrumentConfigWin ...@@ -34,6 +34,8 @@ from .gui.configInstrumentUI import InstrumentConfigWin
from .scenePyramid import ScenePyramid from .scenePyramid import ScenePyramid
from .gepardlogging import setDefaultLoggingConfig from .gepardlogging import setDefaultLoggingConfig
from .gui.viewItemHandler import ViewItemHandler from .gui.viewItemHandler import ViewItemHandler
from .exportSLF import exportSLFFile
if TYPE_CHECKING: if TYPE_CHECKING:
from __main__ import GEPARDMainWindow from __main__ import GEPARDMainWindow
...@@ -124,7 +126,17 @@ class SampleView(QtWidgets.QGraphicsView): ...@@ -124,7 +126,17 @@ class SampleView(QtWidgets.QGraphicsView):
""" """
self.configWin = InstrumentConfigWin(self) self.configWin = InstrumentConfigWin(self)
self.configWin.show() self.configWin.show()
def exportAptsToSLF(self) -> None:
progress: QtWidgets.QProgressDialog = QtWidgets.QProgressDialog()
progress.setCancelButton(None)
progress.setWindowModality(QtCore.Qt.WindowModal)
self.dataset.particleContainer.updateFTIRApertures(self.pyramid, progressbar=progress)
self.dataset.save()
fname = exportSLFFile(self.dataset, progress)
QtWidgets.QMessageBox.about(self, "Done", f"Particle and Aperture data saved to {fname} in project directory")
def saveDataSet(self): def saveDataSet(self):
if self.dataset is not None: if self.dataset is not None:
self.dataset.save() self.dataset.save()
......
import logging import logging
import numpy as np
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..dataset import DataSet from ..dataset import DataSet
from ..analysis.particleContainer import ParticleContainer
from ..analysis.particleAndMeasurement import Particle, Measurement
from ..__main__ import GEPARDMainWindow from ..__main__ import GEPARDMainWindow
if TYPE_CHECKING: if TYPE_CHECKING:
from ..sampleview import SampleView from ..sampleview import SampleView
...@@ -31,3 +34,18 @@ def getDefaultMainWin() -> GEPARDMainWindow: ...@@ -31,3 +34,18 @@ def getDefaultMainWin() -> GEPARDMainWindow:
def getDefaultSampleview() -> 'SampleView': def getDefaultSampleview() -> 'SampleView':
gepard: GEPARDMainWindow = getDefaultMainWin() gepard: GEPARDMainWindow = getDefaultMainWin()
return gepard.view return gepard.view
def getDefaultParticleContainer(numParticles: int = 4) -> ParticleContainer:
partContainer: ParticleContainer = ParticleContainer()
partContainer.initializeParticles(numParticles)
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.setParticleContours(contours)
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