Deploy Signal Classifier Using Wavelets and Deep Learning on Raspberry Pi
This example shows the workflow to classify human electrocardiogram (ECG) signals using the Continuous Wavelet Transform (CWT) and a deep convolutional neural network (CNN). This example also provides information on how to generate and deploy the code and CNN for prediction on a Raspberry Pi target (ARM®-based device).
SqueezeNet is a deep CNN originally designed to classify images in 1000 categories. In the example Classify Time Series Using Wavelet Analysis and Deep Learning (Wavelet Toolbox), SqueezeNet is retrained to classify ECG waveforms based on their scalograms. A scalogram is a time-frequency representation of the signal and is the absolute value of the CWT of the signal. We reuse the retrained SqueezeNet in this example.
ECG Data Description
In this example, ECG data from PhysioNet is used. The ECG data is obtained from three groups of people: persons with cardiac arrhythmia (ARR), persons with congestive heart failure (CHF), and persons with normal sinus rhythms (NSR). The data set includes 96 recordings from persons with ARR, 30 recordings from persons with CHF, and 36 recordings from persons with NSR. The 162 ECG recordings are from three PhysioNet databases: MIT-BIH Arrhythmia Database [2][3], MIT-BIH Normal Sinus Rhythm Database [3], and The BIDMC Congestive Heart Failure Database [1][3]. Shortened ECG data of the above references can be downloaded from the GitHub repository.
Prerequisites
ARM processor that supports the NEON extension
ARM Compute Library version 19.05 (on the target ARM hardware)
Environment variables for the compilers and libraries
For supported versions of libraries and for information about setting up environment variables, see Prerequisites for Deep Learning with MATLAB Coder (MATLAB Coder). This example is not supported in MATLAB Online™.
Functionality of Generated Code
The core function in the generated executable, processECG
, uses 65,536 samples of single-precision ECG data as input. The function:
Takes the CWT of the ECG data.
Obtains scalogram from wavelet coefficients.
Converts the scalogram to an RGB image of dimension 227-by-227-by-3. This makes the image compatible with the SqueezeNet network architecture.
Performs prediction to classify the image using SqueezeNet.
type processECG
function [YPred] = processECG(input) % processECG function - converts 1D ECG to image and predicts the syndrome % of heart disease % % This function is only intended to support the example: % Signal Classification Code Generation Using Wavelets and % Deep Learning on Raspberry Pi. It may change or be removed in a % future release. % Copyright 2020 The MathWorks, Inc. % colourmap for image transformation persistent net jetdata; if(isempty(jetdata)) jetdata = colourmap(128,class(input)); end % Squeezenet trained network if(isempty(net)) net = coder.loadDeepLearningNetwork('trainedNetCGARM.mat'); end % Wavelet Transformation & Image conversion cfs = ecg_to_Image(input); image = ind2rgb(im2uint8(rescale(cfs)),single(jetdata)); image = im2uint8(imresize(image,[227,227])); % figure if isempty(coder.target) imshow(image); end % Prediction [YPred] = extractdata(predict(net,dlarray(single(image),"SSC"))); %% ECG to image conversion function cfs = ecg_to_Image(input) %Wavelet Transformation persistent filterBank siglen = length(input); if isempty(filterBank) filterBank = cwtfilterbank('SignalLength',siglen,'VoicesPerOctave',6); end %CWT conversion cfs = abs(filterBank.wt(input)); end %% Colourmap function J = colourmap(m,class) n = ceil(m/4); u = [(1:1:n)/n ones(1,n-1) (n:-1:1)/n]'; g = ceil(n/2) - (mod(m,4)==1) + (1:length(u))'; r = g + n; b = g - n; r1 = r(r<=128); g1 = g(g<=128); b1 = b(b >0); J = zeros(m,3); J(r1,1) = u(1:length(r1)); J(g1,2) = u(1:length(g1)); J(b1,3) = u(end-length(b1)+1:end); feval = str2func(class); J = feval(J); end end
Create Code Generation Configuration Object
Create a code generation configuration object for generation of an executable program. Specify generation of C++ code.
cfg = coder.config('exe'); cfg.TargetLang = 'C++';
Set Up Configuration Object for Deep Learning Code Generation
Create a coder.ARMNEONConfig
object. Specify the same version of the ARM Compute library as the one on the Raspberry Pi. Specify the architecture of the Raspberry Pi.
dlcfg = coder.DeepLearningConfig('arm-compute'); dlcfg.ArmComputeVersion = '19.05'; dlcfg.ArmArchitecture = 'armv7';
Attach Deep Learning Configuration Object to Code Generation Configuration Object
Set the DeepLearningConfig
property of the code generation configuration object to the deep learning configuration object. Make the MATLAB Source Comments visible in the configuration object at the time of code generation.
cfg.DeepLearningConfig = dlcfg; cfg.MATLABSourceComments = 1;
Create a Connection to the Raspberry Pi
Use the MATLAB Support Package for Raspberry Pi Support Package function, raspi
, to create a connection to the Raspberry Pi. In the following code, replace:
raspiname
with the name or IP address of your Raspberry Piusername
with your user namepassword
with your password
if (~exist("r","var")) r = raspi('raspiname','username','password'); end
Configure Code Generation Hardware Parameters for Raspberry Pi
Create a coder.Hardware
object for Raspberry Pi and attach it to the code generation configuration object.
hw = coder.hardware('Raspberry Pi');
cfg.Hardware = hw;
Specify the build folder on the Raspberry Pi.
buildDir = '~/remdirECG';
cfg.Hardware.BuildDir = buildDir;
Provide C++ Main File for Code Execution
The C++ main file reads the input ECG data, calls the processECG
function to perform preprocessing and deep learning using CNN on the ECG data, and displays the classification probability.
Specify the main file in the code generation configuration object. To learn more about generating and customizing main_ecg_raspi.cpp
, refer to Generating Standalone C/C++ Executables from MATLAB Code (MATLAB Coder).
cfg.CustomSource = 'main_ecg_raspi.cpp';
Generate Source C++ Code Using codegen
Use the codegen
function to generate the C++ code. When codegen
is used with the MATLAB Support Package for Raspberry Pi Hardware, the executable is built on the Raspberry Pi board.
Make sure to set the environment variables ARM_COMPUTELIB
and LD_LIBRARY_PATH
on the Raspberry Pi. See Prerequisites for Deep Learning with MATLAB Coder (MATLAB Coder).
codegen -config cfg processECG -args {ones(1,65536,'single')} -d arm_compute
Deploying code. This may take a few minutes. Code generation successful.
Fetch Generated Executable Directory
To test the generated code on the Raspberry Pi, copy the input ECG signal to the generated code directory. You can find this directory manually or by using the raspi.utils.getRemoteBuildDirectory
API. This function lists the directories of the binary files that are generated by using codegen
.
applicationDirPaths = raspi.utils.getRemoteBuildDirectory('applicationName','processECG')
applicationDirPaths = 1×1 cell array
{1×1 struct}
The complete path to the remote build directory is derived from the present working directory. If you do not know which applicationDirPaths
entry contains the generated code, use the helper function helperFindTargetDir
. Otherwise, specify the proper directory.
directoryUnknown = true; if directoryUnknown targetDirPath = helperFindTargetDir(applicationDirPaths); else targetDirPath = applicationDirPaths{1}.directory; end
Copy Input File to Raspberry Pi
The text file input_ecg_raspi.csv
contains the ECG samples of a representative ARR signal. To copy the file required to run the executable program, use putFile
, which is available with the MATLAB Support Package for Raspberry Pi Hardware.
r.putFile('input_ecg_raspi.csv', targetDirPath);
For a pictorial representation, the first 1000 samples can be plotted by using these steps.
input = dlmread('input_ecg_raspi.csv'); plot(input(1:1000)) title('ARR Signal')
Run Executable on Raspberry Pi
Run the executable program on the Raspberry Pi from MATLAB and direct the output back to MATLAB. The input file name is passed as the command line argument for the executable.
exeName = 'processECG.elf'; % executable name fileName = 'input_ecg_raspi.csv'; % Input ECG file that is pushed to target command = ['cd ' targetDirPath ';./' exeName ' ' fileName]; output = system(r,command)
output = 'Predicted Values on the Target Hardware ARR CHF NSR 0.806078 0.193609 0.000313103 '
References
Baim, D. S., W. S. Colucci, E. S. Monrad, H. S. Smith, R. F. Wright, A. Lanoue, D. F. Gauthier, B. J. Ransil, W. Grossman, and E. Braunwald. "Survival of patients with severe congestive heart failure treated with oral milrinone." Journal of the American College of Cardiology. Vol. 7, Number 3, 1986, pp. 661–670.
Goldberger A. L., L. A. N. Amaral, L. Glass, J. M. Hausdorff, P. Ch. Ivanov, R. G. Mark, J. E. Mietus, G. B. Moody, C.-K. Peng, and H. E. Stanley. "PhysioBank, PhysioToolkit,and PhysioNet: Components of a New Research Resource for Complex Physiologic Signals." Circulation. Vol. 101, Number 23: e215–e220. [Circulation Electronic Pages;
http://circ.ahajournals.org/content/101/23/e215.full
]; 2000 (June 13). doi: 10.1161/01.CIR.101.23.e215.Moody, G. B., and R. G. Mark. "The impact of the MIT-BIH Arrhythmia Database." IEEE Engineering in Medicine and Biology Magazine. Vol. 20. Number 3, May-June 2001, pp. 45–50. (PMID: 11446209)
Supporting Functions
helperFindTargetDir
function targetDir = helperFindTargetDir(dirPaths) % % This function is only intended to support wavelet deep learning examples. % It may change or be removed in a future release. % find pwd p = pwd; if ispc % replace blank spaces with underscores p = strrep(p,' ','_'); % split path into component folders pSplit = regexp(p,filesep,'split'); % Since Windows uses colons, remove any colons that occur for k=1:numel(pSplit) pSplit{k} = erase(pSplit{k},':'); end % now build the path using Linux file separation pLinux = ''; for k=1:numel(pSplit)-1 pLinux = [pLinux,pSplit{k},'/']; end pLinux = [pLinux,pSplit{end}]; else pLinux = p; end targetDir = ''; for k=1:numel(dirPaths) d = strfind(dirPaths{k}.directory,pLinux); if ~isempty(d) targetDir = dirPaths{k}.directory; break end end if numel(targetDir) == 0 disp('Target directory not found.'); end end