Working with LabOne Software Trigger Data in Matlab

LabOne’s Software (SW) Trigger allows the user to save bursts of instrument signals (e.g. its demodulator samples or a PID controller’s error value) when a specified trigger condition is met. The SW Trigger works much in the same way as a laboratory oscilloscope but has the advantage that the saved data is directly available on the PC for further analysis. My colleague, Romain, has already described how to acquire instrument data with the SW Trigger in the LabOne User Interface (UI) in his blogs LabOne Software Trigger as a tool for multichannel mapping applications with the UHFLI and  LabOne for HF2: using digital lines as trigger sources. The purpose of this blog is to explain how to work with SW Trigger data saved by the LabOne UI in Matlab, in particular, how to precisely superimpose the captured signal segments using the “trigger timestamp” that available in the saved data.

Once data has been captured in the LabOne UI’s SW Trigger tab, the user can manage the signal segments and save them using the “History” sub-tab. The segments can be saved to disk in either Matlab or CSV format; this is specified in the “Record Data” section of the Config tab. The “Record Data” section also informs which directory the SW Trigger data will be saved to.

LabOne User Interface's Config and SW Trigger tabs.

The LabOne User Interface’s Config and SW Trigger tabs. The fields highlighted are relevant to saving SW Trigger data.

We can load the .mat file saved by the LabOne UI using Matlab’s load function. The loaded data contains nested structs, the structure of which reflects the path of the saved signal segments. The first nested Matlab struct is the top-level device node (in the example below dev2006 was the instrument used). Then, for example, if we’ve saved samples from demodulator channels 1 and 4 (as visible in the screenshot’s plot above), the next nested Matlab structs will be the demod and then their samples:

>> data = load('swtrigger_data_00000/swtrigger_data_00000.mat') % Load the data saved by the LabOne User Interface.
>> length(data.dev2006.demods.samples)
ans =
    4
>> channel = 1; isempty(data.dev2006.demods(channel).sample) % Two channels have data; also not empty if channel=4.
ans =
    0
>> channel = 2; isempty(data.dev2006.demods(channel).sample) % Two channels were not saved; also empty if channel=3.
ans =
    1

The demods struct has length 4, it is an array of structs and each element in the array indexes a demodulator channel. The sample field of the demods struct has data present in the first and fourth elements of the array, whereas the second and third elements are empty, reflecting the demodulator channels we saved (or subscribed to) in the SW Trigger. The sample field is a cell array, the elements of which contain the signal segments for each trigger received. Each element is a struct containing the demodulator sample values for that trigger.

>> iscell(data.dev2006.demods(channel).sample)
ans =
    1
>> trigger = 1; isstruct(data.dev2006.demods(channel).sample{trigger}) % Access the data acquired from the first trigger.
ans =
    1
>> fieldnames(data.dev2006.demods(channel).sample{trigger}) % The demod sample fields from the first signal segment.
ans =
    'timestamp' % An array of the instrument's internal timestamps for the demodulator data in ticks.
    'x'         % An array of the demodulator's X value.
    'y'         % An array of the demodulator's Y value.
    'frequency' % An array of the reference oscillator frequencies used by the demodulator.
    'phase'     % An array of the oscillator's phase.
    'bits'      % An array of the DIO's value.
    'auxin0'    % An array of the auxiliary input 0 value.
    'auxin1'    % An array of the auxiliary input 1 value.
    'time'      % A struct containing information about the signal segment, including the trigger timestamp.

Note that the values of the instrument’s Digital I/O connector and auxiliary inputs are included in the demodulator sample and are available for every timestamp allowing alignment of data from external instruments.

The time struct contains important information for the entire signal segment, in particular, the trigger field contains trigger timestamp for each segment. This is the timestamp corresponding to when the trigger was activated for the recording of this segment. In general, the trigger timestamp will lie in between two of the segment’s demodulator timestamps and allowd the signal segments to be aligned with each other relative to the point in time when the trigger was activated.

The plot below was created by the function plot_segments() which demonstrates how to use the demod sample fields by plotting the demodulator R value for each triggered signal segment. The data is plotted once in absolute time (above) using the instrument’s timestamp and once with all signal segments superimposed (below), aligned by the trigger timestamp.

Matlab figure() displaying demodulator samples saved by the SW Trigger.

The Matlab figure generated by the plot_segments() function.

Here’s the code for plot_segments (download here):

function plot_segments(sample)
% Plot the SW Trigger's signal segments in one demodulator's sample cell array.
 num_segments = length(sample);
 clockbase = 1.8e9; % The UHF's ADC sampling rate (can be obtained from the device's clockbase node).
 colors = hsv(num_segments);

 figure();
 subplot(2, 1, 1)
 hold on; grid on;
 t0 = double(sample{1}.timestamp(1))/clockbase;
 for i=1:num_segments
   % Loop over the signal segments and plot each one in continuous time.
   t = double(sample{i}.timestamp)/clockbase;
   trigger_timestamp = double(sample{i}.time.trigger);
   plot(t - t0, abs(sample{i}.x + j*sample{i}.y), 'color', colors(i,:));
 end
 title(sprintf('\\bf Demodulator sample segments from %d triggers(continuous time).', num_segments))
 xlabel('\\bf Time (s)')
 ylabel('\\bf Demod R, (V)')

 subplot(2, 1, 2);
 for i=1:num_segments
   % Loop over the signal segments and plot each segment superimposed w.r.t the trigger timestamp.
   t = double(sample{i}.timestamp)/clockbase;
   trigger_time = double(sample{i}.time.trigger)/clockbase;
   plot(t - trigger_time, abs(sample{i}.x + j*sample{i}.y), 'color', colors(i,:));
 end
 ylimits = ylim();
 line([0, 0],ylim, 'Color', 'k', 'LineStyle', '--')
 xlimits = xlim();
 line(xlim, [0.070, 0.070], 'Color', 'k', 'LineStyle', '--'% This example used an edge trigger with a threshold of 0.070 V.
 hold on; grid on;
 title(sprintf('\\bf Demodulator sample segments from %d triggers\n(superimposed relative to trigger timestamp).', num_segments))
 xlabel('\\bf Time, relative to the trigger timestamp (s)')
 ylabel('\\bf Demod R, (V)')
 axis('tight')
end