phenotypic.util#

A module for useful utility operations and functions that don’t fit into a specific category.

Functions

geometric_median

Compute geometric median of a set of points.

Classes

ImageMetricsCalculator

Computes image quality metrics from detection matrices.

NoiseMetrics

Noise-related metrics from detection matrix analysis.

ContrastMetrics

Contrast-related metrics from detection matrix analysis.

StructureMetrics

Structure tensor and ridge detection metrics.

BackgroundMetrics

Background uniformity metrics.

QualityScores

Normalized 0-1 quality scores for radar chart visualization.

class phenotypic.util.BackgroundMetrics[source]

Bases: TypedDict

Background uniformity metrics.

clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
background_estimate: ndarray | None
mean_gradient: float
nonuniformity_ratio: float
class phenotypic.util.ContrastMetrics[source]

Bases: TypedDict

Contrast-related metrics from detection matrix analysis.

clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
dynamic_range: float
michelson: float
p1: float
p99: float
rms_contrast: float
class phenotypic.util.ImageMetricsCalculator(detect_mat: numpy.ndarray)[source]

Bases: object

Computes image quality metrics from detection matrices.

This class extracts noise, contrast, structure, and background metrics used to assess image quality for colony detection. It provides the computational core shared by both matplotlib-based and Panel-based diagnostics visualizations.

Parameters:

detect_mat (np.ndarray) – 2D grayscale detection matrix (typically image.detect_mat[:]).

bit_depth

Image bit depth (8 or 16) inferred from max intensity.

Examples

>>> from phenotypic.data import load_synth_yeast_plate
>>> from phenotypic.util.image_metrics import ImageMetricsCalculator
>>> image = load_synth_yeast_plate()
>>> calc = ImageMetricsCalculator(image.detect_mat[:])
>>> noise = calc.compute_noise_metrics()
>>> print(f"SNR: {noise['snr']:.2f}")
SNR: ...
>>> contrast = calc.compute_contrast_metrics()
>>> print(f"RMS contrast: {contrast['rms_contrast']:.3f}")
RMS contrast: ...
compute_all(structure_sigma: float = 1.5, ridge_scales: list[float] | None = None, ridge_method: Literal['meijering', 'frangi', 'hessian'] = 'meijering', background_sigma: float = 50.0, include_non_serializable: bool = False) dict[str, Any][source]

Compute all metrics and return comprehensive dict.

Parameters:
  • structure_sigma (float) – Sigma for structure tensor computation.

  • ridge_scales (list[float] | None) – Scales for ridge detection.

  • ridge_method (Literal['meijering', 'frangi', 'hessian']) – Ridge detection algorithm.

  • background_sigma (float) – Sigma for background estimation.

  • include_non_serializable (bool) – If False, removes numpy arrays from output.

Returns:

bit_depth, noise, contrast, structure, background, quality_scores, interpretations, recommendations.

Return type:

Dict with keys

compute_background_metrics(sigma: float = 50.0) BackgroundMetrics[source]

Compute background uniformity metrics.

Parameters:

sigma (float) – Sigma for background estimation (Gaussian smoothing).

Returns:

Dictionary with nonuniformity_ratio, mean_gradient, and background_estimate.

Return type:

BackgroundMetrics

compute_contrast_metrics() ContrastMetrics[source]

Compute contrast-related metrics (parameter-free).

Returns:

Dictionary with rms_contrast, michelson, dynamic_range, p1, and p99.

Return type:

ContrastMetrics

compute_local_contrast(img: np.ndarray | None = None, window_size: int = 15) np.ndarray[source]

Compute local Weber contrast map.

Parameters:
  • img (np.ndarray | None) – Input image array. If None, uses the stored detection matrix.

  • window_size (int) – Size of local window.

Returns:

Local contrast map.

Return type:

np.ndarray

compute_local_variance(img: np.ndarray | None = None, window_size: int = 15) np.ndarray[source]

Compute local variance map.

Parameters:
  • img (np.ndarray | None) – Input image array. If None, uses the stored detection matrix.

  • window_size (int) – Size of local window.

Returns:

Local variance map.

Return type:

np.ndarray

compute_noise_metrics() NoiseMetrics[source]

Compute noise-related metrics (parameter-free).

Returns:

Dictionary with SNR, sigma_mad, and correlation_length.

Return type:

NoiseMetrics

compute_psd(img: np.ndarray | None = None) tuple[np.ndarray, np.ndarray][source]

Compute radially averaged power spectral density.

Parameters:

img (np.ndarray | None) – Input image array. If None, uses the stored detection matrix.

Returns:

Tuple of (frequencies, radial_psd).

Return type:

tuple[np.ndarray, np.ndarray]

compute_quality_scores(noise: NoiseMetrics, contrast: ContrastMetrics, structure: StructureMetrics, background: BackgroundMetrics) QualityScores[source]

Compute normalized 0-1 quality scores for radar chart.

Parameters:
  • noise (NoiseMetrics) – Noise metrics from compute_noise_metrics().

  • contrast (ContrastMetrics) – Contrast metrics from compute_contrast_metrics().

  • structure (StructureMetrics) – Structure metrics from compute_structure_metrics().

  • background (BackgroundMetrics) – Background metrics from compute_background_metrics().

