Source code for zea.beamform.delays

"""Delay computation for ultrasound beamforming."""

import matplotlib.pyplot as plt
import numpy as np


[docs] def compute_t0_delays_planewave(probe_geometry, polar_angles, azimuth_angles=0, sound_speed=1540): """Computes the transmit delays for a planewave, shifted such that the first element fires at t=0. Args: probe_geometry (np.ndarray): The positions of the elements in the array of shape (n_el, 3). polar_angles (np.ndarray): The polar angles of the planewave in radians of shape (n_tx,). azimuth_angles (np.ndarray, optional): The azimuth angles of the planewave in radians of shape (n_tx,). Defaults to 0. sound_speed (float, optional): The speed of sound. Defaults to 1540. Returns: np.ndarray: The transmit delays for each element of shape (n_tx, n_el). """ assert probe_geometry is not None, "Probe geometry must be provided to compute t0_delays." # Convert single angles to arrays for broadcasting polar_angles = np.atleast_1d(polar_angles) azimuth_angles = np.atleast_1d(azimuth_angles) # Compute v for all angles v = np.stack( [ np.sin(polar_angles) * np.cos(azimuth_angles), np.sin(polar_angles) * np.sin(azimuth_angles), np.cos(polar_angles), ], axis=-1, ) # Compute the projection of the element positions onto the wave vectors projection = np.sum(probe_geometry[:, None, :] * v, axis=-1).T # Convert from distance to time to compute the transmit delays. t0_delays_not_zero_aligned = projection / sound_speed # The smallest (possibly negative) time corresponds to the moment when # the first element fires. t_first_fire = np.min(t0_delays_not_zero_aligned, axis=1) # The transmit delays are the projection minus the offset. This ensures # that the first element fires at t=0. t0_delays = t0_delays_not_zero_aligned - t_first_fire[:, None] return t0_delays
[docs] def compute_t0_delays_focused( origins, focus_distances, probe_geometry, polar_angles, azimuth_angles=None, sound_speed=1540, ): """Computes the transmit delays for a focused transmit, shifted such that the first element fires at t=0. Args: origins (np.ndarray): The origin of the focused transmit of shape (n_tx, 3,). focus_distance (float): The distance to the focus. probe_geometry (np.ndarray): The positions of the elements in the array of shape (element, 3). polar_angles (np.ndarray): The polar angles of the planewave in radians of shape (n_tx,). azimuth_angles (np.ndarray, optional): The azimuth angles of the planewave in radians of shape (n_tx,). sound_speed (float, optional): The speed of sound. Defaults to 1540. Returns: np.ndarray: The transmit delays for each element of shape (n_tx, element). """ n_tx = len(focus_distances) assert polar_angles.shape == (n_tx,), ( f"polar_angles must have length n_tx = {n_tx}. Got length {len(polar_angles)}." ) assert origins.shape == (n_tx, 3), ( f"origins must have shape (n_tx, 3). Got shape {origins.shape}." ) assert probe_geometry.shape[1] == 3 and probe_geometry.ndim == 2, ( f"probe_geometry must have shape (element, 3). Got shape {probe_geometry.shape}." ) # Convert single angles to arrays for broadcasting polar_angles = np.atleast_1d(polar_angles) if azimuth_angles is None: azimuth_angles = np.zeros(len(polar_angles)) else: azimuth_angles = np.atleast_1d(azimuth_angles) assert azimuth_angles.shape == (n_tx,), ( f"azimuth_angles must have length n_tx = {n_tx}. Got length {len(azimuth_angles)}." ) # Compute v for all angles v = np.stack( [ np.sin(polar_angles) * np.cos(azimuth_angles), np.sin(polar_angles) * np.sin(azimuth_angles), np.cos(polar_angles), ], axis=-1, ) # Add a new dimension for broadcasting # The shape is now (n_tx, n_el, 3) v = np.expand_dims(v, axis=1) # Compute the location of the virtual source by adding the focus distance # to the origin along the wave vectors. virtual_sources = origins[:, None] + focus_distances[:, None, None] * v # Compute the distances between the virtual sources and each element dist = np.linalg.norm(virtual_sources - probe_geometry, axis=-1) # Adjust distances based on the direction of focus dist *= -np.sign(focus_distances[:, None]) # Convert from distance to time to compute the # transmit delays/travel times. travel_times = dist / sound_speed # The smallest (possibly negative) time corresponds to the moment when # the first element fires. t_first_fire = np.min(travel_times, axis=1) # Shift the transmit delays such that the first element fires at t=0. t0_delays = travel_times - t_first_fire[:, None] return t0_delays
[docs] def plot_t0_delays(t0_delays): """Plot the t0_delays for each transducer element. Elements are on the x-axis, and the t0_delays are on the y-axis. We plot multiple lines for each angle/transmit in the scan object. Args: t0_delays (np.ndarray): The t0 delays for each element of shape (n_tx, n_el). """ n_tx = t0_delays.shape[0] _, ax = plt.subplots() for tx in range(n_tx): ax.plot(t0_delays[tx], label=f"Transmit {tx}") ax.set_xlabel("Element number") ax.set_ylabel("t0 delay [s]") plt.show()