Commit 672f17f6 authored by Josef Brandt's avatar Josef Brandt

From Linux..

parent f3c17ebf
...@@ -36,6 +36,25 @@ from .unittests.test_gepard import testGepard ...@@ -36,6 +36,25 @@ from .unittests.test_gepard import testGepard
from .helperfunctions import getAppFolder from .helperfunctions import getAppFolder
def excepthook(excType, excValue, tracebackobj):
"""
Global function to catch unhandled exceptions.
@param excType exception type
@param excValue exception value
@param tracebackobj traceback object
:return:
"""
tbinfofile = StringIO()
traceback.print_tb(tracebackobj, None, tbinfofile)
tbinfofile.seek(0)
tbinfo = tbinfofile.read()
logging.critical("Fatal error in program excecution!")
logging.critical(tbinfo)
from .errors import showErrorMessageAsWidget
showErrorMessageAsWidget(tbinfo)
class GEPARDMainWindow(QtWidgets.QMainWindow): class GEPARDMainWindow(QtWidgets.QMainWindow):
def __init__(self, logger): def __init__(self, logger):
super(GEPARDMainWindow, self).__init__() super(GEPARDMainWindow, self).__init__()
...@@ -387,25 +406,6 @@ if __name__ == '__main__': ...@@ -387,25 +406,6 @@ if __name__ == '__main__':
""" """
app.closeAllWindows() app.closeAllWindows()
def excepthook(excType, excValue, tracebackobj):
"""
Global function to catch unhandled exceptions.
@param excType exception type
@param excValue exception value
@param tracebackobj traceback object
:return:
"""
tbinfofile = StringIO()
traceback.print_tb(tracebackobj, None, tbinfofile)
tbinfofile.seek(0)
tbinfo = tbinfofile.read()
logging.critical("Fatal error in program excecution!")
logging.critical(tbinfo)
from .errors import showErrorMessageAsWidget
showErrorMessageAsWidget(tbinfo)
sys.excepthook = excepthook sys.excepthook = excepthook
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
......
...@@ -20,6 +20,8 @@ If not, see <https://www.gnu.org/licenses/>. ...@@ -20,6 +20,8 @@ If not, see <https://www.gnu.org/licenses/>.
""" """
import numpy as np import numpy as np
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
# from .. import dataset
from gepard import dataset
if TYPE_CHECKING: if TYPE_CHECKING:
from .particleCharacterization import FTIRAperture from .particleCharacterization import FTIRAperture
...@@ -28,6 +30,9 @@ if TYPE_CHECKING: ...@@ -28,6 +30,9 @@ if TYPE_CHECKING:
class Particle(object): class Particle(object):
def __init__(self): def __init__(self):
super(Particle, self).__init__() super(Particle, self).__init__()
# from ...gepard.dataset import DataSet
self.uid: int = dataset.DataSet.getUID()
self.index: int = np.nan self.index: int = np.nan
self.longSize: float = np.nan self.longSize: float = np.nan
self.shortSize: float = np.nan self.shortSize: float = np.nan
...@@ -50,7 +55,7 @@ class Particle(object): ...@@ -50,7 +55,7 @@ class Particle(object):
meas.setHQI(100) meas.setHQI(100)
def getParticleAssignment(self) -> str: def getParticleAssignment(self) -> str:
assignment = 'Will not be measured' assignment = 'Excluded from Spectrum Scan'
if len(self.measurements) > 0: if len(self.measurements) > 0:
assignment = self.getMeasAssignmentWithHighestHQI() assignment = self.getMeasAssignmentWithHighestHQI()
return assignment return assignment
......
...@@ -94,9 +94,7 @@ def getFTIRAperture(partImg: np.ndarray) -> FTIRAperture: ...@@ -94,9 +94,7 @@ def getFTIRAperture(partImg: np.ndarray) -> FTIRAperture:
return aperture return aperture
def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, zimg=None, ftir: bool = False): def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, scenePyramid=None, zimg=None, ftir: bool = False):
if fullimage is None:
fullimage = loadFullimageFromDataset(dataset)
if zimg is None: if zimg is None:
zimg = loadZValImageFromDataset(dataset) zimg = loadZValImageFromDataset(dataset)
...@@ -119,7 +117,12 @@ def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, zimg=None, ...@@ -119,7 +117,12 @@ def getParticleStatsWithPixelScale(contour, dataset, fullimage=None, zimg=None,
newStats.longSize *= pixelscale newStats.longSize *= pixelscale
newStats.shortSize *= pixelscale newStats.shortSize *= pixelscale
partImg, extrema = getParticleImageFromFullimage(cnt, fullimage) partImg = 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) newStats.color = getParticleColor(partImg)
if ftir: if ftir:
...@@ -168,7 +171,7 @@ def getParticleHeight(contour, fullZimg, dataset): ...@@ -168,7 +171,7 @@ def getParticleHeight(contour, fullZimg, dataset):
zimg = cv2.medianBlur(zimg, 5) zimg = cv2.medianBlur(zimg, 5)
avg_ZValue = np.mean(zimg[zimg > 0]) avg_ZValue = np.mean(zimg[zimg > 0])
if np.isnan(avg_ZValue): #i.e., only zeros in zimg if np.isnan(avg_ZValue): # i.e., only zeros in zimg
avg_ZValue = 0 avg_ZValue = 0
z0, z1 = dataset.zpositions.min(), dataset.zpositions.max() z0, z1 = dataset.zpositions.min(), dataset.zpositions.max()
height = avg_ZValue/255.*(z1-z0) + z0 height = avg_ZValue/255.*(z1-z0) + z0
...@@ -176,20 +179,20 @@ def getParticleHeight(contour, fullZimg, dataset): ...@@ -176,20 +179,20 @@ def getParticleHeight(contour, fullZimg, dataset):
def getContourStats(cnt): def getContourStats(cnt):
##characterize particle # characterize particle
if cnt.shape[0] >= 5: ##at least 5 points required for ellipse fitting... if cnt.shape[0] >= 5: # at least 5 points required for ellipse fitting...
ellipse = cv2.fitEllipse(cnt) ellipse = cv2.fitEllipse(cnt)
short, long = ellipse[1] short, long = ellipse[1]
else: else:
rect = cv2.minAreaRect(cnt) rect = cv2.minAreaRect(cnt)
long, short = rect[1] long, short = rect[1]
if short>long: if short>long:
long, short = short, long long, short = short, long
area = cv2.contourArea(cnt) area = cv2.contourArea(cnt)
return long, short, area return long, short, area
def mergeContours(contours): def mergeContours(contours):
...@@ -217,11 +220,30 @@ def getParticleImageFromFullimage(contour, fullimage): ...@@ -217,11 +220,30 @@ def getParticleImageFromFullimage(contour, fullimage):
return img, (xmin, xmax, ymin, ymax) return img, (xmin, xmax, ymin, ymax)
def getParticleImageFromScenePyramid(contour, scenePyramid):
contourCopy = deepcopy(contour)
xmin, xmax, ymin, ymax = getContourExtrema(contourCopy)
img = scenePyramid.getImagePart(ymin, ymax, xmin, xmax)
img = img.copy()
mask = np.zeros(img.shape[:2])
for i in range(len(contourCopy)):
contourCopy[i][0][0] -= xmin
contourCopy[i][0][1] -= ymin
cv2.drawContours(mask, [contourCopy], -1, (255, 255, 255), -1)
cv2.drawContours(mask, [contourCopy], -1, (255, 255, 255), 1)
img[mask == 0] = 0
img = np.array(img, dtype=np.uint8)
return img, (xmin, xmax, ymin, ymax)
def contoursToImg(contours, padding=0): def contoursToImg(contours, padding=0):
contourCopy = deepcopy(contours) contourCopy = deepcopy(contours)
xmin, xmax, ymin, ymax = getContourExtrema(contourCopy) xmin, xmax, ymin, ymax = getContourExtrema(contourCopy)
padding = padding #pixel in each direction
rangex = int(np.round((xmax-xmin)+2*padding)) rangex = int(np.round((xmax-xmin)+2*padding))
rangey = int(np.round((ymax-ymin)+2*padding)) rangey = int(np.round((ymax-ymin)+2*padding))
if rangex == 0 or rangey == 0: if rangex == 0 or rangey == 0:
...@@ -239,36 +261,39 @@ def contoursToImg(contours, padding=0): ...@@ -239,36 +261,39 @@ def contoursToImg(contours, padding=0):
def imgToCnt(img, xmin, ymin, padding=0): def imgToCnt(img, xmin, ymin, padding=0):
def getContour(img, contourMode): def getContour(img, contourMode):
def getLargestContour(contours):
areas = []
for contour in contours:
areas.append(cv2.contourArea(contour))
maxIndex = areas.index(max(areas))
print(f'{len(contours)} contours found, getting the largest one. Areas are: {areas}, '
f'taking contour at index {maxIndex}')
return contours[maxIndex]
if cv2.__version__ > '3.5': if cv2.__version__ > '3.5':
contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, contourMode) contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, contourMode)
else: else:
temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, contourMode) temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, contourMode)
if len(contours) == 0: #i.e., no contour found if len(contours) == 0: # i.e., no contour found
raise InvalidParticleError raise InvalidParticleError
elif len(contours) == 1: #i.e., exactly one contour found elif len(contours) == 1: # i.e., exactly one contour found
contour = contours[0] contour = contours[0]
else: #i.e., multiple contours found else: # i.e., multiple contours found
contour = getLargestContour(contours) contour = getLargestContour(contours)
return contour return contour
def getLargestContour(contours):
areas = []
for contour in contours:
areas.append(cv2.contourArea(contour))
maxIndex = areas.index(max(areas))
print(f'{len(contours)} contours found, getting the largest one. Areas are: {areas}, taking contour at index {maxIndex}')
return contours[maxIndex]
img = closeHolesOfSubImage(img) img = closeHolesOfSubImage(img)
contour = getContour(img, contourMode=cv2.CHAIN_APPROX_NONE) contour = getContour(img, contourMode=cv2.CHAIN_APPROX_NONE)
for i in range(len(contour)): for i in range(len(contour)):
contour [i][0][0] += xmin-padding contour[i][0][0] += xmin-padding
contour [i][0][1] += ymin-padding contour[i][0][1] += ymin-padding
return contour return contour
......
...@@ -22,6 +22,7 @@ If not, see <https://www.gnu.org/licenses/>. ...@@ -22,6 +22,7 @@ If not, see <https://www.gnu.org/licenses/>.
import cv2 import cv2
from ...errors import InvalidParticleError from ...errors import InvalidParticleError
class ShapeClassifier(object): class ShapeClassifier(object):
def __init__(self): def __init__(self):
self.shapeClasses = [Spherule(), Fibre(), Irregular()] self.shapeClasses = [Spherule(), Fibre(), Irregular()]
......
...@@ -24,20 +24,41 @@ import os ...@@ -24,20 +24,41 @@ import os
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
from . import importSpectra
from .particleAndMeasurement import Particle, Measurement from .particleAndMeasurement import Particle, Measurement
specImportEnabled: bool = True
try:
from ..gepardevaluation.analysis import importSpectra
except ModuleNotFoundError:
specImportEnabled = False
if TYPE_CHECKING: if TYPE_CHECKING:
from ..dataset import DataSet from ..dataset import DataSet
class ParticleContainer(object): class ParticleContainer(object):
def __init__(self, datasetParent: 'DataSet'): def __init__(self):
super(ParticleContainer, self).__init__() super(ParticleContainer, self).__init__()
self.datasetParent: 'DataSet' = datasetParent self.datasetParent: 'DataSet' = None
self.particles: List[Particle] = [] self.particles: List[Particle] = []
self.measurements: List[Measurement] = [] self.measurements: List[Measurement] = []
self.inconsistentParticles: List[Particle] = [] self.inconsistentParticles: List[Particle] = []
def setDataSet(self, ds):
"""
dynamically sets a reference to the dataset containing this particle container
:param ds:
:return:
"""
self.datasetParent = ds
def unsetDataSet(self):
"""
is called before dataset is saved, preventing pickle to resolve this reference to dataset into
a copy of it (baking its data into the save file)
:return:
"""
self.datasetParent = None
def addEmptyMeasurement(self) -> int: def addEmptyMeasurement(self) -> int:
newMeas = Measurement() newMeas = Measurement()
self.measurements.append(newMeas) self.measurements.append(newMeas)
...@@ -58,33 +79,30 @@ class ParticleContainer(object): ...@@ -58,33 +79,30 @@ class ParticleContainer(object):
self.measurements[indexOfMeasurment].pixelcoord_x = x self.measurements[indexOfMeasurment].pixelcoord_x = x
self.measurements[indexOfMeasurment].pixelcoord_y = y self.measurements[indexOfMeasurment].pixelcoord_y = y
def getMeasurementScanindex(self, indexOfMeasurement): def getMeasurementScanindex(self, indexOfMeasurement) -> int:
return self.measurements[indexOfMeasurement].getScanIndex() return self.measurements[indexOfMeasurement].getScanIndex()
def getSpectraFromDisk(self): def getSpectraFromDisk(self) -> np.ndarray:
spectra = None spectra = None
specPath = self.datasetParent.getSpectraFileName() if specImportEnabled:
specPath = self.datasetParent.getSpectraFileName()
if os.path.exists(specPath): if os.path.exists(specPath):
spectra = np.load(specPath) spectra = np.load(specPath)
else: else:
fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File',
fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File', self.datasetParent.path, 'text file (*.txt)')[0] self.datasetParent.path, 'text file (*.txt)')[0]
if fname: if fname:
#TODO: implement a more elegant way of testing through the individual imports...
try:
spectra, spectraNames = importSpectra.importWITecSpectra(fname)
except ImportError:
try: try:
spectra, spectraNames = importSpectra.importRenishawSpectra(fname) instruments = importSpectra.listInstruments()
except ImportError: instrument, okPressed = QtWidgets.QInputDialog.getItem(QtWidgets.QWidget(), "File format",
try: "Select instrument:", instruments, 0, False)
spectra, spectraNames = importSpectra.importPerkinElmerSpectra(fname) if okPressed and instrument:
except ImportError: spectra, spectraNames = importSpectra.chooseInstrument(instrument, fname)
pass except (ValueError, ImportError):
QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Error', 'Unknown format, no spectra loaded.')
if spectra is not None:
np.save(self.datasetParent.getSpectraFileName(), spectra) if spectra is not None:
np.save(self.datasetParent.getSpectraFileName(), spectra)
return spectra return spectra
def initializeParticles(self, numParticles): def initializeParticles(self, numParticles):
...@@ -104,7 +122,8 @@ class ParticleContainer(object): ...@@ -104,7 +122,8 @@ class ParticleContainer(object):
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 testForInconsistentParticleAssignments(self): #i.e., particles that have multiple measurements with different assignments def testForInconsistentParticleAssignments(self):
# Find particles that have multiple measurements with different assignments
self.inconsistentParticles = [] self.inconsistentParticles = []
for particle in self.particles: for particle in self.particles:
if not particle.measurementsHaveSameOrigAssignment(): if not particle.measurementsHaveSameOrigAssignment():
...@@ -162,7 +181,19 @@ class ParticleContainer(object): ...@@ -162,7 +181,19 @@ class ParticleContainer(object):
raise raise
return particle return particle
def getParticleOfUID(self, uid):
try:
for particle in self.particles:
if uid == particle.uid:
break
except:
print('failed getting particle')
print('requested Index:', uid)
print('len particles', len(self.particles))
assert particle.uid == uid, f'particle.index ({particle.uid}) does match requested index in particleList ({uid})'
return particle
def getParticleIndexContainingSpecIndex(self, index): def getParticleIndexContainingSpecIndex(self, index):
for particle in self.particles: for particle in self.particles:
if index in particle.getMeasurementIndices(): if index in particle.getMeasurementIndices():
...@@ -195,6 +226,10 @@ class ParticleContainer(object): ...@@ -195,6 +226,10 @@ class ParticleContainer(object):
def getParticleAssignmentByIndex(self, partIndex): def getParticleAssignmentByIndex(self, partIndex):
particle = self.getParticleOfIndex(partIndex) particle = self.getParticleOfIndex(partIndex)
return particle.getParticleAssignment() return particle.getParticleAssignment()
def getParticleAssignmentByUID(self, uid):
particle = self.getParticleOfUID(uid)
return particle.getParticleAssignment()
def getMeasurementPixelCoords(self) -> list: def getMeasurementPixelCoords(self) -> list:
coords: list = [] coords: list = []
...@@ -209,7 +244,6 @@ class ParticleContainer(object): ...@@ -209,7 +244,6 @@ class ParticleContainer(object):
coords.append([particle.aperture.centerX, particle.aperture.centerY]) coords.append([particle.aperture.centerX, particle.aperture.centerY])
return coords return coords
def getNumberOfParticlesOfAssignment(self, assignment): def getNumberOfParticlesOfAssignment(self, assignment):
num = 0 num = 0
for particle in self.particles: for particle in self.particles:
...@@ -224,6 +258,10 @@ class ParticleContainer(object): ...@@ -224,6 +258,10 @@ class ParticleContainer(object):
def getSpectraIndicesOfParticle(self, particleIndex): def getSpectraIndicesOfParticle(self, particleIndex):
particle = self.getParticleOfIndex(particleIndex) particle = self.getParticleOfIndex(particleIndex)
return particle.getMeasurementIndices() return particle.getMeasurementIndices()
def getSpectraIndicesOfParticleByUID(self, particleUID):
particle = self.getParticleOfUID(particleUID)
return particle.getMeasurementIndices()
def getListOfParticleAssignments(self): def getListOfParticleAssignments(self):
particleAssignments = [] particleAssignments = []
...@@ -279,7 +317,15 @@ class ParticleContainer(object): ...@@ -279,7 +317,15 @@ class ParticleContainer(object):
def getParticleShapeByIndex(self, particleIndex): def getParticleShapeByIndex(self, particleIndex):
particle = self.getParticleOfIndex(particleIndex) particle = self.getParticleOfIndex(particleIndex)
return particle.shape return particle.shape
def getParticleIndexByUID(self, uid):
for idx, particle in enumerate(self.particles):
if uid == particle.uid:
break
assert uid == particle.uid
return idx
def getSizesOfParticleType(self, assignment): def getSizesOfParticleType(self, assignment):
particleSizes = [] particleSizes = []
for particle in self.particles: for particle in self.particles:
...@@ -293,7 +339,14 @@ class ParticleContainer(object): ...@@ -293,7 +339,14 @@ class ParticleContainer(object):
if particle.getParticleAssignment() == assignment: if particle.getParticleAssignment() == assignment:
indices.append(particle.index) indices.append(particle.index)
return indices return indices
def getUIDsOfParticleType(self, assignment):
ids = []
for particle in self.particles:
if particle.getParticleAssignment() == assignment:
ids.append(particle.uid)
return ids
def getSizeOfParticleByIndex(self, index): def getSizeOfParticleByIndex(self, index):
particle = self.getParticleOfIndex(index) particle = self.getParticleOfIndex(index)
return particle.getParticleSize() return particle.getParticleSize()
...@@ -322,16 +375,22 @@ class ParticleContainer(object): ...@@ -322,16 +375,22 @@ class ParticleContainer(object):
particle = self.getParticleOfIndex(index) particle = self.getParticleOfIndex(index)
particle.color = newColor particle.color = newColor
particle.wasManuallyEdited = True particle.wasManuallyEdited = True
def changeParticleShape(self, index, newShape): def changeParticleShape(self, index, newShape):
particle = self.getParticleOfIndex(index) particle = self.getParticleOfIndex(index)
if 'fibre' == newShape:
particle.longSize, particle.shortSize = pc.getFibreDimension(particle.contour)
particle.longSize *= self.datasetParent.getPixelScale()
particle.shortSize *= self.datasetParent.getPixelScale()
particle.shape = newShape particle.shape = newShape
particle.wasManuallyEdited = True particle.wasManuallyEdited = True
def addMergedParticle(self, particleIndices, newContour, newStats, newAssignment=None): def addMergedParticle(self, particleIndices, newContour, newStats, newAssignment=None) -> Particle:
newParticle = Particle() newParticle = Particle()
newParticle.contour = newContour newParticle.contour = newContour
#copy Measurements # copy Measurements
for index in particleIndices: for index in particleIndices:
particle = self.getParticleOfIndex(index) particle = self.getParticleOfIndex(index)
for meas in particle.getMeasurements(): for meas in particle.getMeasurements():
...@@ -343,7 +402,12 @@ class ParticleContainer(object): ...@@ -343,7 +402,12 @@ class ParticleContainer(object):
newParticle.__dict__.update(newStats.__dict__) newParticle.__dict__.update(newStats.__dict__)
newParticle.wasManuallyEdited = True newParticle.wasManuallyEdited = True
self.particles.append(newParticle) self.particles.append(newParticle)
print('added new particle') return newParticle
def removeParticles(self, indices):
assert isinstance(indices, list)
for idx in indices:
self.removeParticle(idx)