Skip to content

Add RL/RLGC deconvolution for 3D fluorescence - #573

Merged
srivarra merged 11 commits into
mainfrom
rl-rlgc
Aug 18, 2026
Merged

Add RL/RLGC deconvolution for 3D fluorescence#573
srivarra merged 11 commits into
mainfrom
rl-rlgc

Conversation

@talonchandler

@talonchandler talonchandler commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Adds Richardson-Lucy (RL) and Gradient-Consensus (RLGC) deconvolution for 3D fluorescence.

Select it in a config with reconstruction_algorithm: RL or RLGC, and tune rl_iterations, rl_background, and rl_stopping_tolerance. RLGC resists the noise overfitting ("starry night") that RL shows when over-iterated.

RL/RLGC options raise NotImplementedError for 2D fluorescence and for phase/birefringence.

The operator-agnostic solver lives in waveorder/rlgc.py, adapted from Andrew York's Gradient Consensus demo (doi.org/10.5281/zenodo.10278918). Tests in tests/models/test_rlgc.py cover the adjoint operators, bead sharpening under Poisson noise, RL vs RLGC over-iteration, and the NotImplemented guards.

@talonchandler
talonchandler marked this pull request as ready for review August 3, 2026 22:24
@talonchandler
talonchandler requested a review from srivarra as a code owner August 3, 2026 22:24
talonchandler and others added 6 commits August 4, 2026 10:20
Signed-off-by: Sricharan Reddy Varra <sricharan.varra@biohub.org>
Eq. 28 returns a spectral amplitude at the cutoff, which is beta's
scale. alpha is added to |OTF|**2, so substituting the same value
leaves it ~200x too large.

On a confocal OTF with |H(cutoff)| = 0.0106 the Wiener spectral
product at the cutoff was 0.0053 instead of 0.171, i.e. ~187
iterations to converge the finest frequencies rather than ~6 --
defeating the point of an unmatched back projector.
calculate_back_projector depends only on the OTF and the rl_bp_*
knobs, all fixed for a run, but sat inside
apply_inverse_transfer_function and so rebuilt on every call.
Callers reconstructing many tiles can now build it once.

Output is bit-identical; matched is unaffected.
Signed-off-by: Sricharan Reddy Varra <sricharan.varra@biohub.org>
srivarra
srivarra previously approved these changes Aug 7, 2026

@srivarra srivarra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks good to me. Do we want to wait to merge, or just go ahead?

@talonchandler talonchandler left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, left one comment to clean up the parametrization a bit. Otherwise I think we're close to ready.

Comment thread docs/examples/cli/configs/fluorescence_2d.yml Outdated
The RL parameters sat flat alongside the Tikhonov and TV ones, so every
generated config carried ten rl_* lines whether or not the algorithm read
them, and the list would grow with each new back projector.

They now live in an optional RLSettings block that follows the algorithm:
filled with defaults for RL/RLGC, dropped with a warning otherwise. A
Tikhonov config carries one `rl: null` line instead of ten. Dropping rather
than rejecting a stray block keeps the napari plugin working, since it
builds widgets from every field and so submits one whatever the algorithm.

Two knobs leave the user-facing schema, both still reachable at the model
level and both unchanged in behaviour:

- rl_bp_order stays at 8. Measured across 2-16 on anisotropic confocal
  data, 8 sits where the passband has saturated and ringing has not.
- rl_bp_resolution_mode stays at 'fwhm'. It declares an instrument class
  rather than tuning anything; 'fwhm_over_sqrt2' is for resolution-doubling
  optics such as iSIM.

to_model_kwargs() is the seam between the nested config and the flat model
signatures. Every apply_inverse call site uses it, including phase and
birefringence, so a nested block added there later is picked up without
touching the callers.

Also corrects the shared reconstruction_algorithm description, which still
advertised only Tikhonov and TV after RL/RLGC were added. That string
regenerates into the phase and birefringence example configs.

Refs #573
The previous commit widened the shared reconstruction_algorithm
description to mention RL/RLGC. That field is on FourierApplyInverseSettings,
so the text regenerated into the phase and birefringence example configs --
modalities that raise NotImplementedError for RL/RLGC, where the original
'Tikhonov' or 'TV' wording was already correct.

Fluorescence overrides the field with its own description, so its configs
were never affected either way.
@srivarra

Copy link
Copy Markdown
Contributor

@talonchandler

Here's an example of an updated yaml config for RL:

input_channel_names: [GFP]
time_indices: all
reconstruction_dimension: 3
fluorescence:
  transfer_function:
    yx_pixel_size: 0.1                  # scalar, or {y: 1.018, x: 0.1842} for anisotropic
    z_pixel_size: 0.25
    z_padding: 0
    index_of_refraction_media: 1.3
    numerical_aperture_detection: 1.2
    wavelength_emission: 0.532
    confocal_pinhole_diameter: null     # null = widefield
  apply_inverse:
    reconstruction_algorithm: RL
    rl:
      iterations: 100
      back_projector: matched
      background: 0.0
      stopping_tolerance: null

and if you want to use Tikhonov:

input_channel_names: [GFP]
time_indices: all
reconstruction_dimension: 3
fluorescence:
  transfer_function:
    yx_pixel_size: 0.1
    z_pixel_size: 0.25
    z_padding: 0
    index_of_refraction_media: 1.3
    numerical_aperture_detection: 1.2
    wavelength_emission: 0.532
    confocal_pinhole_diameter: null
  apply_inverse:
    reconstruction_algorithm: Tikhonov
    regularization_strength: 0.001

So if a user runs RL they have another block of parameters to set, but if they don't, it doesn't need to be filled in.

@talonchandler
talonchandler requested a review from srivarra August 18, 2026 17:21
Widening the shared FourierApplyInverseSettings to accept "RL"/"RLGC" let a
phase or birefringence config validate and then fail deep in the
reconstruction, after the transfer function had been computed. Keep the shared
model to the Fourier filters and let fluorescence widen it in its own subclass.

Two more pairings are now caught while parsing rather than mid-run:
2D fluorescence with RL/RLGC (checked on ReconstructionSettings, the only
model that sees reconstruction_dimension), and RLGC with an unmatched back
projector. The model-level NotImplementedError guards stay for direct callers.
…ters

reconstruct() computes the transfer function itself and has no
back_projector_otf parameter, so documenting one promised a knob that does not
exist. Point callers who want to reuse a back projector at the
calculate/apply pair instead, which is where the parameter lives.
@talonchandler

Copy link
Copy Markdown
Collaborator Author

LGTM! Thanks @srivarra...made a couple minor changes, and I think it's ready for final review and merge.

@srivarra
srivarra merged commit c27e6d9 into main Aug 18, 2026
7 checks passed
@srivarra
srivarra deleted the rl-rlgc branch August 18, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants