#!/usr/bin/env python3 # -*- 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 cv2 from ...errors import InvalidParticleError class ShapeClassifier(object): def __init__(self): self.shapeClasses = [Spherule(), Fibre(), Irregular()] def classifyShape(self, contour, particleHeight): newShape = BaseShape() newShape.contour = contour newShape.height = particleHeight newShape.getParticleCharacteristics() mostFittingCriteria = 0 bestFittingShape = 'unknown' for testShape in self.shapeClasses: numFits = newShape.fitsOtherShapeCriteria(testShape) if numFits > mostFittingCriteria: mostFittingCriteria = numFits bestFittingShape = testShape.name return bestFittingShape class BaseShape(object): def __init__(self): self.name = None self.contour = None self.longSize = None self.shortSize = None self.height = None self.aspectRatio = None self.aspectRatioRange = None self.solidity = None self.solidityRange = None self.height2AverageLength = None self.height2AverageLengthRange = None def getParticleCharacteristics(self): assert self.contour is not None, 'not able to get contour characteristics of NONE contour' area = cv2.contourArea(self.contour) hull = cv2.convexHull(self.contour) hull_area = cv2.contourArea(hull) if area == 0 or hull_area == 0: raise InvalidParticleError self.solidity = area/hull_area long, short = self.getEllipseOrBoxLongAndShortSize() self.aspectRatio = long/short avgLength = (long+short)/2 self.height2AverageLength = self.height/avgLength def getEllipseOrBoxLongAndShortSize(self): if self.contour.shape[0] >= 5: ##at least 5 points required for ellipse fitting... ellipse = cv2.fitEllipse(self.contour) short, long = ellipse[1] else: rect = cv2.minAreaRect(self.contour) long, short = rect[1] if short>long: long, short = short, long if short == 0.0: raise InvalidParticleError return long, short def fitsOtherShapeCriteria(self, otherShape): assert self.aspectRatio is not None assert self.solidity is not None numFittingCriteria = 0 if otherShape.aspectRatioRange[0] <= self.aspectRatio <= otherShape.aspectRatioRange[1]: numFittingCriteria += 1 if otherShape.solidityRange[0] <= self.solidity <= otherShape.solidityRange[1]: numFittingCriteria += 1 if otherShape.height2AverageLengthRange[0] <= self.height2AverageLength <= otherShape.height2AverageLengthRange[1]: numFittingCriteria += 1 return numFittingCriteria class Spherule(BaseShape): def __init__(self): super(Spherule, self).__init__() self.name = 'spherule' self.aspectRatioRange = [1.0, 1.2] self.solidityRange = [0.95, 1.] self.height2AverageLengthRange = [0.4, 1.5] class Irregular(BaseShape): def __init__(self): super(Irregular, self).__init__() self.name = 'irregular' self.aspectRatioRange = [1.0, 3] self.solidityRange = [0.9, 1.] self.height2AverageLengthRange = [0.4, 10] class Flake(BaseShape): def __init__(self): super(Flake, self).__init__() self.name = 'flake' self.aspectRatioRange = [1.0, 3] self.solidityRange = [0.7, 1.] self.height2AverageLengthRange = [0.0, 0.4] class Fibre(BaseShape): def __init__(self): super(Fibre, self).__init__() self.name = 'fibre' self.aspectRatioRange = [5, 1000] self.solidityRange = [0.0, 0.4] self.height2AverageLengthRange = [0, 1000]