Skip to content

Beamforming

Delay-and-sum RF post-processing. coords["t0"] from a reception simulation is already the beamforming reference — an echo peaks at its geometric round-trip time — so no pulse-lag correction is needed here; das_volume additionally recovers each event's transmit time reference. See the Reception user guide.

das_volume

General Numba 3-D DAS (TX=RX). Each event carries delays/apodization plus a virtual_source_mm (DW z<0 / focused z>0 / synthetic z≈0) or angles_deg (PW).

das_volume

das_volume(
    rf: NDArray[floating],
    coords: dict,
    tx_events: list[dict],
    transducer,
    grid_mm: dict,
    *,
    c: float = 1540.0,
    fnum: float = 1.0,
    rx_apodization: str = "hann",
    t_offset_s: float = 0.0,
    coherence_weight: bool = False,
) -> tuple[NDArray[float32], dict]

3-D delay-and-sum for any transmission basis (TX aperture = RX aperture).

One beamformer for the four classic unfocused/focused transmit schemes. Each event dict carries the SAME "delays"/"apodization" arrays fed to sequence_rf, plus ONE geometric key describing the transmitted wavefront:

  • "virtual_source_mm": [x, y, z] — spherical wavefront. z < 0 is a diverging wave (source behind the array), z > 0 a focused transmit (the wave converges to the focus, then diverges — the virtual-source model of a focused beam), z ≈ 0 a single-element / synthetic-aperture firing (source on the aperture).
  • "angles_deg": α or (θx, θy) — steered plane wave with direction n = [sin θx, sin θy, √(1 − sin²θx − sin²θy)].

The transmit time origin is recovered from the event's own delays, so no delay-reference convention (min- vs max-referenced) needs to be assumed. The simulator's time axis starts with the TX bulk delay removed (its t0 is beam-axis referenced), so element e fires at τ_e = delays_e − max(delays). For a spherical event the wavefront obeys τ_e = t_ref ± |r_e − r_vs|/c (− for a source behind reaching the element, + for a focused wave leaving the element toward the focus), so::

diverging / on-aperture:  t_ref = mean_e(τ_e − |r_e − r_vs|/c)
focused (z_vs > 0):       t_ref = mean_e(τ_e + |r_e − r_vs|/c)

(the mean is over apodization-active elements; it is exact when the delays were built from that source, and a least-squares fit otherwise). A plane wave analogously gives t_ref = mean_e(τ_e − r_e·n/c). The voxel's transmit arrival is then::

spherical:  t_tx = t_ref ± |r − r_vs|/c    (− above a transmit focus)
plane:      t_tx = t_ref + r·n/c

