%% Code Generation for a Video Classification Network
% This example shows how to generate CUDA(R) code for a deep learning network 
% that classifies video and deploy the generated code onto the NVIDIA(R)
% Jetson Xavier board using the GPU Coder(TM) Support Package for NVIDIA GPUs.
% The deep learning network has both convolutional and 
% bidirectional long short-term memory (BiLSTM) layers. 
% The generated application reads the data from a specified
% video file as a sequence of video frames
% and outputs a label that classifies the activity in the video.
% This example generates code for the network trained in the
% _Classify Videos Using Deep Learning_ example from the Deep Learning
% Toolbox (TM). For more information, see
% <docid:nnet_ug#mw_d4e0b754-b99d-4714-953b-b992a5b7a129 Classify Videos Using Deep Learning>.

% Copyright 2020 The MathWorks, Inc.

%% Third-Party Prerequisites
%
% *Target Board Requirements*
%
% * NVIDIA Jetson board. 
% * Ethernet crossover cable to connect the target board and host PC (if 
% the target board cannot be connected to a local network).
% * Supported Jetpack SDK that includes CUDA and cuDNN libraries
% * Environment variables on the target for the compilers and libraries. 
% For information on the supported versions of the compilers and libraries 
% and their setup, see
% <docid:nvidia_ug#mw_3e566086-bbce-4f1a-8f26-367c8b5f5953 Install and Setup Prerequisites>
%  for NVIDIA boards.

%% Verify NVIDIA Support Package Installation on Host
% To generate and deploy code to an NVIDIA(R) Jetson Xavier board, you will
% need the GPU Coder Support Package for NVIDIA GPUs. Use the
% |checkHardwareSupportPackageInstall| function to verify that the host
% system is compatible to run this example. If the function does not throw
% an error, the support package is correctly installed.

checkHardwareSupportPackageInstall();

%% Connect to the NVIDIA Hardware
% The GPU Coder Support Package for NVIDIA GPUs uses an SSH connection over 
% TCP/IP to execute commands while building and running the generated CUDA 
% code on the Jetson platform. You must therefore connect the
% target platform to the same network as the host computer or use an 
% Ethernet crossover cable to connect the board directly to the host
% computer. Refer to the NVIDIA documentation on how to set up and
% configure your board.
%
% To communicate with the NVIDIA hardware, you must create a live hardware
% connection object by using the 
% <docid:nvidia_ref#mw_5a6bd41b-6ed9-47ee-876d-1975bcdcd195 jetson> 
% function. You must know the host name or IP address, username, and 
% password of the target board to create a live hardware connection object.
% For example, when connecting to the target board for the first time, 
% create a live object for Jetson hardware by using the command:
%
%   hwobj = jetson('jetson-name','ubuntu','ubuntu');
%
% The jetson object reuses these settings from the most recent successful 
% connection to the Jetson hardware. This example establishes an SSH 
% connection to the Jetson hardware using the settings stored in memory.
hwobj = jetson;
%%
% *NOTE:* 
%
% In case of a connection failure, a diagnostics error message is 
% reported on the MATLAB command line. If the connection has failed, the 
% most likely cause is incorrect IP address or hostname.

%% Verify GPU Environment
% Use the <docid:gpucoder_ref#mw_0957d820-192f-400a-8045-0bb746a75278 
% coder.checkGpuInstall> function to verify that the compilers and 
% libraries necessary for running this example are set up correctly.
envCfg = coder.gpuEnvConfig('jetson');
envCfg.DeepLibTarget = 'cudnn';
envCfg.DeepCodegen = 1;
envCfg.Quiet = 1;
envCfg.HardwareObject = hwobj;
coder.checkGpuInstall(envCfg);

%% The |net_classify| Entry-Point Function
% The |net_classify| entry-point function hardcodes the name of a video file.
% Note that this hardcoded path must be adjusted to the location of the
% video file on your target hardware.
% The entry-point function then reads the data from the file using a 
% <docid:matlab_ref#busqe2j-1 VideoReader> object. The data is read into MATLAB
% as a sequence of images (video frames). This data is then
% center-cropped, and finally passed as input to a trained network 
% for prediction. Specifically, the function uses the network trained 
% in the _Classify Videos Using Deep Learning_ example. 
% The function loads the network object from the |net.mat| 
% file into a persistent variable and reuses the persistent object for 
% subsequent prediction calls.
type('net_classify.m')

%% About the Network
% The network used to classify video input has a few notable features:
% 
% 1. The network has a sequence input layer to accept images sequences as
% input.
%
% 2. The network uses a sequence folding layer followed by convolutional
% layers to apply the convolutional operations to each video frame
% independently, thereby extracting features from each frame.
%
% 3. The network uses a sequence unfolding layer and a flatten layer to restore the
% sequence structure and reshape the output to vector sequences, in
% anticipation of the BiLSTM layer.
%
% 4. Finally, the network uses the BiLSTM layer followed by output layers
% to classify the vector sequences.
%
% To display an interactive visualization of the network architecture and 
% information about the network layers, use the 
% <docid:nnet_ref#mw_8d52b67d-b6b3-4c62-b573-56fdf4dce6a0 analyzeNetwork>
% function.

%% Run net_classify in MATLAB
% Download the video classification network.
getVideoClassificationNetwork();

%% 
% Loop over the individual frames of |situp.mp4| to view the test video in MATLAB.
videoFileName = 'situp.mp4';
video = readVideo(videoFileName);

numFrames = size(video,4);
figure
for i = 1:numFrames
    frame = video(:,:,:,i);
    imshow(frame/255);
    drawnow
end

%%
% Run |net_classify| and note the output label. Note that if there is a host GPU
% available, it will be automatically used when running |net_classify|.
net_classify()

%% Generate & Deploy CUDA Code on the Target
% To generate a CUDA executable that can be deployed to an NVIDIA target, 
% create a new GPU coder configuration object for generating an executable.
% Set the target deep learning library to 'cudnn'.
clear cfg
cfg = coder.gpuConfig('exe');
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');

%% 
% Use the <docid:coder_ref#mw_c0d3d077-d33a-4dca-b85d-76027b3170b7 coder.hardware> 
% function to create a configuration object for the Jetson platform and 
% assign it to the |Hardware| property of the GPU code configuration 
% object |cfg|. 
cfg.Hardware = coder.hardware('NVIDIA Jetson');

%%
% Set the build directory on the target hardware. Change the example path
% below to the location on your target hardware where you would like 
% the generated code to be placed.
cfg.Hardware.BuildDir = '/home/ubuntu/VideoClassify';

%%
% The custom main file |main.cu|
% is a wrapper that calls the |net_classify| function in the generated library.
cfg.CustomInclude = '.';
cfg.CustomSource = fullfile('main.cu');

%%
% Run the codegen command. This time, code will be generated and then copied over to
% the target board. The executable will then be built on the target board.
codegen -config cfg net_classify

%% Run the Generated Application on the Target
% Copy the test video file |situp.mp4| from the host computer to the target device
% by using the |putFile| command. Ensure that this video file is placed in
% the location hardcoded in the entry-point function |net_classify|. In
% this example, this location happens to be the target hardware build
% directory.
hwobj.putFile(videoFileName, cfg.Hardware.BuildDir);

%%
% Use |runApplication| to launch the application on the target hardware.
% The label will be displayed in the output terminal on the target.
hwobj.runApplication('net_classify');