Skip to content

Reception

Pulse-echo RF simulator. See the Reception user guide for the method taxonomy, PSF, and phantom recipes.

Reception

The single pulse-echo class. Its method selector chooses how the two-way SIR is evaluated — all methods give the same RF, they trade speed only:

  • "spectral" (default) — fast sparse-delta kernel via closed-form one-way SIR spectra.
  • "fst" / "sdi" / "auto" — sampled two-way SIR convolution (delegated to the conventional ReceptionConventional backend; the string names its SIR-sampling kernel).
  • "paired" — exact but slow pedagogic reference (warns on selection).

Reception

Reception(
    tx,
    rx,
    *,
    c=1540.0,
    rho=1.0,
    fs=100000000.0,
    alpha0=None,
    freq_power=1.0,
    excitation=None,
    method="spectral",
    n_depth_bins="auto",
    verbose=True,
)

Bases: ReceptionBase

Compute pulse-echo RF from point scatterers via sparse delta integration (SDI).

Implements Jensen's spatial-impulse-response pulse-echo model p_pe = v_pe ⊛ h_tx ⊛ h_rx under the Tupholme-Stepanishen far-field trapezoidal rectangular-patch SIR (full derivation of the three formulations in the module docstring above). Each one-way SIR is the double integral of a sparse train of trapezoid-corner deltas; the two-way SIR is recovered with the four integrations I⁴ = ÷(jω)⁴. All formulations evaluate the same RF equation and agree to correlation ~1.0 with each other and with Field II. method picks how the TX/RX corner trains are combined (speed trade-off only):

  • "spectral" (default) — multiply the two closed-form one-way SIR spectra on the in-band bins only. No forward FFT, cost linear in patches, exact, and folds per-patch one-way attenuation in for free.
  • "fst" / "sdi" / "auto" — sample h_tx/h_rx and convolve, delegated to ReceptionConventional. The string names its SIR-sampling kernel: "fst" fully samples each trapezoid, "sdi" places sparse corner deltas, "auto" lets the delegate choose per grid.
  • "paired" — pedagogic reference only. Splats the integrated drive w = I⁴ v_pe at the 16 corner events of every TX–RX patch pair (no FFT, no cumsum): exact but O(M²) per pair, so far slower than spectral. Warns on selection. No attenuation.

Parameters:

Name Type Description Default
tx TransducerBase

Transmit transducer (with delays, apodization, optional impulse_response and excitation).

required
rx TransducerBase

Receive transducer (with apodization, optional impulse_response). Can be the same object as tx for monostatic pulse-echo.

required
c float

Speed of sound (m/s).

1540.0
rho float

Medium density (kg/m^3).

1.0
fs float

Sampling frequency (Hz).

100e6
alpha0 float or None

Attenuation in dB/(MHz^y·cm). None = no attenuation.

None
freq_power float

Attenuation power-law exponent.

1.0
excitation ndarray or None

TX excitation pulse (L,). If None, uses tx.excitation or delta.

None
method str

Pulse-echo formulation: "spectral" (default) / "fst" / "sdi" / "auto" / "paired". All produce the same RF; they trade speed only. "fst"/"sdi"/"auto" delegate to ReceptionConventional; "paired" is a slow pedagogic reference (see the class summary above).

"spectral"
n_depth_bins auto or int

Spectral speed knob. Scatterers are grouped into this many depth bins so each bin uses a short time window — a small nfft and hence few in-band frequency bins, the spectral form's dominant cost factor — with the per-bin results added back on a shared sample lattice (big speedup at high scatterer counts). "auto" sizes it from the arrival-time spread; 1 disables binning. Applies to the summed RF only.

"auto"
verbose bool

Print diagnostic information during simulation.

True

pulse_echo_rf

pulse_echo_rf(
    scatterer_positions_mm,
    amplitudes=None,
    *,
    per_scatterer=False,
    downsampling=None,
    out_path=None,
    checkpoint_chunks=1,
)

Pulse-echo RF from point scatterers.

