Main Content

Generate Generic C Code for a Human Health Monitoring Network

Since R2025a

This example shows how to generate a generic C MEX application for a deep learning network that reconstructs electrocardiogram (ECG) signals from a continuous wave (CW) radar. CW radars are used for daily long-term monitoring and are an alternative to wearable devices, which can monitor ECG signals directly.

To learn about the network and the network training process, see Human Health Monitoring Using Continuous Wave Radar and Deep Learning. This example uses the network that contains a maximal overlap discrete wavelet transform (MODWT) layer.

Download and Prepare Data

The data set [1] in this example contains synchronized data from a CW radar and ECG signals measured simultaneously by a reference device on 30 healthy subjects. The CW radar system uses the six-port technique and operates at 24 GHz in the Industrial Scientific and Medical (ISM) band.

Because the original data set contains a large volume of data, this example uses the data from resting, apnea, and Valsalva maneuver scenarios. Additionally, it uses only the data from subjects 1 through 5 to train and validate the model, and data from subject 6 to test the trained model.

Because the main information in the ECG signal is usually in a frequency band less than 100 Hz, all signals are downsampled to 200 Hz and divided into segments of 1024 points to create signals of approximately 5s.

Download the data set by using the downloadSupportFile function. The whole data set is approximately 16 MB in size and contains two folders. The trainVal folder contains the training and validation data and the test folder contains the test data. Each folder contains ecg and radar folders for ECG and radar signals, respectively.

datasetZipFile = matlab.internal.examples.downloadSupportFile('SPT','data/SynchronizedRadarECGData.zip');
datasetFolder = fullfile(fileparts(datasetZipFile),'SynchronizedRadarECGData');
if ~exist(datasetFolder,'dir')     
    unzip(datasetZipFile,datasetFolder);
end

Create signalDatastore objects to access the data in the files. Create datastores radarTrainValDs and radarTestDs to store the CW radar data used for training/validation and testing, respectively. Then create ecgTrainValDs and ecgTestDs datastore objects to store the ECG signals for training/validation and testing, respectively.

radarTrainValDs = signalDatastore(fullfile(datasetFolder,"trainVal","radar"));
radarTestDs = signalDatastore(fullfile(datasetFolder,"test","radar"));
ecgTrainValDs = signalDatastore(fullfile(datasetFolder,"trainVal","ecg"));
ecgTestDs = signalDatastore(fullfile(datasetFolder,"test","ecg"));

View the categories and distribution of the training data.

trainCats = filenames2labels(radarTrainValDs,'ExtractBefore','_radar');
summary(trainCats)
trainCats: 830×1 categorical

     GDN0001_Resting        59 
     GDN0001_Valsalva       97 
     GDN0002_Resting        60 
     GDN0002_Valsalva       97 
     GDN0003_Resting        58 
     GDN0003_Valsalva      103 
     GDN0004_Apnea          14 
     GDN0004_Resting        58 
     GDN0004_Valsalva      106 
     GDN0005_Apnea          14 
     GDN0005_Resting        59 
     GDN0005_Valsalva      105 
     <undefined>             0 

View the categories and distribution of the test data.

testCats = filenames2labels(radarTestDs,'ExtractBefore','_radar');
summary(testCats)
testCats: 200×1 categorical

     GDN0006_Apnea          14 
     GDN0006_Resting        59 
     GDN0006_Valsalva      127 
     <undefined>             0 

Apply normalization to the ECG signals. Use the helperNormalize helper function to center each signal by subtracting its median and rescale it so that its maximum peak is 1.

ecgTrainValDs = transform(ecgTrainValDs,@helperNormalize);
ecgTestDs = transform(ecgTestDs,@helperNormalize);

Combine the data sets into a training and a test data set.

trainValDs = combine(radarTrainValDs,ecgTrainValDs);
testDs = combine(radarTestDs,ecgTestDs);

Visualize the CW radar and measured ECG signals for one of each of the scenarios. The deep learning network takes the radar signal and reconstructs it to the corresponding ECG signal.

numCats = cumsum(countcats(testCats));
previewindices = [randi([1,numCats(1)]),randi([numCats(1)+1,numCats(2)]),randi([numCats(2)+1,numCats(3)])];
testindices = [randi([1,numCats(1)]),randi([numCats(1)+1,numCats(2)]),randi([numCats(2)+1,numCats(3)])];

helperPlotData(testDs,previewindices);

Inspect the Entry-Point Function

The radarToEcgNet_predict entry-point function loads the dlnetwork object from the radarToEcgNet MAT file into a persistent variable and reuses the variable for subsequent prediction calls. The entry-point function converts the input CW radar data into a dlarray format and calls the predict method on the network by using the dlarray input data. Finally, it reshapes the output into a two-dimensional array.

