tutorials.helpers - moduleus/urx GitHub Wiki
To run the tutorials, 4 helpers is used to ease UAC generation:
makeLinearArrayUacmakePlaneWaveTransmitSetupUacmakeReceiveSetupUacmakePlaneWavesGroupUac
All the following files are available in UAC project in test/tutorials folder.
import numpy as np
import numpy.typing as npt
import ultrasound_acquisition_configuration as uac
def makeLinearArrayUac(
n_elements: int, pitch: float, width: float, height: float, description: str
) -> uac.Probe:
"""
makeLinearArrayUac.py creates a linear array probe in uac format
Inputs:
n_elements : number of elements (transducers) in the probe
pitch : distance between the center of adjacent elements [m]
width : size of the element along the x-axis [m]
height : size of the element along the y-axis [m]
description : name or description of the probe
Outputs:
probe (uac.Probe) : built as a linear array along the x-axis
Drawing:
Linear array (n_elements = 12)
___ pitch __ width
o--->x __ __ __ __ __ __ __ __ __ __ __ __
/| / // // // //+//+// // // // // // / /
/ | /_//_//_//_//_//_//_//_//_//_//_//_/ / height
y z
+--+ perimeter of and element with + as elements' edges
/ / defined either clockwise or counterclockwise
+--+
Requirements:
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/uac/wiki
"""
# Element's geometry : all the elements share the same impulse response
edges = np.array(
[ # x, y z coordinates (dimension 1) of the 4 edges (dimensions 2)
[-width / 2, -width / 2, width / 2, width / 2],
[-height / 2, height / 2, height / 2, -height / 2],
[0, 0, 0, 0],
]
)
element_geometry = uac.ElementGeometry()
element_geometry.perimeter = [ # perimeter of one element
uac.Vector3D(edges[0, 0], edges[1, 0], edges[2, 0]),
uac.Vector3D(edges[0, 1], edges[1, 1], edges[2, 1]),
uac.Vector3D(edges[0, 2], edges[1, 2], edges[2, 2]),
uac.Vector3D(edges[0, 3], edges[1, 3], edges[2, 3]),
]
# (Optional) Impulse response : all the elements share the same impulse response
impulse_response = uac.ImpulseResponse()
impulse_response.sampling_frequency = (
50e6 # sampling frequency of the impulse response data [Hz]
)
impulse_response.time_offset = 0 # delay before impulse reponse starts [s]
impulse_response.units = "N/A" # unit of the impulse response
impulse_response.data = [ # impulse response depending on time
0.0000,
0.0467,
0.1641,
0.2780,
0.2521,
0.0000,
-0.4160,
-0.7869,
-0.8756,
-0.5759,
-0.0000,
0.5759,
0.8756,
0.7869,
0.4160,
0.0000,
-0.2521,
-0.2780,
]
# Build elements' array
elements = uac.VecElement()
xmin = -pitch * (n_elements - 1) / 2 # first element center position
for i in range(0, n_elements): # loop on the elements
x = xmin + i * pitch # computes all the position element
Element = uac.Element()
Element.transform.translation = uac.Vector3D(x, 0, 0) # elements are only along x-axis
Element.element_geometry = element_geometry # shared geometry between elements
Element.impulse_response = impulse_response # shared impulse response between elements
elements.append(Element) # adds elements to the array
# Build the probe
probe = uac.Probe()
probe.description = description
probe.type = (
uac.ProbeType.LINEAR
) # 0: LINEAR, 1: CURVILINEAR, 2: MATRIX, 3: RCA, 4: SPARSE, -1: UNDEFINED
probe.impulse_responses = [impulse_response] # could store different impulse responses
probe.element_geometries = [element_geometry] # could store different element's geometry
probe.elements = elements # set all the elements once to avoid repetitive call to probe
return probe
def makePlaneWaveTransmitSetupUac(
theta: float,
voltage: float,
n_periods: int,
transmit_frequency: float,
max_delay: float,
sound_speed: float,
channel_mapping: uac.VecVecUInt32 | list[list[int]] | npt.NDArray[np.uint32],
probe: uac.Probe,
excitation: uac.Excitation | None = None,
):
"""
makePlaneWaveTransmitSetupUac creates a plane waves transmit setup with a linear array in uac format
Inputs:
theta : plane wave angle of the event [°]
voltage : voltage of the excitation [Vpeak]
n_periods : number of periods of transmitted signal
transmit_frequency : central frequency of the transmitted signal [Hz]
max_delay : maximum delay of the plane wave sequence [s]
sound_speed : celerity of the sound in the medium [m/s]
channel_mapping : order of channels in the interface (start at 0)
probe (uac.Probe) : ultrasound probe used for the transmit event
excitation (uac.Excitation) : (optional) excitation reused from another transmit event
Outputs:
TransmitSetup (uac.TransmitSetup) : built for plane wave transmit event with a linear array probe
excitation (uac.Excitation) : excitation to be used in another transmit event
Requirements:
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/uac/wiki
"""
# Checks
if probe.type != uac.ProbeType.LINEAR:
raise ValueError("Wrong type of probe")
# Excitation parameters
excitation_post_clamp_duration = 900e-9
# clamp after excitation to avoid reflexion on the electronics [s]
excitation_sampling_frequency = 200e6
# sampling frequency of the excitation waveform [Hz]
# Delay
n_elements = len(probe.elements) # number of probe's elements
pitch = (
probe.elements[1].transform.translation.x - probe.elements[0].transform.translation.x
) # pitch of the probe
delays = (
np.sin(np.deg2rad(theta))
* (np.arange(-(n_elements - 1) / 2, (n_elements - 1) / 2 + 1))
* pitch
/ sound_speed
) # linear delay along the element to make the plane wave
# Wave
wave = uac.Wave()
wave.type = (
uac.WaveType.PLANE_WAVE
) # 0: CONVERGING_WAVE, 1: DIVERGING_WAVE, 2:PLANE_WAVE, 3:CYLINDRICAL_WAVE, -1: UNDEFINED
wave.parameters = [
np.sin(np.deg2rad(theta)),
0.0,
np.cos(np.deg2rad(theta)),
] # defines the plane wave with the normal vector
wave.time_zero = (
max_delay + n_periods / transmit_frequency / 2
) # default value, reference point for the time_zero
# Excitation
if excitation is None: # if no excitation input to be reused, definition of the excitation
excitation = uac.Excitation()
excitation.pulse_shape = "square wave" # description of the pulse shape
excitation.transmit_frequency = transmit_frequency
excitation.sampling_frequency = excitation_sampling_frequency
n_samples_excitation = round(
excitation_sampling_frequency / transmit_frequency / 2
) # number of samples for a half period of excitation
if n_samples_excitation != int(
n_samples_excitation
): # excitation_sampling_frequency may not allow transmit_frequency so it is corrected
print(
f"(Warning) Real transmit frequency: {excitation_sampling_frequency / n_samples_excitation / 2} Hz instead of {transmit_frequency}Hz"
) # warn if frequency has been changed
n_post_clamp = round(excitation_sampling_frequency * excitation_post_clamp_duration)
waveform = voltage * np.concatenate(
(
np.tile(
np.concatenate((np.ones(n_samples_excitation), -np.ones(n_samples_excitation))),
n_periods,
),
np.zeros(n_post_clamp),
)
)
excitation.waveform = uac.VecFloat64(waveform.ravel()) # square excitation waveform
# TransmitSetup
transmit_setup = uac.TransmitSetup()
transmit_setup.wave = wave
# active elements (arrays dimension) per excitations (cells dimension)
transmit_setup.active_elements = channel_mapping
transmit_setup.excitations = [excitation] * n_elements # shared excitation for all the elements
transmit_setup.delays = uac.VecFloat64(delays.ravel()) + max_delay
transmit_setup.probe = probe
return transmit_setup, excitation
def makeReceiveSetupUac(
n_samples: int,
rx_time_offset: float,
sampling_frequency: float,
modulation_frequency: float,
channel_mapping: uac.VecVecUInt32 | list[list[int]] | npt.NDArray[np.uint32],
probe: uac.Probe,
):
"""
makeReceiveSetupUac.py creates a receive setup in uac format
Inputs:
n_samples : number of samples per channel per reception event
rx_time_offset : time before the sampling of the received signal [s]
sampling_frequency : sampling frequency of the backscattered signal [Hz]
modulation_frequency : frequency used for the demodulation of received signal in IQ [Hz]
channel_mapping : order of channels in the interface (start at 0)
probe (uac.Probe) : ultrasound probe used for the reception event
Outputs:
ReceiveSetup (uac.ReceiveSetup) : built for reception event with all the elements of the probe
Requirements:
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/uac/wiki
"""
# Probe information
# n_elements = len(probe.elements)
# ReceiveSetup
receive_setup = uac.ReceiveSetup()
receive_setup.sampling_frequency = sampling_frequency
receive_setup.modulation_frequency = modulation_frequency
receive_setup.number_samples = n_samples
receive_setup.active_elements = channel_mapping
receive_setup.tgc_profile = []
receive_setup.tgc_sampling_frequency = 0
receive_setup.time_offset = rx_time_offset
receive_setup.probe = probe
return receive_setup
def makePlaneWavesGroupUac(
thetas: list[float] | npt.NDArray[np.float32],
voltage: float,
n_periods: int,
prf: float,
n_frames: int,
frame_rate: float,
transmit_frequency: float,
sound_speed: float,
n_samples: int,
rx_time_offset: float,
sampling_frequency: float,
modulation_frequency: float,
channel_mapping: uac.VecVecUInt32 | list[list[int]] | npt.NDArray[np.uint32],
probe: uac.Probe,
):
"""
makePlaneWavesSequenceUac.py creates a plane waves sequence with a linear array in uac format
Inputs:
thetas : plane wave angles of the sequence [°]
voltage : voltage of the excitation [Vpeak]
nPeriods : number of periods of transmitted signal
prf : pulse repetition frequency [Hz]
nFrames : number of repetition of the sequence
frameRate : repetition frequency of the sequence [Hz]
transmitFrequency : central frequency of the transmitted signal [Hz]
soundSpeed : celerity of the sound in the medium [m/s]
nSamples : number of samples per channel per reception event
rxTimeOffset : time before the sampling of the received signal [s]
samplingFrequency : sampling frequency of the backscattered signal [Hz]
modulationFrequency : frequency used for the demodulation of received signal in IQ [Hz]
channelMapping : order of channels in the interface (start at 0)
Probe (uac.Probe) : ultrasound probe used for the transmit/reception event
Outputs:
Group (uac.Group) : built for plane wave transmit/reception event with a linear array probe
Excitation (uac.Excitation) : excitation to be used in another transmit event
Requirements:
urx 1.2.0 toolbox
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/urx/wiki
"""
# Maximum negative delay of the sequence
n_elements = len(probe.elements) # number of probe's elements
pitch = (
probe.elements[1].transform.translation.x - probe.elements[0].transform.translation.x
) # pitch of the probe [m]
theta_max: float = max(abs(thetas)) # maximum angle [°]
max_delays = -min(
np.sin(np.deg2rad(theta_max))
* (np.arange(-(n_elements - 1) / 2, (n_elements - 1) / 2 + 1))
* pitch
/ sound_speed
) # maximum negative delay of the plane wave sequence [s]
# Build the sequence of events
sequence = uac.VecEvent()
event = uac.Event() # defines the first event and excitation of the sequence ...
transmit_setup, excitation = makePlaneWaveTransmitSetupUac(
thetas[0],
voltage,
n_periods,
transmit_frequency,
max_delays,
sound_speed,
channel_mapping,
probe,
)
receive_setup = makeReceiveSetupUac(
n_samples, rx_time_offset, sampling_frequency, modulation_frequency, channel_mapping, probe
)
event.transmit_setup = transmit_setup
event.receive_setup = receive_setup
sequence.append(event)
for i in range(
1, len(thetas)
): # ... then increment the events of this sequence with the loop and reuse the excitation
event = uac.Event()
transmit_setup, excitation = makePlaneWaveTransmitSetupUac(
thetas[i],
voltage,
n_periods,
transmit_frequency,
max_delays,
sound_speed,
channel_mapping,
probe,
excitation,
)
receive_setup = makeReceiveSetupUac(
n_samples,
rx_time_offset,
sampling_frequency,
modulation_frequency,
channel_mapping,
probe,
)
event.transmit_setup = transmit_setup
event.receive_setup = receive_setup
event.time_offset = i / prf
sequence.append(event)
# Group
group = uac.Group() # defines the group of the sequence
group.description = "Group of plane waves" # group name or description
group.sound_speed = sound_speed
group.sequence = (
sequence # set all the events of the sequence once to avoid repetitive call to Group
)
group.time_offset = (
0 # test 0, normally must be bigger than the Rx Analog reconf (ex : 20e-6)
)
group.repetition_count = n_frames
group.period = 1 / frame_rate
group.data_type = uac.DataType.INT16 # 0: INT16, 1: INT32, 2:FLOAT ,3:DOUBLE ,-1: UNDEFINED
group.sampling_type = uac.SamplingType.RF # 0: RF, 1: IQ, -1: UNDEFINED
return group, excitationfunction Probe = makeLinearArrayUac(nElements, pitch, width, height, description)
%makeLinearArrayUac.m Creates a linear array probe in uac format
% Inputs:
% nElements : number of elements (transducers) in the probe
% pitch : distance between the center of adjacent elements [m]
% width : size of the element along the x-axis [m]
% height : size of the element along the y-axis [m]
% description : name or description of the probe
%
% Outputs:
% Probe (uac.Probe) : built as a linear array along the x-axis
%
% Drawing: Linear array (nElements = 12)
% __ width
% ____ x __ __ __ __ __ __ __ __ __ __ __ __
% /| / // // // // // // // // // // // / /
% / | /_//_//_//_//_//_//_//_//_//_//_//_/ / height
% y z ___ pitch
%
% +--+ perimeter of and element with + as elements edges
% / / defined either clockwise or counterclockwise
% +--+
%
% For more information see : https://github.com/moduleus/uac/wiki
%
% Requirements: urx 1.2.0 toolbox
% uac 1.2.0 toolbox
%% Element's geometry : all the elements share the same geometry
edges = [ -width/2, -width/2, width/2, width/2; ... % x, y z coordinates (dimension 1) of the 4 edges (dimensions 2)
-height/2, height/2, height/2, -height/2; ...
0, 0, 0, 0];
ElementGeometry = uac.ElementGeometry();
ElementGeometry.perimeter = [ uac.Vector3D(edges(1,1), edges(2,1), edges(3,1)),... % perimeter of one element
uac.Vector3D(edges(1,2), edges(2,2), edges(3,2)),...
uac.Vector3D(edges(1,3), edges(2,3), edges(3,3)),...
uac.Vector3D(edges(1,4), edges(2,4), edges(3,4))];
%% (Optional) Impulse response : all the elements share the same impulse response
ImpulseResponse = uac.ImpulseResponse();
ImpulseResponse.samplingFrequency = 50e6; % sampling frequency of the impulse response data [Hz]
ImpulseResponse.timeOffset = 0; % delay before impulse reponse starts [s]
ImpulseResponse.units = "N/A"; % unit of the impulse response
ImpulseResponse.data = [ 0.0000, 0.0467, 0.1641, 0.2780, 0.2521, 0.0000,... % impulse response depending on time
-0.4160, -0.7869, -0.8756, -0.5759, -0.0000, 0.5759,...
0.8756, 0.7869, 0.4160, 0.0000, -0.2521, -0.2780];
%% Build the elements' array
Elements = uac.Element(); % defines the first element ...
xmin = -pitch * (nElements - 1) / 2; % first element center position
Elements.transform.translation = uac.Vector3D(xmin, 0, 0); % set in the transform translation
% Elements.transform.rotation = uac.Vector3D(); % default value
Elements.elementGeometry = ElementGeometry; % set element's Geometry
Elements.impulseResponse = ImpulseResponse; % set element's impulse response (optional)
% Loop on the other elements % ... then increments this element with a loop
for i = 2:nElements
x = xmin + (i - 1) * pitch; % computes all the position element
Element = uac.Element();
Element.transform.translation = uac.Vector3D(x, 0, 0); % elements are only along x-axis
% Element.transform.rotation = uac.Vector3D(); % default value, rotation of the element
Element.elementGeometry = ElementGeometry; % shared geometry between elements
Element.impulseResponse = ImpulseResponse; % shared impulse response between elements
Elements = [Elements, Element]; % appends elements to the array
end
%% Build the probe
Probe = uac.Probe();
Probe.description = description;
Probe.type = uac.Probe.ProbeType.LINEAR; % 0: LINEAR, 1: CURVILINEAR, 2: RCA, 3: MATRIX, 4: SPARSE, -1: UNDEFINED
% Probe.transform.rotation = uac.Vector3D(); % default value,
% Probe.transform.translation = uac.Vector3D(); % default value
Probe.impulseResponses = ImpulseResponse; % could be an array in case of different impulse responses
Probe.elementGeometries = ElementGeometry; % could be an array in case of different elements geometries
Probe.elements = Elements; % set all the elements once to avoid repetitive call to Probe
end
function [Group, Excitation] = makePlaneWavesGroupUac(thetas, voltage, nPeriods, prf, nFrames, frameRate, transmitFrequency, soundSpeed, nSamples, rxTimeOffset, samplingFrequency, modulationFrequency, channelMapping, Probe)
%makePlaneWavesGroupUac.m Creates a plane waves sequence with a linear array in uac format
% Intputs:
% thetas : plane wave angles of the sequence [°]
% voltage : voltage of the excitation [Vpeak]
% nPeriods : number of periods of transmitted signal
% prf : pulse repetition frequency [Hz]
% nFrames : number of repetition of the sequence
% frameRate : repetition frequency of the sequence [Hz]
% transmitFrequency : central frequency of the transmitted signal [Hz]
% soundSpeed : celerity of the sound in the medium [m/s]
% nSamples : number of samples per channel per reception event
% rxTimeOffset : time before the sampling of the received signal [s]
% samplingFrequency : sampling frequency of the backscattered signal [Hz]
% modulationFrequency : frequency used for the demodulation of received signal in IQ [Hz]
% channelMapping : order of channels in the interface (start at 0)
% Probe (uac.Probe) : ultrasound probe used for the transmit/reception event
%
% Outputs:
% Group (uac.Group) : built for plane wave transmit/reception event with a linear array probe
% Excitation (uac.Excitation) : excitation to be used in another transmit event
%
% For more information see : https://github.com/moduleus/urx/wiki
%
% Requirements: uac 1.2.0 toolbox
%% Maximum negative delay of the sequence
nElements = length(Probe.elements); % number of probe's elements
pitch = Probe.elements(2).transform.translation.x-Probe.elements(1).transform.translation.x; % pitch of the probe [m]
thetaMax = max(abs(thetas)); % maximum angle [°]
maxDelays = -min(sind(thetaMax) * (-(nElements-1)/2:(nElements-1)/2) * pitch / soundSpeed); % maximum negative delay of the plane wave sequence [s]
%% Build the sequence of events
Sequence = uac.Event(); % defines the first event of the sequence ...
[TransmitSetup, Excitation] = makePlaneWaveTransmitSetupUac(thetas(1), voltage, nPeriods, transmitFrequency, maxDelays, soundSpeed, channelMapping, Probe); % build its transmit setup
ReceiveSetup = makeReceiveSetupUac(nSamples, rxTimeOffset, samplingFrequency, modulationFrequency, channelMapping, Probe); % build its receive setup
Sequence.transmitSetup = TransmitSetup;
Sequence.receiveSetup = ReceiveSetup;
Sequence.timeOffset = 0; % delay before the event starts [s]
% Sequence.triggerIn; % not used yet, store acquisition trigger in
% Sequence.triggerOut; % not used yet, store acquisition trigger out
% Sequence.hwConfig; % not used yet, store hardware configuration of the event
% Loop on the other events % ... then increments this sequence with a loop
for i=2:length(thetas)
Event=uac.Event();
[TransmitSetup, Excitation] = makePlaneWaveTransmitSetupUac(thetas(i), voltage, nPeriods, transmitFrequency, maxDelays, soundSpeed, channelMapping, Probe, Excitation);
ReceiveSetup = makeReceiveSetupUac(nSamples, rxTimeOffset, samplingFrequency, modulationFrequency, channelMapping, Probe);
Event.transmitSetup = TransmitSetup;
Event.receiveSetup = ReceiveSetup;
Event.timeOffset = (i-1)/prf;
% Event.triggerIn
% Event.triggerOut
% Event.hwConfig
Sequence = [Sequence, Event]; % append the event to the sequence
end
%% Group
Group = uac.Group(); % defines the group of the sequence
Group.description = 'Group of plane waves'; % group name or description
Group.soundSpeed = soundSpeed;
Group.sequence = Sequence; % set all the events of the sequence once to avoid repetitive call to Group
Group.timeOffset = 0; % test 0, normally must be bigger than the Rx Analog reconf (ex : 20e-6)
% Group.triggerIn
% Group.triggerOut
Group.repetitionCount = nFrames;
% Group.destinations
Group.period = 1/frameRate;
Group.dataType = uac.Group.DataType.INT16; % 0: INT16, 1: INT32, 2:FLOAT ,3:DOUBLE ,-1: UNDEFINED
Group.samplingType = uac.Group.SamplingType.RF; % 0: RF, 1: IQ, -1: UNDEFINED
end
function [TransmitSetup, Excitation] = makePlaneWaveTransmitSetupUac(theta, voltage, nPeriods, transmitFrequency, maxDelay, soundSpeed, channelMapping, Probe, Excitation)
%makePlaneWaveTransmitSetupUac.m Creates a plane waves transmit setup with a linear array in uac format
% Intputs:
% theta : plane wave angle of the event [°]
% voltage : voltage of the excitation [Vpeak]
% nPeriods : number of periods of transmitted signal
% transmitFrequency : central frequency of the transmitted signal [Hz]
% maxDelay : maximum delay of the plane wave sequence [s]
% soundSpeed : celerity of the sound in the medium [m/s]
% channelMapping : order of channels in the interface (start at 0)
% Probe (uac.Probe) : ultrasound probe used for the transmit event
% Excitation (uac.Excitation) : (optional) excitation reused from another transmit event
%
% Outputs:
% TransmitSetup (uac.TransmitSetup) : built for plane wave transmit event with a linear array probe
% Excitation (uac.Excitation) : excitation to be used in another transmit event
%
% For more information see : https://github.com/moduleus/uac/wiki
%
% Requirements: urx 1.2.0 toolbox
% uac 1.2.0 toolbox
%% Excitation parameters
excitationPostClampDuration = 900e-9; % clamp after excitation to avoid reflexion on the electronics [s]
excitationSamplingFrequency = 200e6; % sampling frequency of the excitation waveform [Hz]
%% Checks
if Probe.type ~= uac.Probe.ProbeType.LINEAR
error('Wrong type of probe')
end
%% Plane wave delay
nElements = length(Probe.elements); % number of probe's elements
pitch = Probe.elements(2).transform.translation.x-Probe.elements(1).transform.translation.x; % pitch of the probe
delays = sind(theta) * (-(nElements-1)/2:(nElements-1)/2) * pitch / soundSpeed; % linear delay along the element to make the plane wave
%% Wave
Wave = uac.Wave();
Wave.type = uac.Wave.WaveType.PLANE_WAVE; % 0: CONVERGING_WAVE, 1: DIVERGING_WAVE, 2:PLANE_WAVE, 3:CYLINDRICAL_WAVE, -1: UNDEFINED
Wave.parameters = [sind(theta), 0, cosd(theta)]; % defines the plane wave with the normal vector
Wave.timeZero = maxDelay + nPeriods/transmitFrequency/2; % delay to avoid negative time in the plane waves' delays
% Wave.timeZeroReferencePoint = uac.Vector3D(); % default value, reference point for the timeZero
%% Excitation
if ~exist('Excitation','var') % if no Excitation input to be reused, definition of the Excitation
Excitation = uac.Excitation();
Excitation.pulseShape = "square wave"; % description of the pulse shape
Excitation.transmitFrequency = transmitFrequency;
Excitation.samplingFrequency = excitationSamplingFrequency;
nExcitation = round(excitationSamplingFrequency/transmitFrequency/2); % number of samples for a half period of excitation
if nExcitation ~= floor(nExcitation) % excitationSamplingFrequency may not allow transmitFrequency so it is corrected
warning(['Real transmit frequency:', num2str(excitationSamplingFrequency/nExcitation/2), 'Hz instead of ', num2str(transmitFrequency), 'Hz']); % warn if frequency has been changed
end
nPostClamp = round(excitationSamplingFrequency*excitationPostClampDuration);
Excitation.waveform = [voltage * repmat([ones(1,nExcitation), - ones(1,nExcitation)], [1,nPeriods]), zeros(1, nPostClamp)]; % square excitation waveform, times voltage ?
end
%% TransmitSetup
TransmitSetup = uac.TransmitSetup();
TransmitSetup.wave = Wave;
TransmitSetup.activeElements = num2cell(channelMapping); % active elements (arrays dimension) per excitations (cells dimension), starting at 0
TransmitSetup.excitations = repmat(Excitation, [1, nElements]); % shared excitation for all the elements
TransmitSetup.delays = delays + maxDelay; % set minimum delay to 0 (avoid negative delays)
TransmitSetup.probe = Probe;
% TransmitSetup.probeTransform.translation = uac.Vector3D(); % default value, position of the probe for this transmit event
% TransmitSetup.probeTransform.rotation = uac.Vector3D(); % default value, rotation of the probe for this transmit event
TransmitSetup.timeOffset = 0;
% TransmitSetup.hwConfig; % not used here, store hardware configuration
end
function ReceiveSetup = makeReceiveSetupUac(nSamples, rxTimeOffset, samplingFrequency, modulationFrequency, channelMapping, Probe)
%makeReceiveSetupUac.m Creates a receive setup in uac format
% Inputs:
% nSamples : number of samples per channel per reception event
% rxTimeOffset : time before the sampling of the received signal [s]
% samplingFrequency : sampling frequency of the backscattered signal [Hz]
% modulationFrequency : frequency used for the demodulation of received signal in IQ [Hz]
% channelMapping : order of channels in the interface (start at 0)
% Probe (uac.Probe) : ultrasound probe used for the reception event
%
% Outputs:
% ReceiveSetup (uac.ReceiveSetup) : built for reception event with all the elements of the probe
%
% For more information see : https://github.com/moduleus/uac/wiki
%
% Requirements: uac 1.2.0 toolbox
%% ReceiveSetup
ReceiveSetup = uac.ReceiveSetup();
ReceiveSetup.samplingFrequency = samplingFrequency;
ReceiveSetup.modulationFrequency = modulationFrequency;
ReceiveSetup.numberSamples = nSamples;
ReceiveSetup.activeElements = num2cell(channelMapping); % active elements (arrays dimension) per excitations (cells dimension)
ReceiveSetup.probe = Probe;
% ReceiveSetup.probeTransform.translation = uac.Vector3D(); % default value, position of the probe for the reception event
% ReceiveSetup.probeTransform.rotation = uac.Vector3D(); % default value, rotation of the probe for the reception event
ReceiveSetup.tgcProfile = []; % Time Gain Compensation profile
ReceiveSetup.tgcSamplingFrequency = 0; % sampling of the TGC profile [Hz]
ReceiveSetup.timeOffset = rxTimeOffset; % delay before sampling [s]
% ReceiveSetup.hwConfig % configuration of the hardware
end
// helpers.h
#include <cstddef>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <urx/probe.h>
#include <uac/excitation.h>
#include <uac/group.h>
#include <uac/probe.h>
#include <uac/receive_setup.h>
#include <uac/transmit_setup.h>
// makeLinearArrayUac Creates a linear array probe in uac format
// Inputs:
// n_elements : number of elements (transducers) in the probe
// pitch : distance between the center of adjacent elements [m]
// width : size of the element along the x-axis [m]
// height : size of the element along the y-axis [m]
// description : name or description of the probe
//
// Outputs:
// Probe (uac.Probe) : built as a linear array along the x-axis
//
// Drawing: Linear array (n_elements = 12)
// __ width
// ____ x __ __ __ __ __ __ __ __ __ __ __ __
// /| / // // // // // // // // // // // / /
// / | /_//_//_//_//_//_//_//_//_//_//_//_/ / height
// y z ___ pitch
//
// +--+ perimeter of and element with + as elements edges
// / / defined either clockwise or counterclockwise
// +--+
//
// For more information see : https://github.com/moduleus/uac/wiki
//
// Requirements: uac 1.2.0 toolbox
std::shared_ptr<uac::Probe> makeLinearArrayUac(size_t n_elements, double pitch, double width,
double height, const std::string& description);
/*
makePlaneWaveTransmitSetupUac creates a plane waves transmit setup with a linear array in uac format
Inputs:
theta : plane wave angle of the event [°]
voltage : voltage of the excitation [Vpeak]
n_periods : number of periods of transmitted signal
transmit_frequency : central frequency of the transmitted signal [Hz]
max_delay : maximum delay of the plane wave sequence [s]
sound_speed : celerity of the sound in the medium [m/s]
probe (uac.Probe) : ultrasound probe used for the transmit event
excitation (uac.Excitation) : (optional) excitation reused from another transmit event
Outputs:
TransmitSetup (uac.TransmitSetup) : built for plane wave transmit event with a linear array probe
Excitation (uac.Excitation) : excitation to be used in another transmit event
Requirements:
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/uac/wiki
*/
std::pair<uac::TransmitSetup, std::shared_ptr<uac::Excitation>> makePlaneWaveTransmitSetupUac(
double theta, double voltage, size_t n_periods, double transmit_frequency, double max_delay,
double sound_speed, const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe,
const std::shared_ptr<uac::Excitation>& excitation_input = {});
/*
makeReceiveSetupUac Creates a receive setup in uac format
Inputs:
nSamples : number of samples per channel per reception event
rxTimeOffset : time before the sampling of the received signal [s]
samplingFrequency : sampling frequency of the backscattered signal [Hz]
modulationFrequency : frequency used for the demodulation of received signal in IQ [Hz]
channelMapping : order of channels in the interface (start at 0)
Probe (uac.Probe) : ultrasound probe used for the reception event
Outputs:
ReceiveSetup (uac.ReceiveSetup) : built for reception event with all the elements of the probe
For more information see : https://github.com/moduleus/uac/wiki
Requirements: uac 1.2.0 toolbox
*/
uac::ReceiveSetup makeReceiveSetupUac(uint32_t n_samples, double rx_time_offset,
double sampling_frequency, double modulation_frequency,
const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe);
/*
makePlaneWavesGroupUac creates a plane waves sequence with a linear array in uac format
Inputs:
thetas : plane wave angles of the sequence [°]
voltage : voltage of the excitation [Vpeak]
nPeriods : number of periods of transmitted signal
prf : pulse repetition frequency [Hz]
nFrames : number of repetition of the sequence
frameRate : repetition frequency of the sequence [Hz]
transmitFrequency : central frequency of the transmitted signal [Hz]
soundSpeed : celerity of the sound in the medium [m/s]
nSamples : number of samples per channel per reception event
rxTimeOffset : time before the sampling of the received signal [s]
samplingFrequency : sampling frequency of the backscattered signal [Hz]
modulationFrequency : frequency used for the demodulation of received signal in IQ [Hz]
channelMapping : order of channels in the interface (start at 0)
Probe (uac.Probe) : ultrasound probe used for the transmit/reception event
Outputs:
Group (uac.Group) : built for plane wave transmit/reception event with a linear array probe
Excitation (uac.Excitation) : excitation to be used in another transmit event
Requirements:
urx 1.2.0 toolbox
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/urx/wiki
*/
std::pair<std::shared_ptr<uac::Group>, std::shared_ptr<uac::Excitation>> makePlaneWavesGroupUac(
const std::vector<double>& thetas, double voltage, size_t n_periods, double prf,
uint32_t n_frames, double frame_rate, double transmit_frequency, double sound_speed,
uint32_t n_samples, double rx_time_offset, double sampling_frequency,
double modulation_frequency, const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe);// helpers.cpp
#include "helpers.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <ios>
#include <iostream>
#include <limits>
#include <ostream>
#include <stdexcept>
#include <vector>
#include <urx/detail/double_nan.h>
#include <urx/element.h>
#include <urx/element_geometry.h>
#include <urx/enums.h>
#include <urx/impulse_response.h>
#include <urx/probe.h>
#include <urx/transform.h>
#include <urx/vector.h>
#include <urx/wave.h>
#include <uac/element.h>
#include <uac/element_geometry.h>
#include <uac/enums.h>
#include <uac/event.h>
#include <uac/excitation.h>
#include <uac/group.h>
#include <uac/impulse_response.h>
#include <uac/probe.h>
#include <uac/receive_setup.h>
#include <uac/transmit_setup.h>
#include <uac/wave.h>
#ifndef M_PI
#define M_PI 3.1415926535897932384626433
#endif
std::shared_ptr<uac::Probe> makeLinearArrayUac(size_t n_elements, double pitch, double width,
double height, const std::string& description) {
//makeLinearArrayUac.m Creates a linear array probe in uac format
// Inputs:
// n_elements : number of elements (transducers) in the probe
// pitch : distance between the center of adjacent elements [m]
// width : size of the element along the x-axis [m]
// height : size of the element along the y-axis [m]
// description : name or description of the probe
//
// Outputs:
// Probe (uac.Probe) : built as a linear array along the x-axis
//
// Drawing: Linear array (n_elements = 12)
// __ width
// ____ x __ __ __ __ __ __ __ __ __ __ __ __
// /| / // // // // // // // // // // // / /
// / | /_//_//_//_//_//_//_//_//_//_//_//_/ / height
// y z ___ pitch
//
// +--+ perimeter of and element with + as elements edges
// / / defined either clockwise or counterclockwise
// +--+
//
// For more information see : https://github.com/moduleus/uac/wiki
//
// Requirements: urx 1.2.0 toolbox
// uac 1.2.0 toolbox
// Element's geometry : all the elements share the same geometry
const std::array<std::array<double, 4>, 3> edges = {
{{{-width / 2, -width / 2, width / 2, width / 2}},
{{// x, y z coordinates (dimension 1) of the 4 edges (dimensions 2)
-height / 2, height / 2, height / 2, -height / 2}},
{{0, 0, 0, 0}}}};
const std::shared_ptr<uac::ElementGeometry> element_geometry =
std::make_shared<uac::ElementGeometry>();
element_geometry->perimeter = {{edges[0][0], edges[1][0], edges[2][0]},
// perimeter of one element
{edges[0][1], edges[1][1], edges[2][1]},
{edges[0][2], edges[1][2], edges[2][2]},
{edges[0][3], edges[1][3], edges[2][3]}};
// (Optional) Impulse response : all the elements share the same impulse response
const std::shared_ptr<uac::ImpulseResponse> impulse_response =
std::make_shared<uac::ImpulseResponse>();
impulse_response->sampling_frequency =
50e6; // sampling frequency of the impulse response data [Hz]
impulse_response->time_offset = 0; // delay before impulse reponse starts [s]
impulse_response->units = "N/A"; // unit of the impulse response
impulse_response->data = {0.0000, 0.0467, 0.1641, 0.2780, 0.2521, 0.0000,
// impulse response depending on time
-0.4160, -0.7869, -0.8756, -0.5759, -0.0000, 0.5759, 0.8756, 0.7869,
0.4160, 0.0000, -0.2521, -0.2780};
// Build the elements' array
std::vector<uac::Element> elements;
elements.reserve(n_elements);
const double xmin = -pitch * (n_elements - 1) / 2; // first element center position
// Loop on the other elements
// then increments this element with a loop
for (size_t i = 0; i < n_elements; i++) {
const double x = xmin + i * pitch; // computes all the position element
uac::Element element;
element.transform.translation = {x, 0, 0}; // elements are only along x-axis
// element.transform.rotation = {}; // default value, rotation of the element
element.element_geometry = element_geometry; // shared geometry between elements
element.impulse_response = impulse_response; // shared impulse response between elements
elements.push_back(element); // appends elements to the array
}
// Build the probe
std::shared_ptr<uac::Probe> probe = std::make_shared<uac::Probe>();
probe->description = description;
// 0: LINEAR, 1: CURVILINEAR, 2: RCA, 3: MATRIX, 4: SPARSE, -1: UNDEFINED
probe->type = uac::ProbeType::LINEAR;
// probe->transform.rotation = {}; // default value,
// probe->transform.translation = {}; // default value
// could be an array in case of different impulse responses
probe->impulse_responses.push_back(impulse_response);
// could be an array in case of different elements geometries
probe->element_geometries.push_back(element_geometry);
// set all the elements once to avoid repetitive call to probe
probe->elements = elements;
return probe;
}
std::pair<uac::TransmitSetup, std::shared_ptr<uac::Excitation>> makePlaneWaveTransmitSetupUac(
double theta, double voltage, size_t n_periods, double transmit_frequency, double max_delay,
double sound_speed, const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe,
const std::shared_ptr<uac::Excitation>& excitation_input) {
/*
makePlaneWaveTransmitSetupUac creates a plane waves transmit setup with a linear array in uac format
Inputs:
theta : plane wave angle of the event [°]
voltage : voltage of the excitation [Vpeak]
n_periods : number of periods of transmitted signal
transmit_frequency : central frequency of the transmitted signal [Hz]
max_delay : maximum delay of the plane wave sequence [s]
sound_speed : celerity of the sound in the medium [m/s]
probe (uac.Probe) : ultrasound probe used for the transmit event
excitation (uac.Excitation) : (optional) excitation reused from another transmit event
Outputs:
TransmitSetup (uac.TransmitSetup) : built for plane wave transmit event with a linear array probe
Excitation (uac.Excitation) : excitation to be used in another transmit event
Requirements:
urx 1.2.0 toolbox
uac 1.2.0 toolbox
More information:
see : https://github.com/moduleus/uac/wiki
*/
// Checks
if (probe->type != uac::ProbeType::LINEAR) {
throw std::invalid_argument("Wrong type of probe");
}
// Excitation parameters
const double excitation_post_clamp_duration = 900e-9;
const double excitation_sampling_frequency = 200e6;
// Delay calculation
const uint32_t n_elements = static_cast<uint32_t>(probe->elements.size());
const double pitch =
probe->elements[1].transform.translation.x - probe->elements[0].transform.translation.x;
std::vector<double> delays(n_elements);
for (uint32_t i = 0; i < n_elements; ++i) {
const double element_index = i - (n_elements - 1) / 2.0;
delays[i] = std::sin(theta * M_PI / 180.0) * element_index * pitch / sound_speed;
}
// Wave
uac::Wave wave;
wave.type = uac::WaveType::PLANE_WAVE;
wave.parameters = {std::sin(theta * M_PI / 180.0), 0.0, std::cos(theta * M_PI / 180.0)};
wave.time_zero = max_delay + n_periods / transmit_frequency / 2.0;
// Excitation
std::shared_ptr<uac::Excitation> excitation;
if (excitation_input == nullptr) {
excitation = std::make_shared<uac::Excitation>();
excitation->pulse_shape = "square wave";
excitation->transmit_frequency = transmit_frequency;
excitation->sampling_frequency = excitation_sampling_frequency;
const size_t n_samples_excitation =
static_cast<size_t>(std::lround(excitation_sampling_frequency / transmit_frequency / 2.0));
const double real_freq = excitation_sampling_frequency / n_samples_excitation / 2.0;
if (std::abs(real_freq - transmit_frequency) > 1e-6) {
std::cout << "(Warning) Real transmit frequency: " << real_freq << " Hz instead of "
<< transmit_frequency << "Hz\n";
}
const size_t n_post_clamp = static_cast<size_t>(
std::lround(excitation_sampling_frequency * excitation_post_clamp_duration));
// Generate waveform
std::vector<double> waveform;
for (size_t period = 0; period < n_periods; ++period) {
for (int i = 0; i < n_samples_excitation; ++i) {
waveform.push_back(voltage);
}
for (size_t i = 0; i < n_samples_excitation; ++i) {
waveform.push_back(-voltage);
}
}
for (size_t i = 0; i < n_post_clamp; ++i) {
waveform.push_back(0.0);
}
excitation->waveform = waveform;
} else {
excitation = excitation_input;
}
// TransmitSetup
uac::TransmitSetup transmit_setup;
transmit_setup.wave = wave;
// Active elements
transmit_setup.active_elements = channel_mapping;
for (uint32_t i = 0; i < n_elements; ++i) {
transmit_setup.excitations.push_back(excitation);
}
// Add max_delay to delays
for (double& delay : delays) {
delay += max_delay;
}
transmit_setup.delays = delays;
transmit_setup.probe = probe;
return {transmit_setup, excitation};
}
uac::ReceiveSetup makeReceiveSetupUac(uint32_t n_samples, double rx_time_offset,
double sampling_frequency, double modulation_frequency,
const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe) {
// ReceiveSetup
uac::ReceiveSetup receive_setup;
receive_setup.sampling_frequency = sampling_frequency;
receive_setup.modulation_frequency = modulation_frequency;
receive_setup.number_samples = n_samples;
receive_setup.active_elements =
channel_mapping; // active elements (arrays dimension) per excitations (cells dimension)
receive_setup.probe = probe;
// receive_setup.probe_transform.translation = {}; // default value, position of the probe for the reception event
// receive_setup.probe_transform.rotation = {}; // default value, rotation of the probe for the reception event
// receive_setup.tgc_profile; // Time Gain Compensation profile
receive_setup.tgc_sampling_frequency = 0; // sampling of the TGC profile [Hz]
receive_setup.time_offset = rx_time_offset; // delay before sampling [s]
// receive_setup.hwConfig // configuration of the hardware
return receive_setup;
}
std::pair<std::shared_ptr<uac::Group>, std::shared_ptr<uac::Excitation>> makePlaneWavesGroupUac(
const std::vector<double>& thetas, double voltage, size_t n_periods, double prf,
uint32_t n_frames, double frame_rate, double transmit_frequency, double sound_speed,
uint32_t n_samples, double rx_time_offset, double sampling_frequency,
double modulation_frequency, const std::vector<std::vector<uint32_t>>& channel_mapping,
const std::shared_ptr<uac::Probe>& probe) {
// Maximum negative delay of the sequence
const uint32_t n_elements = static_cast<uint32_t>(probe->elements.size());
// Calculate pitch (assuming probe->elements has transform.translation.x)
const double pitch = probe->elements[1].transform.translation.x -
probe->elements[0].transform.translation.x; // pitch of the probe [m]
// Find maximum absolute theta
double theta_max = std::numeric_limits<double>::lowest();
for (const double theta : thetas) {
theta_max = std::max(theta_max, std::abs(theta));
}
// Calculate maximum negative delay
double min_delay = std::numeric_limits<double>::max();
for (uint32_t i = 0; i < n_elements; ++i) {
const double element_pos = (i - (n_elements - 1) / 2.0);
const double delay = std::sin(theta_max * M_PI / 180.0) * element_pos * pitch / sound_speed;
min_delay = std::min(min_delay, delay);
}
const double max_delays = -min_delay; // maximum negative delay of the plane wave sequence [s]
// Build the sequence of events
std::vector<uac::Event> sequence;
// Define the first event and excitation of the sequence
uac::Event event;
auto [transmit_setup, excitation] =
makePlaneWaveTransmitSetupUac(thetas[0], voltage, n_periods, transmit_frequency, max_delays,
sound_speed, channel_mapping, probe);
uac::ReceiveSetup receive_setup = makeReceiveSetupUac(
n_samples, rx_time_offset, sampling_frequency, modulation_frequency, channel_mapping, probe);
event.transmit_setup = transmit_setup;
event.receive_setup = std::move(receive_setup);
sequence.push_back(event);
// Increment the events of this sequence with the loop and reuse the excitation
for (size_t i = 1; i < thetas.size(); ++i) {
uac::Event event;
const auto [transmit_setup_i, excitation_i] =
makePlaneWaveTransmitSetupUac(thetas[i], voltage, n_periods, transmit_frequency, max_delays,
sound_speed, channel_mapping, probe, excitation);
uac::ReceiveSetup receive_setup =
makeReceiveSetupUac(n_samples, rx_time_offset, sampling_frequency, modulation_frequency,
channel_mapping, probe);
event.transmit_setup = transmit_setup_i;
event.receive_setup = std::move(receive_setup);
event.time_offset = i / prf;
sequence.push_back(event);
}
// Group
const std::shared_ptr<uac::Group> group =
std::make_shared<uac::Group>(); // defines the group of the sequence
group->description = "Group of plane waves"; // group name or description
group->sound_speed = sound_speed;
group->sequence = sequence; // set all the events of the sequence
group->time_offset =
0.0; // test 0, normally must be bigger than the Rx Analog reconf (ex: 20e-6)
group->repetition_count = n_frames;
group->period = 1.0 / frame_rate;
group->data_type = urx::DataType::INT16; // 0: INT16, 1: INT32, 2:FLOAT, 3:DOUBLE, -1: UNDEFINED
group->sampling_type = urx::SamplingType::RF; // 0: RF, 1: IQ, -1: UNDEFINED
return {group, excitation};
}