The core reception primitive: amplitude-weighted superposition of each scatterer's pulse-echo response. The SDI cores carry a second derivative on each one-way SIR (D²h_tx, D²h_rx), so the public RF applies the four integrations I⁴ = ÷(jω)⁴ in the frequency domain to recover the two-way SIR h_tx ⊛ h_rx; the single physical ∂³ stays on the band-limited excitation / impulse-response chain (e ⊛ h_e ⊛ h_r). Frequency-domain integration carries no group delay, so the result stays sample-aligned with the conventional ReceptionConventional backend. Field II uses the same convention, so this equals Field II calc_scat (≡ calc_hhp for a unit point, corr 1.0000).

per_scatterer=True keeps each scatterer separate (PSF); False sums them. coords["t0"] is beam-axis referenced (TX bulk delays.max() subtracted) so downstream beamforming needs no per-line correction.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
per_scatterer bool

If False, sum over scatterers → (Erx, Nt). If True → (N_scat, Erx, Nt) (PSF per point).

False
downsampling int or None

Anti-aliased time decimation factor.

None
out_path str or Path or None

Checkpoint folder (an RFDataset): the acquisition is written to disk as it progresses and a re-run resumes instead of starting over — use with checkpoint_chunks for hours-long phantoms.

None
checkpoint_chunks int

Scatterer chunks checkpointed separately (requires out_path); the RF is linear in the scatterers, so a crash costs at most one chunk. Incompatible with per_scatterer=True.

1

Returns:

Name Type Description
rf (Erx, Nt) or (N_scat, Erx, Nt) numpy.ndarray

Pulse-echo RF per receive element (channels before time).

coords dict

Keys "t0" and "dt" (seconds).

Raises:

Type Description
ValueError

If out_path is combined with per_scatterer=True.

sequence_rf

sequence_rf(
    scatterer_positions_mm,
    amplitudes,
    tx_events,
    *,
    downsampling=None,
    out_path=None,
    checkpoint_chunks=1,
)

Pulse-echo RF for a sequence of TX events (emission basis: PW/DW/...).

Each event sets the TX delays/apodization, then pulse_echo_rf is run (summed over scatterers). Useful as the emission basis for matrix imaging.

With out_path, the sequence is CHECKPOINTED: each event's RF is written to disk (one compressed file per event + a contents file) the moment it finishes, and re-running the same call on the same folder skips the completed events and resumes from the first missing one. The contents file fingerprints the full simulation (probe, medium, excitation, scatterers, events); re-running with anything changed raises instead of silently mixing incompatible data.

checkpoint_chunks bounds the work lost to a crash WITHIN one event: the RF is linear in the scatterers, so the cloud is split into chunks that are simulated and checkpointed one file at a time, then summed. Every chunk carries four zero-amplitude "grid sentinel" points (just nearer than the nearest scatterer and just farther than the farthest, per aperture) so all chunks of an event share one time grid and their sum is sample-exact.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray

Scattering amplitude of each scatterer.

required
tx_events list of dict

Each dict has "delays" and/or "apodization" (E,) arrays.

required
downsampling int or None

Anti-aliased time decimation factor applied per event.

None
out_path str or Path or None

Checkpoint folder (created if missing). Events already completed there are skipped; each new event is written atomically on completion, so a crash costs at most the event in flight.

None
checkpoint_chunks int

Number of scatterer chunks checkpointed per TX event (requires out_path). With N chunks a crash costs at most 1/N of an event; pick it so one chunk takes ~10–15 minutes.

1

Returns:

Name Type Description
rf (N_events, Erx, Nt) numpy.ndarray

Per-event, per-receive-element RF (channels before time).

coords dict

"t0"/"dt" of the first event, plus "t0_per_event" — an (N_events,) array of each event's beam-axis time origin. Events with differing focus have differing t0 (the time grid depends on that event's delays); beamform each event with its own origin. dt is shared (one sampling rate); traces are zero-padded at the END to the common Nt, so only the origin differs.

Raises:

Type Description
ValueError