type('radarToEcgNet_predict.m')
function out = radarToEcgNet_predict(in) %#codegen
% Copyright 2024 The MathWorks, Inc.

persistent dlnet
if isempty(dlnet)
    dlnet = coder.loadDeepLearningNetwork('radarToEcgNet.mat');
end

% convert input to a dlarray
dlIn = dlarray(single(in),'CTB');

% pass in input
dlOut = predict(dlnet,dlIn);

% extract data from dlarray and reshape it to two dimensions
out = extractdata(dlOut);
out = reshape(out, [1,numel(dlOut)]);

end

Configure Code Generation Options and Generate Code

Create a code generation configuration object by using the coder.config (MATLAB Coder) function. For the cfg object, the default value of DeepLearningConfig is a coder.DeepLearningCodeConfig (MATLAB Coder) object. By default, the generated code does not depend on any third-party deep learning libraries.

cfg = coder.config('mex');
cfg.DeepLearningConfig
ans = 
  coder.DeepLearningCodeConfig with properties:

            TargetLibrary: 'none'

   CPU configuration parameters
    LearnablesCompression: 'None'

Because the network requires the CW radar data as an input argument, prepare a sample CW radar data for code generation.

exampleRadarData = read(radarTrainValDs);

Run the codegen command. Specify the sample data as the input type.

codegen radarToEcgNet_predict -config cfg -args {exampleRadarData}
Code generation successful.

Run the Generated MEX Function

Run the generated MEX function by passing a CW radar signal.

idx = testindices(1);
ds = subset(radarTestDs,idx);
testRadarData = read(ds);
predictedEcg_mex = radarToEcgNet_predict_mex(testRadarData);

Run the entry-point function in MATLAB to compare the results.

predictedEcg = radarToEcgNet_predict(testRadarData);

Plot the data.

tiledlayout(1, 2, 'Padding', 'none', 'TileSpacing', 'compact');

fs = 200;
t = linspace(0,length(predictedEcg)/fs,length(predictedEcg));

plot(t,predictedEcg)
title("Reconstructed ECG Signal from MATLAB")
xlabel("Time (s)")
grid on

nexttile(2)

plot(t,predictedEcg_mex)
title("Reconstructed ECG Signal from Generated MEX")
xlabel("Time (s)")
grid on

The reconstructed ECG signals from MATLAB and the generated MEX function match.

Compare Predicted Signals to the Measured ECG Signals

Visualize the output of the deep learning network for all three input signal scenarios and compare it to the measured ECG signal data. To do this, call the helper function helperPlotData by passing the generated MEX function, radarToEcgNet_predict_mex, as a function handle. The helper function picks a representative input for each scenario, runs the generated MEX with that data, and visualizes the result alongside the corresponding measured ECG signal.

helperPlotData(testDs,testindices,@radarToEcgNet_predict_mex);

Reference

[1] Schellenberger, S., Shi, K., Steigleder, T. et al. A data set of clinically recorded radar vital signs with synchronised reference sensor signals. Sci Data 7, 291 (2020). https://doi.org/10.1038/s41597-020-00629-5

Helper Functions

The helperNormalize function normalizes input signals by subtracting the median and dividing the signals by the maximum value.

function x = helperNormalize(x)
% This function is only intended to support this example. It may be changed
% or removed in a future release. 
    x = x-median(x);
    x = {x/max(x)};
end

The helperPlotData function plots the radar and ECG signals.

function  helperPlotData(DS,Indices,mexFunction)
% This function is only intended to support this example. It may be changed
% or removed in a future release. 
    arguments
       DS 
       Indices
       mexFunction = []
    end
    fs = 200;
    N = numel(Indices);
    M = 2;
    if ~isempty(mexFunction)
        M = M + 1;
    end

    tiledlayout(M, N, 'Padding', 'none', 'TileSpacing', 'compact');
    for i = 1:N
        idx = Indices(i);
        ds = subset(DS,idx);
        [~,name,~] = fileparts(ds.UnderlyingDatastores{1}.Files{1});
        data = read(ds);
        radar = data{1};
        ecg = data{2};
        t = linspace(0,length(radar)/fs,length(radar));

        nexttile(i)
        plot(t,radar)
        title(["Sample",regexprep(name, {'_','radar'}, '')])
        xlabel(["Radar Signal","Time (s)"])
        grid on
    
        nexttile(N+i)
        plot(t,ecg)
        xlabel(["Measured ECG Signal","Time (s)"])
        ylim([-0.3,1])
        grid on
    
        if ~isempty(mexFunction)
            nexttile(2*N+i)
            predictedEcg = mexFunction(radar);
            plot(t,predictedEcg)
            grid on
            ylim([-0.3,1])
            xlabel(["Reconstructed ECG Signal from Generated MEX","Time (s)"])
        end
    end
    set(gcf,'Position',[0 0 300*N,150*M])
end

See Also

Functions

Objects

Topics