and the echo returns over the direct path t_rx = |r − r_e|/c. The sample at t_tx + t_rx is read from each channel (linear interpolation), weighted by a depth-dependent radial receive aperture (|r_xy − r_e,xy| ≤ z/(2·F#), optionally Hann-tapered) and summed coherently over channels and events.

Parameters:

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

Per-event, per-channel RF, as returned by sequence_rf.

required
coords dict

"dt" and "t0_per_event" (or "t0") from sequence_rf.

required
tx_events list[dict]

One dict per event: "delays" (E,) (seconds; zeros if absent), optional "apodization" (E,) (selects the active elements for the time-origin fit), and "virtual_source_mm" or "angles_deg".

required
transducer TransducerBase

The array (transmit = receive): element_centers (metres) provides both the RX channel positions (RF channel order) and the TX geometry for the time-origin recovery.

required
grid_mm dict

Voxel grid: {"x_extent": [x0, xf], "y_extent": ..., "z_extent": ..., "dx": ..., "dy": ..., "dz": ...} in mm (same convention as the simulators' field grids).

required
c float

Speed of sound (m/s).

1540.0
fnum float

Receive F-number: elements within z/(2·fnum) laterally of the voxel are summed.

1.0
rx_apodization ('hann', 'rect')

Taper of the dynamic receive aperture.

'hann'
t_offset_s float

Extra delay added to every sample lookup, in seconds. Zero is correct for eSDIva RF: coords["t0"] is already referenced so that an echo peaks at its geometric round-trip time. Use it for RF from elsewhere whose time axis is not (e.g. raw Field II calc_scat output, where the two-way pulse lag is still in the signal), or to inject a system delay.

0.0
coherence_weight bool

Multiply each voxel by its aperture coherence factor CF = |Σ s|² / (N·Σ s²): 1 for a true in-phase reflector, near 0 for incoherent clutter.

False

Returns:

Name Type Description
volume (Nx, Ny, Nz) numpy.ndarray

Beamformed RF volume (float32, coherent sum over channels and events). Envelope-detect along z (e.g. Hilbert) before display.

axes dict

"x_mm", "y_mm", "z_mm" — voxel-centre coordinates.

Raises:

Type Description
ValueError

If rx_apodization is unknown, the event count mismatches, or an event carries neither/both geometric keys.

das_rca_volume

3-D DAS specialised for row-column (RCA) plane-wave sequences.

das_rca_volume

das_rca_volume(
    rf: NDArray[floating],
    coords: dict,
    *,
    angles_deg,
    tx_centers_mm,
    rx_centers_mm,
    rx_length_mm: float,
    grid_mm: dict,
    c: float = 1540.0,
    fnum: float = 1.0,
    rx_apodization: str = "hann",
    t_offset_s: float = 0.0,
) -> tuple[NDArray[float32], dict]

3-D delay-and-sum for a row-column (RCA) plane-wave sequence.

In RCA imaging one set of long parallel elements (the "columns") transmits plane waves steered in the plane containing the column-array axis, and the orthogonal set (the "rows") receives. Focusing is therefore one-way per direction: transmit compounding sharpens the image along the TX array axis, receive delay-and-sum along the RX array axis.

Per voxel r = (u, v, z) — u along the TX array (= the rows' long axis), v along the RX array — and per event with steering angle α::

t_tx = (u·sinα + z·cosα − ξ_max) / c      (plane-wave arrival)
t_rx = |r − nearest point of row_r| / c    (echo back to row r)

ξ_max = max_e(u_e·sinα + z_e·cosα) is the largest TX-element projection on the steering direction. It is subtracted because the simulator's t0 is beam-axis referenced: the TX bulk delay (delays.max(), which for plane-wave delays (ξ_e − ξ_min)/c equals (ξ_max − ξ_min)/c) is already removed from the time axis, leaving the wavefront crossing a point r at (ξ(r) − ξ_max)/c.

The sample at t_tx + t_rx is read from each row's trace (linear interpolation), weighted by a depth-dependent receive aperture (|v − v_row| ≤ z/(2·F#), optionally Hann-tapered) and summed coherently over rows and angles.

The TX array axis is inferred from tx_centers_mm (the horizontal axis of largest spread); the RX array axis is the orthogonal one. For the dual orientation (rows transmit, columns receive), call again with the swapped arrays and RF, and compound the two envelope volumes.

Parameters:

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

Per-event, per-receive-row RF, as returned by sequence_rf.

required
coords dict

"dt" and "t0_per_event" (or "t0") from sequence_rf.

required
angles_deg (N_events,) array-like

Steering angle of each transmitted plane wave (degrees, in the TX-array/z plane).

required
tx_centers_mm (Etx, 3) numpy.ndarray

TX (column) element centres in mm, e.g. tx.element_centers * 1e3.

required
rx_centers_mm (Erx, 3) numpy.ndarray

RX (row) element centres in mm, in the same order as the RF channels.

required
rx_length_mm float

Full length of each receive row along its long axis, in mm.

required
grid_mm dict

Voxel grid: {"x_extent": [x0, xf], "y_extent": ..., "z_extent": ..., "dx": ..., "dy": ..., "dz": ...} in mm (same convention as the simulators' field grids).

required
c float

Speed of sound (m/s).

1540.0
fnum float

Receive F-number: rows within z/(2·fnum) of the voxel (across the rows) are summed.

1.0
rx_apodization ('hann', 'rect')

Taper of the dynamic receive aperture.

'hann'
t_offset_s float

Extra delay added to every sample lookup, in seconds. Zero is correct for eSDIva RF: coords["t0"] is already referenced so that an echo peaks at its geometric round-trip time. Use it for RF from elsewhere whose time axis is not (e.g. raw Field II calc_scat output, where the two-way pulse lag is still in the signal), or to inject a system delay.

0.0

Returns:

Name Type Description
volume (Nx, Ny, Nz) numpy.ndarray

Beamformed RF volume (float32, coherent sum over rows and angles). Envelope-detect along z (e.g. Hilbert) before display.

axes dict

"x_mm", "y_mm", "z_mm" — voxel-centre coordinates.

Raises:

Type Description
ValueError

If rx_apodization is unknown or the event count mismatches.

DAS_focused_scanline

Single focused B-mode scan line.

DAS_focused_scanline

DAS_focused_scanline(
    rf: NDArray[floating],
    coords: dict,
    rx,
    focus_mm: list[float],
    c: float = 1540.0,
) -> NDArray[float32]

Delay-and-sum beamformer for a single focused scanline.

Applies per-channel RX travel-time delays to align echoes from focus_mm and sums across all receive elements. Suitable for static focused TX where transmit delays are already encoded in the RF data by Reception.

Use this when you already have per-channel RF (e.g. from pulse_echo_rf) and want to beamform it externally. To build a focused line directly, prefer Reception.scan_focusline, which sums on receive inside the SIR kernel (one FFT pair, corner-time-resolution focus) instead of interpolating sampled RF.

The delay for element e is Δt_e = (|r_f − r_e| − |r_f − r_ref|) / c, where r_ref is the centre element position. A positive Δt means the echo arrives later in that channel; the interpolation reads ahead by Δt / dt samples to re-align it.

Parameters:

Name Type Description Default
rf ndarray

Raw channel RF data, shape (E_rx, Nt) (channels, time), as returned by Reception.

required
coords dict

Timing info with keys "t0" (float, seconds) and "dt" (float, seconds), as returned by Reception.

required
rx TransducerBase

Receive transducer. rx.element_centers provides element positions in metres, shape (E_rx, 3).

required
focus_mm list[float]

Focal point [x, y, z] in mm for this scanline.

required
c float

Speed of sound (m/s).

1540.0

Returns:

Type Description
ndarray

Beamformed RF line, shape (Nt,), dtype float32.

envelope_db

Envelope detection and log compression to dB.

envelope_db

envelope_db(
    rf: NDArray[floating], vmin: float | None = None
) -> NDArray[float64]

Compute log-compressed Hilbert envelope.

Parameters:

Name Type Description Default
rf ndarray

RF signal, shape (Nt,) or (Nt, N_lines).

required
vmin float

Minimum linear amplitude floor before log conversion (fraction of peak). None defaults to 1e-20 (no effective floor). To clip at −60 dB, pass vmin=10**(-60/20)0.001.

None

Returns:

Type Description
ndarray

Log-compressed envelope in dB (peak = 0 dB), same shape as rf.