Image metrics¶
Intensity metrics¶
metrics::intensity contains pure, domain-agnostic calculations over scalar
intensity images. Internally, single.rs holds single-image calculations and
compare.rs holds reference/estimate comparisons. Those implementation
modules are private: every function and result type is available directly from
metrics::intensity.
Every two-image metric calls its inputs reference and estimate. A signed
residual is always estimate - reference. An optional valid_mask has the
same shape as both images; true includes a pixel and false excludes it.
All metrics reject mismatched shapes, non-finite included values, and an empty
valid mask.
Evaluation metrics do not contain losses used to optimize a reconstruction:
optimization objectives remain separate in algorithms::objective because
their gradient and numerical-stability contracts differ from reporting
metrics.
Rust API¶
All comparison functions accept ndarray::ArrayView2<'_, T> with
T: num_traits::ToPrimitive. They convert individual included samples to
f64 for evaluation and return f64; the image data is borrowed, never
copied. This supports ordinary u8, u16, u32, f32, and f64 images.
The stats function summarizes one non-empty &[f64] intensity image.
use fpm_rs::metrics::intensity::{nrmse, psnr, stats};
let image_stats = stats(reference.as_slice().unwrap(), None)?;
let relative_error = nrmse(reference.view(), estimate.view(), None)?;
let quality_db = psnr(reference.view(), estimate.view(), None, 65_535.0)?;
Metric definitions¶
| Function | Definition or convention |
|---|---|
stats |
Mean, population standard deviation, extrema, sum, zeros, and optional saturation count for one image. |
compare_intensity |
Aggregate sums and residual statistics for a reference/estimate pair. |
bias |
Mean signed residual. |
mae, mse, rmse |
Mean absolute, squared, and root mean squared residual. |
relative_l1 |
sum(abs(estimate - reference)) / sum(abs(reference)). |
nrmse |
L2(estimate - reference) / L2(reference). |
amplitude_nrmse |
NRMSE after applying sqrt to both intensities; requires non-negative values. |
correlation |
Pearson correlation; undefined for a constant valid image. |
psnr |
Peak signal-to-noise ratio in dB. data_range is required, finite, and positive; identical inputs produce +∞. |
ssim |
Single-scale SSIM, higher is more similar. Uses an 11×11 Gaussian window with σ=1.5, K1=0.01, and K2=0.03, following Wang, Bovik, Sheikh, and Simoncelli, “Image quality assessment: From error visibility to structural similarity” (2004). |
poisson_deviance |
Summed Poisson deviance for non-negative intensities. A positive epsilon floors estimate intensity. |
mean_poisson_deviance |
Poisson deviance divided by valid-pixel count. |
fitted_gain |
Least-squares gain in estimate ≈ gain × reference. |
relative_l1, nrmse, and fitted_gain are undefined for a zero reference
normalization and return an error rather than silently choosing a scale.
data_range is explicit for PSNR and SSIM because deriving it separately from
each image makes results incomparable between frames and runs. Use a documented
normalization such as 1.0, or the camera's calibrated full-scale value.
For SSIM, a masked local window is included only when all of its 11×11 pixels are valid. Images smaller than the window, or masks without a fully valid window, return an error.
Python API¶
The same functions are available under fpm_rs.metrics:
import fpm_rs as fpm
image_stats = fpm.metrics.stats(reference, saturation_value=65535.0)
summary = fpm.metrics.compare_intensity(reference, estimate, valid_mask=valid_pixels)
score = fpm.metrics.ssim(
reference,
estimate,
valid_mask=valid_pixels,
data_range=65535.0,
)
gain = fpm.metrics.fitted_gain(reference, estimate)
Python accepts any real, two-dimensional NumPy-compatible numeric array,
including transposed and stepped arrays and uint8/uint16 data, and evaluates
it as float64. Existing float64 strides are preserved; dtype conversion may
allocate. valid_mask may also be strided and must be a two-dimensional Boolean
array. The Python bridge copies logical values into Rust-owned arrays before
releasing the GIL; this is not a contiguity-repair copy and applies to both
standard and strided inputs. The Rust ArrayView2 metric API itself borrows
arbitrary layouts without a copy; FFT-based complex metrics additionally
allocate their required transform workspace.
Complex-field metrics¶
metrics::complex_field provides eight focused comparison metrics for complex
images. Each function borrows ndarray::ArrayView2<Complex<T>> inputs, supports
f32 and f64, accumulates in f64, and requires an explicit
ComplexAlignment. An optional Boolean mask uses true to include a pixel;
the same pixels determine both the fitted alignment and the reported metric.
use fpm_rs::metrics::complex_field::{ComplexAlignment, nrmse};
let error = nrmse(
reference.view(),
estimate.view(),
Some(valid_mask.view()),
ComplexAlignment::GlobalPhase,
)?;
For selected reference values r and estimate values e, alignment multiplies
the estimate by a scalar a. All fitted modes minimize
sum(|a e - r|²). Defining c = sum(conj(e) r) and
E = sum(|e|²), the modes are:
| Alignment | Fitted scalar |
|---|---|
None |
a = 1 |
GlobalPhase |
a = c / |c|, constrained to unit magnitude |
Scale |
a = max(real(c) / E, 0), constrained to a non-negative real value |
ComplexGain |
a = c / E, unconstrained complex value |
After alignment, the residual is always a e - r.
| Function | Definition or convention |
|---|---|
bias |
Arithmetic mean of the complex residual. |
mae |
Mean residual magnitude. |
mse, rmse |
Mean squared residual magnitude and its square root. |
relative_l1 |
sum(|a e - r|) / sum(|r|). |
nrmse |
L2(a e - r) / L2(r). |
amplitude_nrmse |
L2(|a e| - |r|) / L2(|r|). |
correlation |
sum(conj(r) a e) / sqrt(sum(|r|²) sum(|a e|²)). |
The correlation convention means that with no alignment, an estimate equal to
reference * exp(i theta) has correlation exp(i theta). Undefined zero
normalizations, degenerate fitted alignments, shape mismatches, empty masks,
and non-finite selected components return ComplexMetricError; denominators
are never adjusted with an arbitrary epsilon. Alignment is applied during
accumulation, so no aligned image is allocated.
The aggregate compare_complex_fields report used by reconstruction evaluation
remains separate from this explicit-alignment API. Evaluation metrics also
remain separate from the optimization losses in algorithms::objective.