Commit 3a47533b authored by JosefBrandt's avatar JosefBrandt

More robust detection of invalid particles

currently only fully implemented in legacy conversion, has to be adapted during particle detection as well!
parent a79c38ed
...@@ -83,7 +83,6 @@ class TypeHistogramView(QtWidgets.QScrollArea): ...@@ -83,7 +83,6 @@ class TypeHistogramView(QtWidgets.QScrollArea):
self.widgets = [] self.widgets = []
def updateTypeHistogram(self, types): def updateTypeHistogram(self, types):
# print("Updating polymer type view", flush=True)
for pi in self.widgets: for pi in self.widgets:
self.indicatorbox.removeWidget(pi) self.indicatorbox.removeWidget(pi)
pi.setParent(None) pi.setParent(None)
...@@ -97,7 +96,6 @@ class TypeHistogramView(QtWidgets.QScrollArea): ...@@ -97,7 +96,6 @@ class TypeHistogramView(QtWidgets.QScrollArea):
for index, entry in enumerate(types): for index, entry in enumerate(types):
num, text, color = entry num, text, color = entry
# print("num, text, color:", num, text, color, flush=True)
pi = ParticleIndicator(num, numtotal, color, text) pi = ParticleIndicator(num, numtotal, color, text)
self.indicatorbox.addWidget(pi) self.indicatorbox.addWidget(pi)
pi.clicked.connect(getIndexFunction(index)) pi.clicked.connect(getIndexFunction(index))
...@@ -129,7 +127,7 @@ class SpectraPlot(QtWidgets.QGroupBox): ...@@ -129,7 +127,7 @@ class SpectraPlot(QtWidgets.QGroupBox):
self.spectra = self.dataset.particleContainer.getSpectraFromDisk() self.spectra = self.dataset.particleContainer.getSpectraFromDisk()
self.canvas.draw() self.canvas.draw()
def updateParticleSpectrum(self, specIndex, assignment, particleSize, hqi, color): def updateParticleSpectrum(self, specIndex, assignment, hqi):
#draw Sample Spectrum #draw Sample Spectrum
self.spec_axis.axis("on") self.spec_axis.axis("on")
self.spec_axis.clear() self.spec_axis.clear()
...@@ -138,9 +136,11 @@ class SpectraPlot(QtWidgets.QGroupBox): ...@@ -138,9 +136,11 @@ class SpectraPlot(QtWidgets.QGroupBox):
self.spec_axis.set_ylabel('Counts', fontsize = 15) self.spec_axis.set_ylabel('Counts', fontsize = 15)
if self.spectra is not None: if self.spectra is not None:
specInfo = f'ScanPoint Number {specIndex+1}, with assignment {assignment} (hqi = {hqi})'
self.spec_axis.plot(self.spectra[:, 0], self.spectra[:, specIndex+1]) self.spec_axis.plot(self.spectra[:, 0], self.spectra[:, specIndex+1])
self.spec_axis.set_title('{}, ScanPoint Number {}, Size = {} µm, HQI = {}, color = {}'.format(assignment, specIndex+1, particleSize, hqi, color)) self.spec_axis.set_title(specInfo, fontsize=13)
self.spec_axis.set_xbound(100, (3400 if self.spectra[-1, 0] > 3400 else self.spectra[-1, 0])) self.spec_axis.set_xbound(100, (3400 if self.spectra[-1, 0] > 3400 else self.spectra[-1, 0]))
wavenumber_diff = list(self.spectra[:, 0]-100) wavenumber_diff = list(self.spectra[:, 0]-100)
y_start = wavenumber_diff.index(min(wavenumber_diff)) y_start = wavenumber_diff.index(min(wavenumber_diff))
y_min = min(self.spectra[y_start:, specIndex+1]) y_min = min(self.spectra[y_start:, specIndex+1])
......
...@@ -45,7 +45,7 @@ except: ...@@ -45,7 +45,7 @@ except:
class ParticleAnalysis(QtWidgets.QMainWindow): class ParticleAnalysis(QtWidgets.QMainWindow):
def __init__(self, dataset, viewparent=None): def __init__(self, dataset, viewparent=None):
super(ParticleAnalysis, self).__init__(viewparent) super(ParticleAnalysis, self).__init__(viewparent)
self.resize(1680, 1050) # self.resize(1680, 1050)
self.setWindowTitle('Results of polymer analysis') self.setWindowTitle('Results of polymer analysis')
self.layout = QtWidgets.QHBoxLayout() self.layout = QtWidgets.QHBoxLayout()
self.widget = QtWidgets.QWidget() self.widget = QtWidgets.QWidget()
...@@ -202,8 +202,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -202,8 +202,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
self.layout.addLayout(self.menuLayout) self.layout.addLayout(self.menuLayout)
self.layout.addLayout(viewLayout) self.layout.addLayout(viewLayout)
self.createActions() self.createActions()
self.createMenus() self.createMenus()
...@@ -220,40 +218,45 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -220,40 +218,45 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
self.loadSpectraAct = QtWidgets.QAction("Load &Spectra", self) self.loadSpectraAct = QtWidgets.QAction("Load &Spectra", self)
self.loadSpectraAct.triggered.connect(self.initializeSpecPlot) self.loadSpectraAct.triggered.connect(self.initializeSpecPlot)
self.noOverlayAct = QtWidgets.QAction("&No Overlay", self) self.databaseAct = QtWidgets.QAction("&ManageDatabase", self)
self.selOverlayAct = QtWidgets.QAction("&Selected Overlay", self) self.databaseAct.triggered.connect(self.launchDBManager)
self.fullOverlayAct = QtWidgets.QAction("&Full Overlay", self)
self.expExcelAct= QtWidgets.QAction("Export &Excel List", self)
self.expExcelAct.setDisabled(True)
self.expExcelAct.triggered.connect(self.exportToExcel)
self.expSQLAct = QtWidgets.QAction("Export to &SQL Database", self)
self.expSQLAct.setDisabled(True)
self.expSQLAct.triggered.connect(self.exportToSQL)
self.getAndActivateActionsFromGepardMain()
def getAndActivateActionsFromGepardMain(self):
gepard = self.viewparent.imparent
self.noOverlayAct = gepard.noOverlayAct
self.selOverlayAct = gepard.selOverlayAct
self.fullOverlayAct = gepard.fullOverlayAct
self.transpAct = QtWidgets.QAction("&Transparent Overlay", self) self.transpAct = gepard.transpAct
self.transpAct.triggered.connect(self.updateContourColors) self.transpAct.triggered.connect(self.updateContourColors)
self.hideLabelAct = QtWidgets.QAction('&Hide Polymer Numbers', self) self.hideLabelAct = gepard.hideLabelAct
self.hideLabelAct.triggered.connect(self.show_hide_labels) self.hideLabelAct.triggered.connect(self.show_hide_labels)
self.darkenAct = QtWidgets.QAction("&Darken Image", self) self.darkenAct = gepard.darkenAct
self.darkenAct.triggered.connect(self.darkenBackground) self.darkenAct.triggered.connect(self.darkenBackground)
for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct]: for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct]:
act.setCheckable(True) act.setCheckable(True)
self.fullOverlayAct.setChecked(True) self.fullOverlayAct.setChecked(True)
self.seedAct = QtWidgets.QAction("&Set Color Seed", self) self.seedAct = gepard.seedAct
self.seedAct.triggered.connect(self.updateColorSeed) self.seedAct.triggered.connect(self.updateColorSeed)
self.removeTinyParticlesAct = QtWidgets.QAction("&Remove not unknown Particles < 1 µm") for act in [self.noOverlayAct, self.selOverlayAct, self.fullOverlayAct, self.hideLabelAct, self.transpAct, self.darkenAct, self.seedAct]:
self.removeTinyParticlesAct.triggered.connect(self.removeTinyParticles) act.setDisabled(False)
self.databaseAct = QtWidgets.QAction("&ManageDatabase", self)
self.databaseAct.triggered.connect(self.launchDBManager)
self.expExcelAct= QtWidgets.QAction("Export &Excel List", self)
self.expExcelAct.setDisabled(True)
self.expExcelAct.triggered.connect(self.exportToExcel)
self.expSQLAct = QtWidgets.QAction("Export to &SQL Database", self)
self.expSQLAct.setDisabled(True)
self.expSQLAct.triggered.connect(self.exportToSQL)
def createMenus(self): def createMenus(self):
self.importMenu = QtWidgets.QMenu("&Import Spectra and Results") self.importMenu = QtWidgets.QMenu("&Import Spectra and Results")
self.importMenu.addActions([self.loadSpectraAct, self.loadTrueMatchAct]) self.importMenu.addActions([self.loadSpectraAct, self.loadTrueMatchAct])
...@@ -272,7 +275,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -272,7 +275,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
self.dispMenu.addActions([self.transpAct, self.hideLabelAct, self.darkenAct, self.seedAct]) self.dispMenu.addActions([self.transpAct, self.hideLabelAct, self.darkenAct, self.seedAct])
self.toolMenu = QtWidgets.QMenu("&Tools") self.toolMenu = QtWidgets.QMenu("&Tools")
self.toolMenu.addAction(self.removeTinyParticlesAct)
self.toolMenu.addAction(self.databaseAct) self.toolMenu.addAction(self.databaseAct)
self.exportMenu = QtWidgets.QMenu("&Export", self) self.exportMenu = QtWidgets.QMenu("&Export", self)
...@@ -308,7 +310,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -308,7 +310,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
del self.importWindow del self.importWindow
self.importWindow = LoadTrueMatchResults(self.particleContainer, self) self.importWindow = LoadTrueMatchResults(self.particleContainer, self)
# self.importWindow.exec()
@QtCore.pyqtSlot() @QtCore.pyqtSlot()
def applyHQIThresholdToResults(self): def applyHQIThresholdToResults(self):
...@@ -452,11 +453,9 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -452,11 +453,9 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
self.sizeHist_ax.figure.canvas.draw() self.sizeHist_ax.figure.canvas.draw()
def updateSpecPlot(self): def updateSpecPlot(self):
particleSize = np.round(self.particleContainer.getSizeOfParticleByIndex(self.currentParticleIndex))
hqi = self.particleContainer.getHQIOfSpectrumIndex(self.currentSpectrumIndex) hqi = self.particleContainer.getHQIOfSpectrumIndex(self.currentSpectrumIndex)
assignment = self.particleContainer.getParticleAssignmentByIndex(self.currentParticleIndex) assignment = self.particleContainer.getParticleAssignmentByIndex(self.currentParticleIndex)
color = self.particleContainer.getParticleColorByIndex(self.currentParticleIndex) self.specPlot.updateParticleSpectrum(self.currentSpectrumIndex, assignment, hqi)
self.specPlot.updateParticleSpectrum(self.currentSpectrumIndex, assignment, particleSize, hqi, color)
if self.refSelector.isEnabled() and self.refSelector.currentText() != '': if self.refSelector.isEnabled() and self.refSelector.currentText() != '':
refID = self.dbWin.activeDatabase.spectraNames.index(self.refSelector.currentText()) refID = self.dbWin.activeDatabase.spectraNames.index(self.refSelector.currentText())
...@@ -574,39 +573,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow): ...@@ -574,39 +573,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
self.dataset.colorSeed = text self.dataset.colorSeed = text
self.updateHistogramsAndContours() self.updateHistogramsAndContours()
def removeTinyParticles(self):
indices = []
for particle in self.particleContainer.particles:
if particle.getParticleAssignment() != 'unknown' and particle.getParticleSize() < 1:
indices.append(particle.index)
indices = sorted(indices, reverse=True)
numIndices = len(indices)
for index, partIndex in enumerate(indices):
self.setWidgetsToNewParticleIndex(partIndex)
assignment = self.particleContainer.getParticleAssignmentByIndex(partIndex)
specIndices = self.particleContainer.getSpectraIndicesOfParticle(partIndex)
self.viewparent.highLightContour(partIndex)
self.viewparent.centerOnRamanIndex(specIndices[0])
reply = QtWidgets.QMessageBox.question(self, f'Particle {index+1} of {numIndices}',
f"Do you want to remove that particle? Type = {assignment}",
QtWidgets.QMessageBox.Yes |
QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.No)
if reply == QtWidgets.QMessageBox.Yes:
self.viewparent.removeParticleContour(partIndex)
self.particleContainer.removeParticles([partIndex])
elif reply == QtWidgets.QMessageBox.Cancel:
self.particleContainer.resetParticleIndices()
self.viewparent.resetContourIndices()
return
self.particleContainer.resetParticleIndices()
self.viewparent.resetContourIndices()
self.updateHistogramsAndContours()
def show_hide_labels(self): def show_hide_labels(self):
hidden = self.hideLabelAct.isChecked() hidden = self.hideLabelAct.isChecked()
for scanIndicator in self.viewparent.ramanscanitems: for scanIndicator in self.viewparent.ramanscanitems:
......
...@@ -70,6 +70,7 @@ class LoadTrueMatchResults(QtWidgets.QWidget): ...@@ -70,6 +70,7 @@ class LoadTrueMatchResults(QtWidgets.QWidget):
def show3FlagsReview(self): def show3FlagsReview(self):
self.editEntryWindow = ModifyManualEdits(self, self.manualPolymers, self.manualAdditives) self.editEntryWindow = ModifyManualEdits(self, self.manualPolymers, self.manualAdditives)
self.setDisabled(True)
self.editEntryWindow.show() self.editEntryWindow.show()
def getImportFiles(self): def getImportFiles(self):
......
...@@ -19,17 +19,14 @@ along with this program, see COPYING. ...@@ -19,17 +19,14 @@ along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>. If not, see <https://www.gnu.org/licenses/>.
""" """
import numpy as np import numpy as np
from viewitems import SegmentationContour, RamanScanIndicator
from .particleCharacterization import getParticleColor
class Particle(object): class Particle(object):
def __init__(self): def __init__(self):
super(Particle, self).__init__() super(Particle, self).__init__()
self.index = None self.index = None
self.longSize_ellipse = np.nan self.longSize = np.nan
self.shortSize_ellipse = np.nan self.shortSize = np.nan
self.longSize_box = np.nan self.height = None
self.shortSize_box = np.nan
self.area = None self.area = None
self.contour = None self.contour = None
self.measurements = [] self.measurements = []
...@@ -78,26 +75,18 @@ class Particle(object): ...@@ -78,26 +75,18 @@ class Particle(object):
return assignments[indexOfHighestHQI] return assignments[indexOfHighestHQI]
def getParticleSize(self): def getParticleSize(self):
if not np.isnan(self.longSize_ellipse): if np.isnan(self.longSize):
size = self.longSize_ellipse
elif not np.isnan(self.longSize_box):
size = self.longSize_box
else:
print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}') print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}')
raise ValueError raise ValueError
assert size is not None, f'Error, size of particle {self.index} is None' else:
return size return self.longSize
def getShortParticleSize(self): def getShortParticleSize(self):
if not np.isnan(self.shortSize_ellipse): if np.isnan(self.shortSize):
size = self.shortSize_ellipse
elif not np.isnan(self.shortSize_box):
size = self.shortSize_box
else:
print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}') print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}')
raise ValueError raise ValueError
assert size is not None, f'Error, short size of particle {self.index} is None' else:
return size return self.shortSize
def getNumberOfMeasurements(self): def getNumberOfMeasurements(self):
return len(self.measurements) return len(self.measurements)
...@@ -119,10 +108,7 @@ class Particle(object): ...@@ -119,10 +108,7 @@ class Particle(object):
def applyHQITresholdToMeasurements(self, minHQI): def applyHQITresholdToMeasurements(self, minHQI):
for measurement in self.measurements: for measurement in self.measurements:
measurement.applyHQIThreshold(minHQI) measurement.applyHQIThreshold(minHQI)
def recreateViewItem(self):
self.viewItem = SegmentationContour()
class Measurement(object): class Measurement(object):
def __init__(self): def __init__(self):
......
...@@ -24,66 +24,44 @@ import numpy as np ...@@ -24,66 +24,44 @@ import numpy as np
import cv2 import cv2
from copy import deepcopy from copy import deepcopy
def getContourStatsWithPixelScale(cnt, pixelscale): from .particleClassification.colorClassification import ColorClassifier
long, short, longellipse, shortellipse, area = getContourStats(cnt) from .particleClassification.shapeClassification import ShapeClassifier
return long*pixelscale, short*pixelscale, longellipse*pixelscale, shortellipse*pixelscale, area*pixelscale**2 from segmentation import closeHolesOfSubImage
from errors import NotConnectedContoursError, InvalidParticleError
def getContourStats(cnt):
##characterize particle class ParticleStats(object):
longellipse, shortellipse = np.nan, np.nan longSize = None
shortSize = None
if cnt.shape[0] >= 5: ##at least 5 points required for ellipse fitting... height = None
ellipse = cv2.fitEllipse(cnt) area = None
shortellipse, longellipse = ellipse[1] shape = None
color = None
def particleIsValid(particle):
if particle.longSize == 0 or particle.shortSize == 0:
return False
if cv2.contourArea(particle.contour) == 0:
return False
return True
rect = cv2.minAreaRect(cnt) def getParticleStatsWithPixelScale(cnt, pixelscale, fullimage, dataset):
long, short = rect[1] newStats = ParticleStats()
if short>long:
long, short = short, long
area = cv2.contourArea(cnt)
return long, short, longellipse, shortellipse, area newStats.longSize, newStats.shortSize, newStats.area = getContourStats(cnt)
newStats.longSize *= pixelscale
class ColorRangeHSV(object): newStats.shortSize *= pixelscale
def __init__(self, name, hue, hue_tolerance, min_sat, max_sat): newStats.area *= (pixelscale**2)
self.name = name
self.minHue = hue-hue_tolerance/2 newStats.height = getParticleHeight(cnt, dataset)
self.maxHue = hue+hue_tolerance/2 print('newHeight =', newStats.height)
self.minSat = min_sat newStats.shape = getParticleShape(cnt, newStats.height)
self.maxSat = max_sat
def containsHSV(self, hsv): partImg = getParticleImageFromFullimage(cnt, fullimage)
hue = hsv[0] newStats.color = getParticleColor(partImg)
sat = hsv[1]
if self.minHue <= hue <= self.maxHue and self.minSat <= sat <= self.maxSat:
return True
else:
if self.name != 'white':
return False
else:
if sat < 128 and hsv[2] > 70:
return True
class ColorClassifier(object):
def __init__(self):
hue_tolerance = 30
self.colors = [ColorRangeHSV('yellow', 30, hue_tolerance, 30, 255),
ColorRangeHSV('blue', 120, hue_tolerance, 80, 255),
ColorRangeHSV('red', 180, hue_tolerance, 50, 255),
ColorRangeHSV('red', 0, hue_tolerance, 50, 255),
ColorRangeHSV('green', 70, hue_tolerance, 50, 255),
ColorRangeHSV('white', 128, 256, 0, 50)]
def classifyColor(self, meanHSV): return newStats
result = 'non-determinable'
for color in self.colors:
if color.containsHSV(meanHSV):
result = color.name
break
return result
def getParticleColor(imgRGB, colorClassifier=None): def getParticleColor(imgRGB, colorClassifier=None):
img = cv2.cvtColor(imgRGB, cv2.COLOR_RGB2HSV_FULL) img = cv2.cvtColor(imgRGB, cv2.COLOR_RGB2HSV_FULL)
...@@ -93,6 +71,44 @@ def getParticleColor(imgRGB, colorClassifier=None): ...@@ -93,6 +71,44 @@ def getParticleColor(imgRGB, colorClassifier=None):
color = colorClassifier.classifyColor(meanHSV) color = colorClassifier.classifyColor(meanHSV)
return color return color
def getParticleShape(contour, particleHeight, shapeClassifier=None):
if shapeClassifier is None:
shapeClassifier = ShapeClassifier()
try:
shape = shapeClassifier.classifyShape(contour, particleHeight)
except InvalidParticleError:
raise
return shape
def getParticleHeight(contour, dataset):
zimg = getParticleImageFromFullimage(contour, dataset.getZvalImg())
if zimg.shape[0] == 0 or zimg.shape[1] == 0:
raise InvalidParticleError
zimg = cv2.medianBlur(zimg, 5)
avg_ZValue = np.mean(zimg[zimg > 0])
if np.isnan(avg_ZValue): #i.e., only zeros in zimg
avg_ZValue = 0
z0, z1 = dataset.zpositions.min(), dataset.zpositions.max()
height = avg_ZValue/255.*(z1-z0) + z0
return height
def getContourStats(cnt):
##characterize particle
if cnt.shape[0] >= 5: ##at least 5 points required for ellipse fitting...
ellipse = cv2.fitEllipse(cnt)
short, long = ellipse[1]
else:
rect = cv2.minAreaRect(cnt)
long, short = rect[1]
if short>long:
long, short = short, long
area = cv2.contourArea(cnt)
return long, short, area
def mergeContours(contours): def mergeContours(contours):
img, xmin, ymin, padding = contoursToImg(contours) img, xmin, ymin, padding = contoursToImg(contours)
return imgToCnt(img, xmin, ymin, padding) return imgToCnt(img, xmin, ymin, padding)
...@@ -115,13 +131,15 @@ def getParticleImageFromFullimage(contour, fullimage): ...@@ -115,13 +131,15 @@ def getParticleImageFromFullimage(contour, fullimage):
img = np.array(img, dtype = np.uint8) img = np.array(img, dtype = np.uint8)
return img return img
def contoursToImg(contours, padding=2): 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 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:
raise InvalidParticleError
img = np.zeros((rangey, rangex)) img = np.zeros((rangey, rangex))
for curCnt in contourCopy: for curCnt in contourCopy:
...@@ -130,42 +148,44 @@ def contoursToImg(contours, padding=2): ...@@ -130,42 +148,44 @@ def contoursToImg(contours, padding=2):
curCnt[i][0][1] -= ymin-padding curCnt[i][0][1] -= ymin-padding
cv2.drawContours(img, [curCnt], -1, 255, -1) cv2.drawContours(img, [curCnt], -1, 255, -1)
cv2.drawContours(img, [curCnt], -1, 255, 1) cv2.drawContours(img, [curCnt], -1, 255, 1)
img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3)))) img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3))))
return img, xmin, ymin, padding return img, xmin, ymin, padding
def imgToCnt(img, xmin, ymin, padding): def imgToCnt(img, xmin, ymin, padding=0):
def getSimpleContour(img): def getContour(img, flag):
if cv2.__version__ > '3.5': if cv2.__version__ > '3.5':
contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, flag)
else: else:
temp, contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_EXTERNAL, flag)
if len(contour)>1: if len(contours) == 0: #i.e., no contour found
raise NotConnectedContoursError raise InvalidParticleError
elif len(contours) == 1: #i.e., exactly one contour found
contour = contours[0]
else: #i.e., multiple contours found
contour = getLargestContour(contours)
return contour return contour
def getFullContour(img): def getLargestContour(contours):
if cv2.__version__ > '3.5': areas = []
contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) for contour in contours:
else: areas.append(cv2.contourArea(contour))
temp, contour, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) maxIndex = areas.index(max(areas))
print(f'{len(contours)} contours found, getting the largest one. Areas are: {areas}, taking contour at index {maxIndex}')
if len(contour)>1: