# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
"""
Blob detection computation module
---------------------------------
"""
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
# Note:
# ----
# All dataset classes must also be imported in the cdl.computation.param module.
from __future__ import annotations
import guidata.dataset as gds
import cdl.algorithms.image as alg
from cdl.computation.image import calc_resultshape
from cdl.config import _
from cdl.obj import ImageObj, ResultShape, ShapeTypes
[docs]
class GenericDetectionParam(gds.DataSet):
"""Generic detection parameters"""
threshold = gds.FloatItem(
_("Relative threshold"),
default=0.5,
min=0.1,
max=0.9,
help=_(
"Detection threshold, relative to difference between "
"data maximum and minimum"
),
)
[docs]
class Peak2DDetectionParam(GenericDetectionParam):
"""Peak detection parameters"""
size = gds.IntItem(
_("Neighborhoods size"),
default=10,
min=1,
unit="pixels",
help=_(
"Size of the sliding window used in maximum/minimum filtering algorithm"
),
)
create_rois = gds.BoolItem(_("Create regions of interest"), default=True)
[docs]
def compute_peak_detection(
image: ImageObj, p: Peak2DDetectionParam
) -> ResultShape | None:
"""Compute 2D peak detection
with :py:func:`cdl.algorithms.image.get_2d_peaks_coords`
Args:
imageOutput: input image
p: parameters
Returns:
Peak coordinates
"""
return calc_resultshape(
"peak", "point", image, alg.get_2d_peaks_coords, p.size, p.threshold
)
[docs]
class ContourShapeParam(GenericDetectionParam):
"""Contour shape parameters"""
shapes = (
("ellipse", _("Ellipse")),
("circle", _("Circle")),
("polygon", _("Polygon")),
)
# The following item is used to store the 'shape type' and is implicitly accessed by
# the `cdl.core.gui.processor.base.BaseProcessor.compute_10` method. The keys of the
# item choices (i.e. the first element of each tuple of `shapes`) must match the
# names of the `cdl.core.model.base.ShapeTypes` (when uppercased).
assert {shape[0].upper() for shape in shapes}.issubset(
set(ShapeTypes.__members__.keys())
)
shape = gds.ChoiceItem(_("Shape"), shapes, default="ellipse")
[docs]
def compute_contour_shape(image: ImageObj, p: ContourShapeParam) -> ResultShape | None:
"""Compute contour shape fit
with :py:func:`cdl.algorithms.image.get_contour_shapes`"""
return calc_resultshape(
"contour", p.shape, image, alg.get_contour_shapes, p.shape, p.threshold
)
[docs]
class BaseBlobParam(gds.DataSet):
"""Base class for blob detection parameters"""
min_sigma = gds.FloatItem(
"σ<sub>min</sub>",
default=1.0,
unit="pixels",
min=0,
nonzero=True,
help=_(
"The minimum standard deviation for Gaussian Kernel. "
"Keep this low to detect smaller blobs."
),
)
max_sigma = gds.FloatItem(
"σ<sub>max</sub>",
default=30.0,
unit="pixels",
min=0,
nonzero=True,
help=_(
"The maximum standard deviation for Gaussian Kernel. "
"Keep this high to detect larger blobs."
),
)
threshold_rel = gds.FloatItem(
_("Relative threshold"),
default=0.2,
min=0.0,
max=1.0,
help=_("Minimum intensity of blobs."),
)
overlap = gds.FloatItem(
_("Overlap"),
default=0.5,
min=0.0,
max=1.0,
help=_(
"If two blobs overlap by a fraction greater than this value, the "
"smaller blob is eliminated."
),
)
[docs]
class BlobDOGParam(BaseBlobParam):
"""Blob detection using Difference of Gaussian method"""
exclude_border = gds.BoolItem(
_("Exclude border"),
default=True,
help=_("If True, exclude blobs from the border of the image."),
)
[docs]
def compute_blob_dog(image: ImageObj, p: BlobDOGParam) -> ResultShape | None:
"""Compute blobs using Difference of Gaussian method
with :py:func:`cdl.algorithms.image.find_blobs_dog`
Args:
imageOutput: input image
p: parameters
Returns:
Blobs coordinates
"""
return calc_resultshape(
"blob_dog",
"circle",
image,
alg.find_blobs_dog,
p.min_sigma,
p.max_sigma,
p.overlap,
p.threshold_rel,
p.exclude_border,
)
[docs]
class BlobDOHParam(BaseBlobParam):
"""Blob detection using Determinant of Hessian method"""
log_scale = gds.BoolItem(
_("Log scale"),
default=False,
help=_(
"If set intermediate values of standard deviations are interpolated "
"using a logarithmic scale to the base 10. "
"If not, linear interpolation is used."
),
)
[docs]
def compute_blob_doh(image: ImageObj, p: BlobDOHParam) -> ResultShape | None:
"""Compute blobs using Determinant of Hessian method
with :py:func:`cdl.algorithms.image.find_blobs_doh`
Args:
imageOutput: input image
p: parameters
Returns:
Blobs coordinates
"""
return calc_resultshape(
"blob_doh",
"circle",
image,
alg.find_blobs_doh,
p.min_sigma,
p.max_sigma,
p.overlap,
p.log_scale,
p.threshold_rel,
)
[docs]
class BlobLOGParam(BlobDOHParam):
"""Blob detection using Laplacian of Gaussian method"""
exclude_border = gds.BoolItem(
_("Exclude border"),
default=True,
help=_("If True, exclude blobs from the border of the image."),
)
[docs]
def compute_blob_log(image: ImageObj, p: BlobLOGParam) -> ResultShape | None:
"""Compute blobs using Laplacian of Gaussian method
with :py:func:`cdl.algorithms.image.find_blobs_log`
Args:
imageOutput: input image
p: parameters
Returns:
Blobs coordinates
"""
return calc_resultshape(
"blob_log",
"circle",
image,
alg.find_blobs_log,
p.min_sigma,
p.max_sigma,
p.overlap,
p.log_scale,
p.threshold_rel,
p.exclude_border,
)
[docs]
class BlobOpenCVParam(gds.DataSet):
"""Blob detection using OpenCV"""
min_threshold = gds.FloatItem(
_("Min. threshold"),
default=10.0,
min=0.0,
help=_(
"The minimum threshold between local maxima and minima. "
"This parameter does not affect the quality of the blobs, "
"only the quantity. Lower thresholds result in larger "
"numbers of blobs."
),
)
max_threshold = gds.FloatItem(
_("Max. threshold"),
default=200.0,
min=0.0,
help=_(
"The maximum threshold between local maxima and minima. "
"This parameter does not affect the quality of the blobs, "
"only the quantity. Lower thresholds result in larger "
"numbers of blobs."
),
)
min_repeatability = gds.IntItem(
_("Min. repeatability"),
default=2,
min=1,
help=_(
"The minimum number of times a blob needs to be detected "
"in a sequence of images to be considered valid."
),
)
min_dist_between_blobs = gds.FloatItem(
_("Min. distance between blobs"),
default=10.0,
min=0.0,
nonzero=True,
help=_(
"The minimum distance between two blobs. If blobs are found "
"closer together than this distance, the smaller blob is removed."
),
)
_prop_col = gds.ValueProp(False)
filter_by_color = gds.BoolItem(
_("Filter by color"),
default=True,
help=_("If true, the image is filtered by color instead of intensity."),
).set_prop("display", store=_prop_col)
blob_color = gds.IntItem(
_("Blob color"),
default=0,
help=_(
"The color of the blobs to detect (0 for dark blobs, 255 for light blobs)."
),
).set_prop("display", active=_prop_col)
_prop_area = gds.ValueProp(False)
filter_by_area = gds.BoolItem(
_("Filter by area"),
default=True,
help=_("If true, the image is filtered by blob area."),
).set_prop("display", store=_prop_area)
min_area = gds.FloatItem(
_("Min. area"),
default=25.0,
min=0.0,
help=_("The minimum blob area."),
).set_prop("display", active=_prop_area)
max_area = gds.FloatItem(
_("Max. area"),
default=500.0,
min=0.0,
help=_("The maximum blob area."),
).set_prop("display", active=_prop_area)
_prop_circ = gds.ValueProp(False)
filter_by_circularity = gds.BoolItem(
_("Filter by circularity"),
default=False,
help=_("If true, the image is filtered by blob circularity."),
).set_prop("display", store=_prop_circ)
min_circularity = gds.FloatItem(
_("Min. circularity"),
default=0.8,
min=0.0,
max=1.0,
help=_("The minimum circularity of the blobs."),
).set_prop("display", active=_prop_circ)
max_circularity = gds.FloatItem(
_("Max. circularity"),
default=1.0,
min=0.0,
max=1.0,
help=_("The maximum circularity of the blobs."),
).set_prop("display", active=_prop_circ)
_prop_iner = gds.ValueProp(False)
filter_by_inertia = gds.BoolItem(
_("Filter by inertia"),
default=False,
help=_("If true, the image is filtered by blob inertia."),
).set_prop("display", store=_prop_iner)
min_inertia_ratio = gds.FloatItem(
_("Min. inertia ratio"),
default=0.6,
min=0.0,
max=1.0,
help=_("The minimum inertia ratio of the blobs."),
).set_prop("display", active=_prop_iner)
max_inertia_ratio = gds.FloatItem(
_("Max. inertia ratio"),
default=1.0,
min=0.0,
max=1.0,
help=_("The maximum inertia ratio of the blobs."),
).set_prop("display", active=_prop_iner)
_prop_conv = gds.ValueProp(False)
filter_by_convexity = gds.BoolItem(
_("Filter by convexity"),
default=False,
help=_("If true, the image is filtered by blob convexity."),
).set_prop("display", store=_prop_conv)
min_convexity = gds.FloatItem(
_("Min. convexity"),
default=0.8,
min=0.0,
max=1.0,
help=_("The minimum convexity of the blobs."),
).set_prop("display", active=_prop_conv)
max_convexity = gds.FloatItem(
_("Max. convexity"),
default=1.0,
min=0.0,
max=1.0,
help=_("The maximum convexity of the blobs."),
).set_prop("display", active=_prop_conv)
[docs]
def compute_blob_opencv(image: ImageObj, p: BlobOpenCVParam) -> ResultShape | None:
"""Compute blobs using OpenCV
with :py:func:`cdl.algorithms.image.find_blobs_opencv`
Args:
imageOutput: input image
p: parameters
Returns:
Blobs coordinates
"""
return calc_resultshape(
"blob_opencv",
"circle",
image,
alg.find_blobs_opencv,
p.min_threshold,
p.max_threshold,
p.min_repeatability,
p.min_dist_between_blobs,
p.filter_by_color,
p.blob_color,
p.filter_by_area,
p.min_area,
p.max_area,
p.filter_by_circularity,
p.min_circularity,
p.max_circularity,
p.filter_by_inertia,
p.min_inertia_ratio,
p.max_inertia_ratio,
p.filter_by_convexity,
p.min_convexity,
p.max_convexity,
)