Skip to content

Add ADMIRE beamformer - #650

Draft
tristan-deep wants to merge 3 commits into
mainfrom
feature/admire
Draft

tristan-deep wants to merge 3 commits into
mainfrom
feature/admire

Conversation

@tristan-deep

@tristan-deep tristan-deep commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Adds ADMIRE (Aperture Domain Model Image REconstruction), a model-based beamformer that suppresses off-axis and multipath clutter. For each short depth window and frequency, it fits the aperture-domain signal with modeled point-scatterer wavefronts (elastic net, coordinate descent) and keeps only the part explained by scatterers near the image line.

What's in here

  • zea.beamform.admire: model generation (NumPy, cached on disk, optionally parallel with n_workers) and the fit (Keras ops, runs on every backend). Ported from the VU-BEAM-Lab reference.
  • ops.ADMIRE, also usable as Beamform(beamformer="admire").
  • Tests, including checks against ports of the reference solver and ICA.
pipeline = ops.Pipeline(
    [
        ops.Demodulate(),
        ops.Beamform(beamformer="admire", num_patches=1, n_elements=65),
        ops.EnvelopeDetect(),
        ops.Normalize(),
        ops.LogCompress(),
    ],
    jit_options="ops",
)

Carotid example

zea-carotid-2023, 21 compounded plane waves, 65-element sub-apertures. DAS left, ADMIRE right:

image
  • Clutter in the lumen is mostly removed, and the vessel walls and superficial speckle are kept.
  • Below the far wall ADMIRE also removes some genuine tissue signal. The default regularization (λ = 0.0189, from the reference) is fairly aggressive on this data.

Things to know

  • Checked against the reference: I ran the original MATLAB/C code in Octave on synthetic data. Predictor wavefronts and models (without ICA) are identical, the solver matches the compiled C to ~1e-15, and given the reference's models the full pipeline reproduces its output to float32 precision.
  • Deviations from the reference: the reference ICA (ica.m) depends on the arbitrary phase of the eigenvectors the linear algebra library returns for complex data, so its models differ per platform. Those phases are fixed here so models are reproducible. The calibration table is matched to the nearest listed frequency, and IQ data plus compounded plane and diverging waves are supported. The reference targets focused, walked-aperture RF data.
  • License and citation: the reference code is Apache-2.0. The copyright holders and the changes are listed in the module docstring, and all citations the authors request are added.
  • Cost: model generation runs once per geometry, about 2 min for the example above with 120 processes, and is cached. The fit costs roughly 3× DAS per frame on a GPU.
  • Limitations: linear arrays only (phased-array sectors need steered models), and the non-overlapping windows give some horizontal blockiness.

ADMIRE suppresses off-axis and multipath clutter by fitting a model of
point-scatterer wavefronts to the aperture-domain signal of short axial windows,
frequency by frequency, and keeping only the scatterers near each image line.

- zea.beamform.admire: model generation (NumPy, cached, optionally parallel over
  windows) and the elastic-net fit (Keras ops, all backends), ported from the
  VU-BEAM-Lab reference code (Apache-2.0)
- ops.ADMIRE, also available as Beamform(beamformer="admire")
- The ICA eigenvector phases are fixed so the models are reproducible across
  platforms; the calibration table is matched to the nearest center frequency
- Tests, including checks against ports of the reference solver and ICA
- Citations requested by the reference authors
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3198 1 3197 43
View the top 1 failed test(s) by shortest run time
tests/test_admire.py::test_generate_admire_models_parallel
Stack Traces | 11.8s run time
iq_models = (array([0.011    , 0.0110385, 0.011077 , 0.0111155, 0.011154 , 0.0111925,
       0.011231 , 0.0112695, 0.011308 , 0.01...pacing=0.1211, outer_distance_offset_limits=(-0.008, 0.0032), predictor_chunk_size=32768, max_cache_bytes=1073741824)))

    def test_generate_admire_models_parallel(iq_models):
        """Generating windows in worker processes gives the same models."""
        depths, models = iq_models
        with cache_disabled():
            parallel = generate_admire_models(
                depths,
                sound_speed=SOUND_SPEED,
                center_frequency=CENTER_FREQUENCY,
                pitch=PITCH,
                n_elements=16,
                f_number=2.0,
                analytic=True,
                n_workers=2,
            )
>       np.testing.assert_array_equal(parallel.models, models.models)
E       AssertionError: 
E       Arrays are not equal
E       
E       Mismatched elements: 226 / 12288 (1.84%)
E       First 5 mismatches are at indices:
E        [0, 0, 0, 9]: (0.09790604561567307+0.08192162215709686j) (ACTUAL), (0.09790604561567307+0.08192162960767746j) (DESIRED)
E        [0, 0, 3, 2]: (-0.29199835658073425-0.19729137420654297j) (ACTUAL), (-0.29199835658073425-0.19729135930538177j) (DESIRED)
E        [0, 0, 8, 5]: (0.27432113885879517+0.0021945443004369736j) (ACTUAL), (0.27432113885879517+0.00219454406760633j) (DESIRED)
E        [0, 0, 8, 12]: (0.0005239321617409587+0.02781238593161106j) (ACTUAL), (0.0005239322199486196+0.02781238593161106j) (DESIRED)
E        [0, 0, 9, 5]: (0.1900138258934021+0.003493748838081956j) (ACTUAL), (0.1900138258934021+0.0034937486052513123j) (DESIRED)
E       Max absolute difference among violations: 2.9860473e-08
E       Max relative difference among violations: 6.6071743e-07
E        ACTUAL: array([[[[ 1.820553e-01-2.417332e-01j, -3.419336e-02-1.725667e-02j,
E                  1.376672e-01-2.459700e-01j, ...,
E                 -1.865069e-01-7.005151e-02j,  6.324892e-02+1.309722e-02j,...
E        DESIRED: array([[[[ 1.820553e-01-2.417332e-01j, -3.419336e-02-1.725667e-02j,
E                  1.376672e-01-2.459700e-01j, ...,
E                 -1.865069e-01-7.005151e-02j,  6.324892e-02+1.309722e-02j,...

tests/test_admire.py:233: AssertionError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

…ted grids

Beamform(beamformer="admire") now defaults to num_patches=1, and asking for
patching raises an error that says to use num_patches=1. The ADMIRE operation
warns, before generating models, when the grid is not cartesian (such as a polar
sector grid) or when most columns have their sub-aperture beyond the array.

This branch has not been deployed

No deployments
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.

1 participant