Tour of ultrafast ultrasound in DeepInverse#

This example presents the plane-wave ultrafast ultrasound forward physics (deepinv.physics.UltrasoundPlaneWave) available in DeepInverse for pulse-echo imaging problems.

We demonstrate simulating raw RF ultrasound data with/without a pulse-echo, beamforming with delay-and-sum (i.e. the adjoint), both for single-plane-wave imaging and >1 plane waves (i.e. coherent plane-wave compounding, or CPWC).

import math

import torch
import matplotlib.pyplot as plt
import deepinv as dinv

device = dinv.utils.get_device()
Selected GPU 0 with 6908.25 MiB free memory

1. The acquisition setup#

As a first step, let’s simulate the following pulse-echo experiment: A 64-element linear array at 0.3 mm pitch fires 11 plane waves spread over \(\pm 12^\circ\). The center frequency of the array is 5 MHz and the speed of sound is 1540 m/s. The region to image is given in meters: 5 to 40 mm deep, 24 mm wide. The resolution of the image grid is set to lam / 6 axially (to fullfill Nyquist requirements) and lam / 1.5 laterally.

Image size: (682, 117)

2. The pulse-echo impulse response#

In order to account for the physics of a transducer element, we simulate a typical pulse-echo impulse response as a Gaussian-modulated pulse with a given fractional bandwidth and center frequency. This pulse-echo impulse response models the response of a transducer element i.e. how acoustical signals are transformed to electrical signals and vice-versa.

Pulse-echo impulse response
Text(0.5, 24.96281171085858, 'Time [us]')

The number of samples recorded by the transducer elements corresponds to the time taken by the ultrasound wave to travel the longest path of our experiment. The first term corresponds to the longest path in transmit (at most hypot(x, z) over all angles) and the second term to the longest path in receive (extreme left of the transducer to the lower right corner). The pulse length is added since the convolution spreads each echo in time.

3. Defining the forward operator#

deepinv.physics.UltrasoundPlaneWave gathers the grid to reconstruct on (img_size, pixel_size, pixel_origin), the sequence (angles), the probe (element_positions, sampling_frequency, sound_speed, pulse) and the beamforming settings (f_number, receive_apod_window).

4. Simulating per-channel raw data#

We simulate the per-channel raw RF data using the operator defined before. To do so, we consider a reflectivity map x composed of 3 points located at (0, 15mm), (-7.5mm, 25mm) and (7.5mm, 35mm).

x = torch.zeros(1, 1, *img_size, device=device)
grid = physics.pixel_grid
spots = []
for depth_mm, lateral_mm in ((15.0, 0.0), (25.0, -7.5), (35.0, 7.5)):
    position = torch.tensor([lateral_mm, depth_mm], device=grid.device) * 1e-3
    distance = torch.linalg.vector_norm(grid - position, dim=-1)
    ind_dist = int(distance.argmin())
    ind_x = ind_dist // img_size[1]
    ind_z = ind_dist % img_size[1]
    x[0, 0, ind_x, ind_z] = 1.0
    spots.append((ind_x, ind_z))

y = physics(x)

y_bmode = dinv.utils.bmode(y, dim=-1, amplitude_floor_db=-40.0, normalize=True)

dinv.utils.plot(
    y_bmode[:, :, 0],
    titles=[r"Channel data, transmit at $-12^\circ$"],
    figsize=(20, 4),
)
Channel data, transmit at $-12^\circ$

5. Beamforming with the adjoint#

We build an estimate of the reflectivity map (the traditional DAS image) by applying the adjoint operator to the per-channel raw data.

x_das = physics.A_adjoint(y)

bmode_das = dinv.utils.bmode(x_das, dim=-2, amplitude_floor_db=-40, normalize=True)

dinv.utils.plot(
    [x, bmode_das],
    titles=["GT scatterers", f"Beamformed, {angles.numel()} transmits"],
    figsize=(4, 10),
)
GT scatterers, Beamformed, 11 transmits

6. Single-plane wave imaging#

We now restrict the experiment to a single plane wave (normal incidence), by instantiating the operator with the center angle only and beamforming the corresponding transmit. One transmit-receive event per image is what makes ultrafast frame rates possible, at the cost of a point spread function with strong sidelobes and a degraded contrast, shown here against the 11-transmit compounded image.

Tip

You can also create the single-PW physics by updating the existing physics i.e. physics.update(angles=angles[center:center+1]).

center = len(angles) // 2
physics_1pw = dinv.physics.UltrasoundPlaneWave(
    img_size=img_size,
    angles=angles[center : center + 1],
    element_positions=element_positions,
    n_samples=n_samples,
    sampling_frequency=sampling_frequency,
    sound_speed=sound_speed,
    pixel_size=pixel_size,
    pixel_origin=pixel_origin,
    t0=0.0,
    pulse=pulse,
    normalize=False,
    device=device,
    f_number=1.5,
    receive_apod_window="hann",
)
x_1pw = physics_1pw.A_adjoint(y[:, :, center : center + 1])

bmode_1pw = dinv.utils.bmode(x_1pw, dim=-2, amplitude_floor_db=-40, normalize=True)

dinv.utils.plot(
    [bmode_1pw, bmode_das],
    titles=["1 transmit", f"{angles.numel()} transmits"],
    figsize=(4, 10),
)
1 transmit, 11 transmits

What next?#

Now that you master the basics of ultrafast ultrasound imaging physics, you can go further by

  • Reconstructing in-vivo ultrasound images with plug-and-play methods: see the example titled “In-vivo ultrafast ultrasound reconstruction with Plug-and-Play”.

  • Simulating your own experiment: probe, medium, acquisition setup, etc.

Total running time of the script: (0 minutes 2.090 seconds)

🏷 Tags: Ultrasound

Gallery generated by Sphinx-Gallery