tutorials.helpers - moduleus/urx GitHub Wiki
To run the tutorials, 4 helpers is used to ease UAC generation:
- makeLinearArrayUac
- makePlaneWaveTransmitSetupUac
- makeReceiveSetupUac
- makePlaneWavesGroupUac
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};
}