A self-contained implementation of the logicle transform (Parks,
Roederer & Moore, Cytometry A 2006), used as a replacement for
flowWorkspace::flowjo_biexp() with the same
channelRange/maxValue/pos/neg/widthBasis
argument names, but with no hard-coded -1000 floor on
widthBasis. Uses only base R (stats::uniroot() for a
one-time scalar solve).
Usage
biexp.transform(
channelRange = 4096,
maxValue = 262144,
pos = 4.5,
neg = 0,
widthBasis = -10,
inverse = FALSE,
newton.iter = 5L
)Arguments
- channelRange
numeric. Maximum value of the transformed (display) scale. Default
4096.- maxValue
numeric. Maximum value of the input (raw) scale. Default
262144.- pos
numeric. Number of positive decades spanned by the transform (
Min the logicle parameterisation). Default4.5.- neg
numeric. Must be
0; see Details.- widthBasis
numeric, negative. FlowJo-style width parameter; see Details for its relationship to the logicle
W. Default-10.- inverse
logical. If
TRUE, returns the inverse transform (display scale back to raw values) instead of the forward transform.- newton.iter
integer. Fixed number of vectorised Newton iterations used to solve the forward transform. Default
5– the smallest value that reaches double-precision round-trip error across the ranges swept in testing. Has no effect on the inverse direction, which is closed-form. If a differentpos/widthBasiscombination needs more, the built-in residual check will warn rather than silently under-converge.
Value
A function mapping a numeric vector on the input scale to the
transformed display scale (or the reverse, if inverse = TRUE).
Details
FlowJo's widthBasis is a legacy re-parameterisation of the
logicle transform's width parameter W (in asymptotic decades):
$$W = \log_{10}(-\mathrm{widthBasis}) / 2$$
The logicle transform requires \(0 < W \le M/2\), where M is
pos. FlowJo's UI, and flowWorkspace::flowjo_biexp(), impose
a hard floor of widthBasis = -1000 (W = 1.5) regardless of
pos – a historical GUI choice, not a mathematical one. This
function checks the real constraint against whatever pos you
supply and errors with the exact numbers if it's violated.
Only neg = 0 is supported (the value used by every cytometer
profile in this package). Extending to neg > 0 requires the
shifted-origin form of the transform (GatingML 2.0's A parameter)
and is deliberately left unimplemented until there's a concrete need for
it and data to verify it against.
The forward direction (raw intensity -> display decade) has no closed
form and is solved by Newton-Raphson, applied to the whole input vector
at once for a fixed number of iterations (not a per-point loop), starting
from asinh(x / (2 * a.scale)) – correct in the large-|x|
limit and reasonable near zero. The inverse direction (display decade ->
raw intensity) is closed-form.