"""
Module for LoG blob detection.
Detect "blobs" in each frame of an AFM image stack using the Laplacian-of-Gaussian
method. Provides automatic multi-scale blob detection and optional radius estimation.
"""
from typing import Any, Optional
from playnano.analysis.base import AnalysisModule
[docs]
class LoGBlobDetectionModule(AnalysisModule):
"""
Detect blobs in AFM image stacks using the Laplacian-of-Gaussian (LoG) method.
This module applies multi-scale blob detection to each frame in an AFM image stack
using the Laplacian-of-Gaussian algorithm from `skimage.feature.blob_log`. It
supports automatic scale selection and optional estimation of blob radii.
Methods
-------
run(stack, previous_results=None, *, min_sigma=1.0, max_sigma=5.0, num_sigma=10,
threshold=0.1, overlap=0.5, include_radius=True)
Detects blobs in each frame of the AFM image stack and returns per-frame
features and a summary.
Version
-------
0.1.0
Examples
--------
>>> module = LoGBlobDetectionModule()
>>> result = module.run(stack, min_sigma=1.0, max_sigma=5.0, num_sigma=10)
>>> result['summary']['total_blobs']
42
"""
version = "0.1.0"
@property
def name(self) -> str:
"""
Name of the analysis module.
Returns
-------
str
The string identifier for this module: "log_blob_detection".
"""
return "log_blob_detection"
[docs]
def run(
self,
stack,
previous_results: Optional[dict[str, Any]] = None,
*,
min_sigma: float = 1.0,
max_sigma: float = 5.0,
num_sigma: int = 10,
threshold: float = 0.1,
overlap: float = 0.5,
include_radius: bool = True,
) -> dict[str, Any]:
"""
Detect "blobs" in each frame via a Laplacian-of-Gaussian filter.
Parameters
----------
stack : AFMImageStack
Must have stack.data of shape (n_frames, H, W).
min_sigma, max_sigma : float
Parameters passed to skimage.feature.blob_log.
num_sigma : int
Parameter passed to skimage.feature.blob_log.
threshold : float
Absolute intensity threshold for LoG response.
overlap : float
If two detected blobs overlap more than this fraction,
only the larger is kept.
include_radius : bool
If True, append the estimated blob radius in each feature-dict.
Returns
-------
dict[str, Any]
Dictionary with keys:
- features_per_frame : list of list of dict
Per-frame list of detected blobs. Each dict contains:
- frame_timestamp : float
- y, x : float
- sigma : float
- radius : float, optional (if include_radius=True)
- summary : dict
Aggregate metrics:
- total_frames : int
- total_blobs : int
- avg_blobs_per_frame : float
"""
from skimage.feature import blob_log
n_frames, H, W = stack.data.shape
features_per_frame: list[list[dict]] = []
total = 0
for i in range(n_frames):
frame = stack.data[i]
ts = stack.time_for_frame(i)
blobs = blob_log(
frame,
min_sigma=min_sigma,
max_sigma=max_sigma,
num_sigma=num_sigma,
threshold=threshold,
overlap=overlap,
)
# blob_log returns Nx3 array: (y, x, sigma)
feats = []
for y, x, sigma in blobs:
d: dict[str, Any] = {
"frame_timestamp": ts,
"y": float(y),
"x": float(x),
"sigma": float(sigma),
}
if include_radius:
d["radius"] = float(sigma * (2**0.5))
feats.append(d)
features_per_frame.append(feats)
total += len(feats)
summary = {
"total_frames": n_frames,
"total_blobs": total,
"avg_blobs_per_frame": total / n_frames if n_frames else 0,
}
return {
"features_per_frame": features_per_frame,
"summary": summary,
}