Source code for ezmsg.sigproc.math.anscombe
"""
Apply the Anscombe variance-stabilizing transform to the data, ``2 * sqrt(c + 3/8)``,
or invert it.
This is a variance-stabilizing transform for Poisson-distributed data such as
spike or photon counts: the output has approximately unit variance regardless
of the underlying rate, which lets downstream steps that assume homoscedastic
Gaussian noise be applied to count data.
Inputs below ``-3/8`` produce NaN, so this expects non-negative counts.
.. note::
This module supports the :doc:`Array API standard </guides/explanations/array_api>`,
enabling use with NumPy, CuPy, PyTorch, and other compatible array libraries.
"""
import math
import ezmsg.core as ez
from array_api_compat import get_namespace
from ezmsg.baseproc import BaseTransformer, BaseTransformerUnit
from ezmsg.util.messages.axisarray import AxisArray
from ezmsg.util.messages.util import replace
from ..spectral import OptionsEnum
_OFFSET = 3.0 / 8.0
"""The constant in Anscombe's transform, chosen to make the residual bias O(1/c)."""
_D_MIN = 2.0 * math.sqrt(_OFFSET)
"""The forward transform's value at zero counts; the exact inverse is undefined below it."""
_SQRT_1P5 = math.sqrt(1.5)
[docs]
class AnscombeSettings(ez.Settings):
"""The forward transform takes no parameters.
This empty class exists because a Unit cannot express "no settings":
``SETTINGS = None`` is rejected by ezmsg's Unit metaclass, and omitting
``SETTINGS`` makes the metaclass substitute a bare :obj:`ez.Settings`,
which the transformer then rejects.
"""
[docs]
class Anscombe(BaseTransformerUnit[AnscombeSettings, AxisArray, AxisArray, AnscombeTransformer]):
SETTINGS = AnscombeSettings
[docs]
class InverseMethod(OptionsEnum):
"""How to map stabilized values back to counts.
Every inverse here maps a *denoised* stabilized value back to a rate -- that is,
they invert ``rate -> E[anscombe(counts)]``. Applying one to still-noisy data
returns noisy counts, but the mean of that output is not the mean of the input.
"""
EXACT = "exact"
"""Closed-form approximation of the exact unbiased inverse (Makitalo & Foi, 2011).
Unbiased down to very low rates, at the cost of a few extra elementwise ops."""
ASYMPTOTIC = "asymptotic"
"""``(y/2)**2 - 1/8``. Unbiased as the rate grows, noticeably biased below ~5 counts."""
ALGEBRAIC = "algebraic"
"""``(y/2)**2 - 3/8``. The strict functional inverse of the forward transform, so it
round-trips exactly, but it underestimates the mean of noisy data at low rates."""
[docs]
class InverseAnscombeSettings(ez.Settings):
method: str | InverseMethod = InverseMethod.EXACT
"""Which inverse to apply. See :obj:`InverseMethod`. Default is EXACT."""
[docs]
class InverseAnscombe(BaseTransformerUnit[InverseAnscombeSettings, AxisArray, AxisArray, InverseAnscombeTransformer]):
SETTINGS = InverseAnscombeSettings