1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
|
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
"""
Measurement computation module
------------------------------
This module provides tools for extracting quantitative information from images,
such as object centroids, enclosing circles, and region-based statistics.
Main features include:
- Centroid and enclosing circle computation
- Region/property measurements
- Statistical analysis of image regions
These functions are useful for image quantification and morphometric analysis.
"""
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
# Note:
# ----
# - All `guidata.dataset.DataSet` parameter classes must also be imported
# in the `sigima.params` module.
# - All functions decorated by `computation_function` must be imported in the upper
# level `sigima.proc.image` module.
from __future__ import annotations
import numpy as np
from numpy import ma
import sigima.tools.image
from sigima.config import _
from sigima.objects import (
GeometryResult,
ImageObj,
KindShape,
SignalObj,
TableKind,
TableResult,
TableResultBuilder,
)
from sigima.proc.base import new_signal_result
from sigima.proc.decorator import computation_function
from sigima.proc.image.base import compute_geometry_from_obj
# NOTE: Only parameter classes DEFINED in this module should be included in __all__.
# Parameter classes imported from other modules (like sigima.proc.base) should NOT
# be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
# serves as the central API point that imports and re-exports all parameter classes.
__all__ = [
"centroid",
"enclosing_circle",
"horizontal_projection",
"stats",
"vertical_projection",
]
def get_centroid_coords(data: np.ndarray) -> np.ndarray:
"""Return centroid coordinates
with :py:func:`sigima.tools.image.get_centroid_auto`
Args:
data: input data
Returns:
Centroid coordinates
"""
y, x = sigima.tools.image.get_centroid_auto(data)
return np.array([(x, y)])
@computation_function()
def centroid(image: ImageObj) -> GeometryResult | None:
"""Compute centroid
with :py:func:`sigima.tools.image.get_centroid_fourier`
Args:
image: input image
Returns:
Centroid coordinates
"""
return compute_geometry_from_obj(
"centroid", KindShape.MARKER, image, get_centroid_coords
)
def get_enclosing_circle_coords(data: np.ndarray) -> np.ndarray:
"""Return diameter coords for the circle contour enclosing image
values above threshold (FWHM)
Args:
data: input data
Returns:
Diameter coords
"""
x, y, r = sigima.tools.image.get_enclosing_circle(data)
return np.array([[x, y, r]])
@computation_function()
def enclosing_circle(image: ImageObj) -> GeometryResult | None:
"""Compute minimum enclosing circle
with :py:func:`sigima.tools.image.get_enclosing_circle`
Args:
image: input image
Returns:
Diameter coords
"""
return compute_geometry_from_obj(
"enclosing_circle", KindShape.CIRCLE, image, get_enclosing_circle_coords
)
def __calc_snr_without_warning(data: np.ndarray) -> float:
"""Calculate SNR based on <z>/σ(z), ignoring warnings
Args:
data: input data
Returns:
Signal-to-noise ratio
"""
with np.errstate(divide="ignore", invalid="ignore"):
snr = ma.mean(data) / ma.std(data)
return snr
@computation_function()
def stats(obj: ImageObj) -> TableResult:
"""Compute statistics on an image
Args:
obj: input image object
Returns:
Result properties
"""
builder = TableResultBuilder(_("Image statistics"), kind=TableKind.STATISTICS)
builder.add(ma.min, "min")
builder.add(ma.max, "max")
builder.add(ma.mean, "mean")
builder.add(ma.median, "median")
builder.add(ma.std, "std")
builder.add(__calc_snr_without_warning, "snr")
builder.add(ma.ptp, "ptp")
builder.add(ma.sum, "sum")
return builder.compute(obj)
@computation_function()
def horizontal_projection(image: ImageObj) -> SignalObj:
"""Compute the sum of pixel intensities along each col. (projection on the x-axis).
Args:
image: Input image object.
Returns:
Signal object containing the profile.
"""
dst_signal = new_signal_result(
image,
"horizontal_projection",
units=(image.xunit, image.zunit),
labels=(image.xlabel, image.zlabel),
)
x = np.linspace(image.x0, image.x0 + image.width - image.dx, image.data.shape[1])
y = image.data.sum(axis=0, dtype=float)
dst_signal.set_xydata(x, y)
return dst_signal
@computation_function()
def vertical_projection(image: ImageObj) -> SignalObj:
"""Compute the sum of pixel intensities along each row (projection on the y-axis).
Args:
image: Input image object.
Returns:
Signal object containing the profile.
"""
dst_signal = new_signal_result(
image,
"vertical_projection",
units=(image.yunit, image.zunit),
labels=(image.ylabel, image.zlabel),
)
x = np.linspace(image.y0, image.y0 + image.height - image.dy, image.data.shape[0])
y = image.data.sum(axis=1, dtype=float)
dst_signal.set_xydata(x, y)
return dst_signal
|