If checkpoint_chunks > 1 without out_path, or if tx and rx are the same object while events set delays/apodization (RX weights are per receive channel, so the event's TX weights would corrupt the receive traces — pass rx=tx.copy()).

TypeError

If checkpoint_chunks > 1 with a grid-dict scatterer input.

synthetic_aperture_rf

synthetic_aperture_rf(
    scatterer_positions_mm,
    amplitudes=None,
    *,
    tx_groups="element",
    decimation=10,
    out_path=None,
    countdown=True,
    checkpoint_chunks=1,
)

Synthetic-aperture RF: each TX element/group fires alone, all RX record.

Canonical diverging-wave emission basis for matrix imaging: every group fires FLAT (zero delay, unit apodization) — this OVERRIDES any TX delays/ apodization set on the transducer. tx_groups="element" is full FMC (= Field II calc_scat_all); int N uses N-element sub-apertures; a list[list[int]] gives custom groups.

Each group is one TX event of sequence_rf, so it shares that method's checkpointing: with out_path every group (or scatterer chunk, with checkpoint_chunks) lands on disk the moment it finishes, a re-run resumes at the first missing file, and a changed setup refuses. Output Ntx_grp·Erx·Nt can be huge, hence the anti-aliased decimation (default 10×); a 10 s countdown precedes an in-RAM run so it can be aborted (a checkpointed run is always interruptible, so it starts straight away).

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
tx_groups str or int or list[list[int]]

Transmit grouping: "element" fires each element alone (full FMC), int N fires N-element sub-apertures, list[list[int]] fires custom element groups.

"element"
decimation int

Anti-aliased time decimation factor.

10
out_path str or Path or None

Checkpoint folder (an RFDataset, created if missing): one compressed file per group, crash-safe and resumable.

None
countdown bool

Print a 10 s abortable countdown before an in-RAM computation (skipped if stdout is not a TTY or out_path is given).

True
checkpoint_chunks int

Scatterer chunks checkpointed per group (requires out_path); a crash then costs at most one chunk of one group.

1

Returns:

Name Type Description
rf (Ntx_grp, Erx, Nt) numpy.ndarray

Per-group, per-receive-element RF.

coords dict

"t0"/"dt" of the decimated time axis (plus "t0_per_event", identical for all groups — they all fire flat).

scan_focusline

scan_focusline(
    focus_mm,
    scatterer_positions_mm,
    amplitudes=None,
    *,
    FoverD=None,
    apodization_type=None,
    rx_FoverD=None,
    rx_apodization_type=None,
    output="envelope",
    downsampling=None,
)

One conventional focused scan line (Field II calc_scat match).

Recomputes the TX and RX focus + apodization FROM focus_mm (reusing each transducer's compute_delays / compute_apodization), then beamforms ON RECEIVE INSIDE THE SIR KERNEL via _focused_sum_rf (focused_sum=True): every RX patch carries its focusing delay + apodization and the kernel sums them into one line — exactly Field II calc_scat's internal receive beamforming. This replaces the old per-element-RF + external DAS_focused_scanline path, so it is ~E_rx× cheaper (one FFT pair, no per-channel loop) and applies the receive focus at corner-time resolution (no sample-interpolation loss). Loop focus_mm externally to build a B-mode, e.g. img[i] = sim.scan_focusline(...)[0].

By default the RX aperture mirrors the TX focus + apodization (the usual focused-imaging case, and what Field II's sesr.m does: identical xdc_apodization on emit and receive). Pass rx_FoverD / rx_apodization_type to give the receive aperture a different profile.

Parameters:

Name Type Description Default
focus_mm (3,) array_like

Focal point [x, y, z] in mm (the per-line looped variable).

required
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
FoverD float or None

TX F-number for the active aperture. Used only when apodization_type is set (defaults to 2.0 there if None).

None
apodization_type str or None

TX apodization: "hanning" / "rect" (F/D=1) / "gaussian" / custom. None → uniform full aperture (no taper).

None
rx_FoverD float or None

RX F-number. None → mirror FoverD (identical TX/RX aperture).

None
rx_apodization_type str or None

RX apodization type. None → mirror apodization_type.

None
output (envelope, rf)

Return the Hilbert envelope (default) or the raw beamformed RF line.

"envelope"
downsampling int or None

Anti-aliased time decimation factor.

None

Returns:

Name Type Description
out (Nt,) numpy.ndarray

Envelope (or RF line) of the beamformed scan line at lateral focus[0].

coords dict

"t0"/"dt" of the line's time axis (beam-axis referenced).

show

show(
    scatterer_positions_mm=None,
    amplitudes=None,
    *,
    TX_color="Delays",
    RX_color="Delays",
    TX_show_edges=False,
    RX_show_edges=False,
    TX_kwargs=None,
    RX_kwargs=None,
    window_size=(900, 700),
    notebook=False,
    jupyter_backend=None,
    legend=True,
    scale=1.0,
    save_path=None,
    off_screen=None,
    return_plotter=False,
    db_scale=True,
    **kwargs,
)

Interactive 3-D preview of the pulse-echo setup (TX, RX, scatterers).

Renders the transmit and receive apertures (their patch meshes, in mm) and the scatterer cloud, each point coloured by its scattering amplitude and faded in proportion to it — a quick visual check that positions, units and aperture poses are what the simulation will see. When TX and RX are the same object the aperture is drawn once.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) array-like

Scatterer positions in mm. None draws only the apertures.

None
amplitudes (N_scat,) array-like

Scattering amplitude per point. None defaults to ones (all points fully opaque).

None
TX_color str or tuple

Colour of the transmit aperture. Any PyVista colour (name, hex string or RGB tuple) paints the mesh uniformly. The special strings "Delays" / "Apodization" instead colour each patch by that beamforming quantity with a colour bar, as transducer.show() does. When TX and RX show the SAME quantity, one shared colour bar (right side, common colour range) serves both; different quantities get the TX bar on the left and the RX bar on the right.

"Delays"
RX_color str or tuple

Same as TX_color, for the receive aperture.

"Delays"
TX_show_edges bool

Draw the patch edges of the transmit aperture mesh.

False
RX_show_edges bool

Draw the patch edges of the receive aperture mesh.

False
TX_kwargs dict

Extra keyword arguments for the transmit aperture's add_transducer_mesh call (e.g. {"cmap": "viridis", "opacity": 0.5}); they override the defaults above.

None
RX_kwargs dict

Same as TX_kwargs, for the receive aperture.

None
window_size (int, int)

Pixel dimensions of the render window.

(900, 700)
notebook bool

Enable Jupyter notebook rendering.

False
jupyter_backend str

Backend string passed to PyVista ('static', 'trame' …).

None
legend bool

Draw the TX/RX/Scatterers legend. Note the colour bars belong to the "Delays"/"Apodization" aperture colouring — pass plain colours (e.g. TX_color="lightsteelblue") to remove them.

True
scale float

Global resolution multiplier. Enlarges the render window, every font (grid, aperture tags, colour bars) and the saved screenshot together, so a higher value yields a larger, sharper image without changing the framing (e.g. scale=3 for print figures).

1.0
save_path str or Path

Screenshot file path (e.g. "setup.png"). When given, the scene is rendered off-screen and saved instead of opening a window.

None
off_screen bool

Render without opening a window. None (default) renders off-screen only when save_path is given. Set True when return_plotter is used to screenshot in a headless/batch run — otherwise the returned plotter has nothing rendered and plotter.screenshot() raises "Nothing to screenshot".

None
return_plotter bool

Return the PyVista Plotter object instead of showing or saving the scene. Useful for further customisation (e.g. camera position, extra meshes) before calling plotter.show() or plotter.screenshot(). The axis grid is then NOT drawn, so the caller can apply their own plotter.show_grid(...) settings.

False
db_scale bool

When True, the scatterer amplitudes are converted to dB scale for visualization.

True
**kwargs

Forwarded to the scatterer add_mesh call (e.g. point_size).

{}

Returns:

Type Description
Plotter or None

The plotter (scene assembled, grid and rendering left to the caller) when return_plotter=True; otherwise None.

set

set(name, value)

Update a parameter at runtime, then invalidate the conventional delegate.

Extends ReceptionBase.set with validation of the "method" selector and by dropping the cached ReceptionConventional (rebuilt on the next conventional call, picking up the new tx/rx/medium/excitation state).

Parameters:

Name Type Description Default
name str

Parameter name (a key of _SETTABLE, "tx", or "rx").

required
value object

New value; for "method" it must be a valid selector.

required

ReceptionConventional (backend)

The conventional Tupholme-Stepanishen sampled-convolution backend that Reception delegates to for method="fst"/"sdi"/"auto". Same API; normally reached through Reception, documented here for reference.

ReceptionConventional

ReceptionConventional(
    tx,
    rx,
    *,
    c=1540.0,
    rho=1.0,
    fs=100000000.0,
    alpha0=None,
    freq_power=1.0,
    excitation=None,
    method="auto",
    n_depth_bins="auto",
    verbose=True,
)

Bases: ReceptionBase

Pulse-echo RF by direct two-way SIR convolution (Jensen's model).

Computes the received echo from point scatterers using the standard SIR formulation (model and citations in the module docstring above): the one-way SIRs h_tx and h_rx are built without differentiation, and the recorded RF is the amplitude-weighted sum over scatterers

rf(t) = (ρ₀/2c₀²) Σ_p σ_p · (exc ⊛ ir_tx ⊛ ir_rx ⊛ h_pe)|_{r_p}

where the sum runs over scatterers p at positions r_p with scattering amplitudes σ_p (the amplitudes argument), and h_pe = h_tx(r₁→r_p) ⊛_t h_rx(r_p→r₁) is the plain two-way SIR for that scatterer. The three physical excitation derivatives are carried by the band-limited exc/IR chain, so no extra explicit ∂/∂t is applied (Field II's calc_scatcalc_hhp convention). Public API via pulse_echo_rf (core) plus sequence_rf / synthetic_aperture_rf / scan_focusline from ReceptionBase.

Parameters:

Name Type Description Default
tx TransducerBase

Transmit transducer (with delays, apodization, optional impulse_response and excitation).

required
rx TransducerBase

Receive transducer (with apodization, optional impulse_response). Can be the same object as tx for monostatic pulse-echo.

required
c float

Speed of sound (m/s).

1540.0
rho float

Medium density (kg/m^3).

1.0
fs float

Sampling frequency (Hz).

100e6
alpha0 float or None

Attenuation in dB/(MHz^y·cm). None = no attenuation.

None
freq_power float

Attenuation power-law exponent.

1.0
excitation ndarray or None

TX excitation pulse (L,). If None, uses tx.excitation or delta.

None
method str

SIR computation method: "FST", "sdi", or "auto".

"auto"
n_depth_bins auto or int

Pulse-echo speed knob (result is unchanged). Scatterers at different depths echo at different times, so one FFT over all of them must span the whole depth range; grouping them into depth bins lets each bin use an FFT only as long as its own arrival window — a big speedup at high scatterer counts. Bins recombine sample-exactly on a shared time axis. "auto" picks the count from the scatterer number and depth spread (~100 scatterers per bin); an int forces it; 1 disables binning.

"auto"
verbose bool

Print diagnostic information during simulation.

True

pulse_echo_rf

pulse_echo_rf(
    scatterer_positions_mm,
    amplitudes=None,
    *,
    per_scatterer=False,
    downsampling=None,
    out_path=None,
    checkpoint_chunks=1,
)

Pulse-echo RF from point scatterers.

The core reception primitive. The recorded pulse-echo signal is the amplitude-weighted superposition of each scatterer's pulse-echo response::

rf = (ρ₀/2c₀²) Σ_i a_i (exc ⊛ ir_tx ⊛ ir_rx ⊛ h_pe)|_{r_i}

The three physical excitation derivatives are carried by the band-limited exc ⊛ ir_tx ⊛ ir_rx chain, so NO extra explicit ∂/∂t is applied. Field II uses the same convention, so this equals its calc_scat (≡ calc_hhp for a unit point, corr 1.0000).

per_scatterer=True keeps each scatterer separate — the point-spread function (PSF) use; False sums them (the recorded echo).

coords["t0"] is referenced to the beam axis (the TX focusing bulk tx.delays.max() is subtracted), so downstream beamforming needs no per-line bulk correction.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
per_scatterer bool

If False, sum over scatterers → (Erx, Nt). If True, keep each scatterer separate → (N_scat, Erx, Nt) (PSF per point).

False
downsampling int or None

Anti-aliased time decimation factor.

None
out_path str or Path or None

Checkpoint folder (an RFDataset): the acquisition is written to disk as it progresses and a re-run resumes instead of starting over — use with checkpoint_chunks for hours-long phantoms.

None
checkpoint_chunks int

Scatterer chunks checkpointed separately (requires out_path); the RF is linear in the scatterers, so a crash costs at most one chunk. Incompatible with per_scatterer=True.

1

Returns:

Name Type Description
rf (Erx, Nt) or (N_scat, Erx, Nt) numpy.ndarray

Pulse-echo RF per receive element (channels before time).

coords dict

Keys "t0" and "dt" (seconds).

Raises:

Type Description
ValueError

If out_path is combined with per_scatterer=True.

sequence_rf

sequence_rf(
    scatterer_positions_mm,
    amplitudes,
    tx_events,
    *,
    downsampling=None,
    out_path=None,
    checkpoint_chunks=1,
)

Pulse-echo RF for a sequence of TX events (emission basis: PW/DW/...).

Each event sets the TX delays/apodization, then pulse_echo_rf is run (summed over scatterers). Useful as the emission basis for matrix imaging.

With out_path, the sequence is CHECKPOINTED: each event's RF is written to disk (one compressed file per event + a contents file) the moment it finishes, and re-running the same call on the same folder skips the completed events and resumes from the first missing one. The contents file fingerprints the full simulation (probe, medium, excitation, scatterers, events); re-running with anything changed raises instead of silently mixing incompatible data.

checkpoint_chunks bounds the work lost to a crash WITHIN one event: the RF is linear in the scatterers, so the cloud is split into chunks that are simulated and checkpointed one file at a time, then summed. Every chunk carries four zero-amplitude "grid sentinel" points (just nearer than the nearest scatterer and just farther than the farthest, per aperture) so all chunks of an event share one time grid and their sum is sample-exact.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray

Scattering amplitude of each scatterer.

required
tx_events list of dict

Each dict has "delays" and/or "apodization" (E,) arrays.

required
downsampling int or None

Anti-aliased time decimation factor applied per event.

None
out_path str or Path or None

Checkpoint folder (created if missing). Events already completed there are skipped; each new event is written atomically on completion, so a crash costs at most the event in flight.

None
checkpoint_chunks int

Number of scatterer chunks checkpointed per TX event (requires out_path). With N chunks a crash costs at most 1/N of an event; pick it so one chunk takes ~10–15 minutes.

1

Returns:

Name Type Description
rf (N_events, Erx, Nt) numpy.ndarray

Per-event, per-receive-element RF (channels before time).

coords dict

"t0"/"dt" of the first event, plus "t0_per_event" — an (N_events,) array of each event's beam-axis time origin. Events with differing focus have differing t0 (the time grid depends on that event's delays); beamform each event with its own origin. dt is shared (one sampling rate); traces are zero-padded at the END to the common Nt, so only the origin differs.

Raises:

Type Description
ValueError

If checkpoint_chunks > 1 without out_path, or if tx and rx are the same object while events set delays/apodization (RX weights are per receive channel, so the event's TX weights would corrupt the receive traces — pass rx=tx.copy()).

TypeError

If checkpoint_chunks > 1 with a grid-dict scatterer input.

synthetic_aperture_rf

synthetic_aperture_rf(
    scatterer_positions_mm,
    amplitudes=None,
    *,
    tx_groups="element",
    decimation=10,
    out_path=None,
    countdown=True,
    checkpoint_chunks=1,
)

Synthetic-aperture RF: each TX element/group fires alone, all RX record.

Canonical diverging-wave emission basis for matrix imaging: every group fires FLAT (zero delay, unit apodization) — this OVERRIDES any TX delays/ apodization set on the transducer. tx_groups="element" is full FMC (= Field II calc_scat_all); int N uses N-element sub-apertures; a list[list[int]] gives custom groups.

Each group is one TX event of sequence_rf, so it shares that method's checkpointing: with out_path every group (or scatterer chunk, with checkpoint_chunks) lands on disk the moment it finishes, a re-run resumes at the first missing file, and a changed setup refuses. Output Ntx_grp·Erx·Nt can be huge, hence the anti-aliased decimation (default 10×); a 10 s countdown precedes an in-RAM run so it can be aborted (a checkpointed run is always interruptible, so it starts straight away).

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
tx_groups str or int or list[list[int]]

Transmit grouping: "element" fires each element alone (full FMC), int N fires N-element sub-apertures, list[list[int]] fires custom element groups.

"element"
decimation int

Anti-aliased time decimation factor.

10
out_path str or Path or None

Checkpoint folder (an RFDataset, created if missing): one compressed file per group, crash-safe and resumable.

None
countdown bool

Print a 10 s abortable countdown before an in-RAM computation (skipped if stdout is not a TTY or out_path is given).

True
checkpoint_chunks int

Scatterer chunks checkpointed per group (requires out_path); a crash then costs at most one chunk of one group.

1

Returns:

Name Type Description
rf (Ntx_grp, Erx, Nt) numpy.ndarray

Per-group, per-receive-element RF.

coords dict

"t0"/"dt" of the decimated time axis (plus "t0_per_event", identical for all groups — they all fire flat).

scan_focusline

scan_focusline(
    focus_mm,
    scatterer_positions_mm,
    amplitudes=None,
    *,
    FoverD=None,
    apodization_type=None,
    rx_FoverD=None,
    rx_apodization_type=None,
    output="envelope",
    downsampling=None,
)

One conventional focused scan line (Field II calc_scat match).

Recomputes the TX and RX focus + apodization FROM focus_mm (reusing each transducer's compute_delays / compute_apodization), then beamforms ON RECEIVE INSIDE THE SIR KERNEL via _focused_sum_rf (focused_sum=True): every RX patch carries its focusing delay + apodization and the kernel sums them into one line — exactly Field II calc_scat's internal receive beamforming. This replaces the old per-element-RF + external DAS_focused_scanline path, so it is ~E_rx× cheaper (one FFT pair, no per-channel loop) and applies the receive focus at corner-time resolution (no sample-interpolation loss). Loop focus_mm externally to build a B-mode, e.g. img[i] = sim.scan_focusline(...)[0].

By default the RX aperture mirrors the TX focus + apodization (the usual focused-imaging case, and what Field II's sesr.m does: identical xdc_apodization on emit and receive). Pass rx_FoverD / rx_apodization_type to give the receive aperture a different profile.

Parameters:

Name Type Description Default
focus_mm (3,) array_like

Focal point [x, y, z] in mm (the per-line looped variable).

required
scatterer_positions_mm (N_scat, 3) numpy.ndarray

Scatterer positions in mm.

required
amplitudes (N_scat,) numpy.ndarray or None

Scattering coefficient at each position. None defaults to ones.

None
FoverD float or None

TX F-number for the active aperture. Used only when apodization_type is set (defaults to 2.0 there if None).

None
apodization_type str or None

TX apodization: "hanning" / "rect" (F/D=1) / "gaussian" / custom. None → uniform full aperture (no taper).

None
rx_FoverD float or None

RX F-number. None → mirror FoverD (identical TX/RX aperture).

None
rx_apodization_type str or None

RX apodization type. None → mirror apodization_type.

None
output (envelope, rf)

Return the Hilbert envelope (default) or the raw beamformed RF line.

"envelope"
downsampling int or None

Anti-aliased time decimation factor.

None

Returns:

Name Type Description
out (Nt,) numpy.ndarray

Envelope (or RF line) of the beamformed scan line at lateral focus[0].

coords dict

"t0"/"dt" of the line's time axis (beam-axis referenced).

show

show(
    scatterer_positions_mm=None,
    amplitudes=None,
    *,
    TX_color="Delays",
    RX_color="Delays",
    TX_show_edges=False,
    RX_show_edges=False,
    TX_kwargs=None,
    RX_kwargs=None,
    window_size=(900, 700),
    notebook=False,
    jupyter_backend=None,
    legend=True,
    scale=1.0,
    save_path=None,
    off_screen=None,
    return_plotter=False,
    db_scale=True,
    **kwargs,
)

Interactive 3-D preview of the pulse-echo setup (TX, RX, scatterers).

Renders the transmit and receive apertures (their patch meshes, in mm) and the scatterer cloud, each point coloured by its scattering amplitude and faded in proportion to it — a quick visual check that positions, units and aperture poses are what the simulation will see. When TX and RX are the same object the aperture is drawn once.

Parameters:

Name Type Description Default
scatterer_positions_mm (N_scat, 3) array-like

Scatterer positions in mm. None draws only the apertures.

None
amplitudes (N_scat,) array-like

Scattering amplitude per point. None defaults to ones (all points fully opaque).

None
TX_color str or tuple

Colour of the transmit aperture. Any PyVista colour (name, hex string or RGB tuple) paints the mesh uniformly. The special strings "Delays" / "Apodization" instead colour each patch by that beamforming quantity with a colour bar, as transducer.show() does. When TX and RX show the SAME quantity, one shared colour bar (right side, common colour range) serves both; different quantities get the TX bar on the left and the RX bar on the right.

"Delays"
RX_color str or tuple

Same as TX_color, for the receive aperture.

"Delays"
TX_show_edges bool

Draw the patch edges of the transmit aperture mesh.

False
RX_show_edges bool

Draw the patch edges of the receive aperture mesh.

False
TX_kwargs dict

Extra keyword arguments for the transmit aperture's add_transducer_mesh call (e.g. {"cmap": "viridis", "opacity": 0.5}); they override the defaults above.

None
RX_kwargs dict

Same as TX_kwargs, for the receive aperture.

None
window_size (int, int)

Pixel dimensions of the render window.

(900, 700)
notebook bool

Enable Jupyter notebook rendering.

False
jupyter_backend str

Backend string passed to PyVista ('static', 'trame' …).

None
legend bool

Draw the TX/RX/Scatterers legend. Note the colour bars belong to the "Delays"/"Apodization" aperture colouring — pass plain colours (e.g. TX_color="lightsteelblue") to remove them.

True
scale float

Global resolution multiplier. Enlarges the render window, every font (grid, aperture tags, colour bars) and the saved screenshot together, so a higher value yields a larger, sharper image without changing the framing (e.g. scale=3 for print figures).

1.0
save_path str or Path

Screenshot file path (e.g. "setup.png"). When given, the scene is rendered off-screen and saved instead of opening a window.

None
off_screen bool

Render without opening a window. None (default) renders off-screen only when save_path is given. Set True when return_plotter is used to screenshot in a headless/batch run — otherwise the returned plotter has nothing rendered and plotter.screenshot() raises "Nothing to screenshot".

None
return_plotter bool

Return the PyVista Plotter object instead of showing or saving the scene. Useful for further customisation (e.g. camera position, extra meshes) before calling plotter.show() or plotter.screenshot(). The axis grid is then NOT drawn, so the caller can apply their own plotter.show_grid(...) settings.

False
db_scale bool

When True, the scatterer amplitudes are converted to dB scale for visualization.

True
**kwargs

Forwarded to the scatterer add_mesh call (e.g. point_size).

{}

Returns:

Type Description
Plotter or None

The plotter (scene assembled, grid and rendering left to the caller) when return_plotter=True; otherwise None.