Returns:

Dictionary of quality scores (SNR, Contrast, Coherence, Uniformity, Sharpness), all normalized to [0, 1].

Return type:

QualityScores

compute_structure_metrics(sigma: float = 1.5, scales: list[float] | None = None, ridge_method: Literal['meijering', 'frangi', 'hessian'] = 'meijering') StructureMetrics[source]

Compute structure tensor and ridge detection metrics.

Parameters:
  • sigma (float) – Sigma for structure tensor computation.

  • scales (list[float] | None) – List of scales for multiscale ridge detection. Defaults to [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0].

  • ridge_method (Literal['meijering', 'frangi', 'hessian']) – Method for ridge detection: “meijering” (default), “frangi”, or “hessian”.

Returns:

Dictionary with mean_coherence, optimal_scale, peak_response, ridge_responses, scales, ridge_method, and coherence_map.

Return type:

StructureMetrics

static generate_interpretation(section: Literal['noise', 'contrast', 'structure', 'background'], metrics: dict[str, Any]) str[source]

Generate human-readable interpretation text.

Parameters:
  • section (Literal['noise', 'contrast', 'structure', 'background']) – Section name (“noise”, “contrast”, “structure”, “background”).

  • metrics (dict[str, Any]) – Computed metrics for the section.

Returns:

Interpretation text string.

Return type:

str

static generate_recommendations(noise: NoiseMetrics, contrast: ContrastMetrics, structure: StructureMetrics, background: BackgroundMetrics) list[str][source]

Generate actionable preprocessing recommendations.

Parameters:
  • noise (NoiseMetrics) – Noise metrics from compute_noise_metrics().

  • contrast (ContrastMetrics) – Contrast metrics from compute_contrast_metrics().

  • structure (StructureMetrics) – Structure metrics from compute_structure_metrics().

  • background (BackgroundMetrics) – Background metrics from compute_background_metrics().

Returns:

List of recommendation strings.

Return type:

list[str]

property bit_depth: int

Image bit depth (8 or 16) inferred from max intensity.

property max_intensity: float

Maximum intensity value based on image bit depth.

class phenotypic.util.NoiseMetrics[source]

Bases: TypedDict

Noise-related metrics from detection matrix analysis.

clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
correlation_length: float
sigma_mad: float
snr: float
class phenotypic.util.QualityScores[source]

Bases: TypedDict

Normalized 0-1 quality scores for radar chart visualization.

clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
Coherence: float
Contrast: float
SNR: float
Sharpness: float
Uniformity: float
class phenotypic.util.StructureMetrics[source]

Bases: TypedDict

Structure tensor and ridge detection metrics.

clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from dict/iterable E and F.

If E is present and has a .keys() method, then does: for k in E: D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
coherence_map: ndarray | None
mean_coherence: float
optimal_scale: float
peak_response: float
ridge_method: str
ridge_responses: list[float]
scales: list[float]
phenotypic.util.geometric_median(points: ndarray, eps: float = 1e-06, method: Literal['cohen', 'weiszfeld'] = 'cohen', matrix_free: bool | None = None, matrix_free_threshold: int = 100, verbose: bool = True, **kwargs) Tuple[ndarray, Dict][source]

Compute geometric median of a set of points.

Main interface supporting both Cohen et al. (2016) nearly-linear time algorithm and classical Weiszfeld algorithm.

Parameters:
  • points (ndarray) – Data points, shape (n, d)

  • eps (float) – Target accuracy for (1 + eps)-approximation

  • method (Literal['cohen', 'weiszfeld']) – Algorithm to use: - ‘cohen’: Cohen et al. (2016) O(nd log³(n/ε)) algorithm [default] - ‘weiszfeld’: Classical Weiszfeld O(?) algorithm

  • matrix_free (bool | None) – For Cohen method, whether to use matrix-free Hessian. If None, automatically decides based on dimension.

  • matrix_free_threshold (int) – Dimension threshold for matrix-free mode

  • verbose (bool) – Whether to print progress information

  • **kwargs – Additional method-specific arguments

Returns:

Geometric median point, shape (d,) info: Dictionary with algorithm statistics:

  • ’iterations’: Number of iterations performed

  • ’objective’: Final objective value f(x)

  • ’converged’: Whether algorithm converged

  • ’method’: Algorithm used

  • Additional method-specific statistics

Return type:

median

Raises:

ValueError – If method is invalid or points array has wrong shape

Examples

>>> # Cohen method (recommended for large problems)
>>> points = np.random.randn(10000, 50)
>>> median, info = geometric_median(points, method='cohen', eps=0.01)
>>> print(f"Converged: {info['converged']}")
>>> print(f"Objective: {info['objective']:.6f}")
>>> # Weiszfeld method (simple, good for small problems)
>>> points = np.random.randn(100, 3)
>>> median, info = geometric_median(points, method='weiszfeld', eps=1e-6)
>>> # Force matrix-free for high-dimensional problems
>>> points = np.random.randn(1000, 500)
>>> median, info = geometric_median(points, method='cohen',
...                                 matrix_free=True, eps=0.1)

References

Cohen, M. B., Lee, Y. T., Miller, G., Pachocki, J., & Sidford, A. (2016). Geometric median in nearly linear time. STOC 2016.