# Holoscan utility scripts

This folder includes the following scripts:

- [`aja_build.sh`](#aja_build)
- [`convert_gxf_entities_to_images.py`](#convert_gxf_entities_to_imagespy)
- [`convert_gxf_entities_to_images.py`](#convert_gxf_entities_to_imagespy)
- [`convert_gxf_entities_to_video.py`](#convert_gxf_entities_to_videopy)
- [`convert_video_to_gxf_entities.py`](#convert_video_to_gxf_entitiespy)
- [`download_ngc_data`](#download_ngc_data)
- [`generate_extension_uuids.py`](#generate_extension_uuidspy)
- [`generate_gxf_manifest.py`](#generate_gxf_manifestpy)
- [`graph_surgeon.py`](#graph_surgeonpy)
- [`gxf_entity_codec.py`](#gxf_entity_codecpy)
- [`video_validation.py`](#video_validationpy)

> Note: these will be included in the SDK installation at `/opt/nvidia/holoscan/bin`

____

## aja_build.sh

Builds the AJA SDK with the proper flags and optionally loads the driver.

## convert_gxf_entities_to_images.py

Takes in the encoded GXF tensor files generated by the `video_stream_recorder` and export raw frames in .png files.

### Prerequisites

```sh
pip install numpy~=1.21 pillow
```

This script depends on `gxf_entity_codec.py` which is located in the same folder.

### Usage

The command below will read the `racerx.gxf_entities` and `racerx.gxf_index` files from the existing racerx dataset under `data/racerx` (which is a 854x480 video with framerate 25fps and 3 channels) and convert them to PNG files.

```sh
python3 scripts/convert_gxf_entities_to_images.py --directory data/racerx --basename racerx
```

Use `--outputdir` to specify the directory where the files will be created.
Use `--outputname` to specify a different output name than the default `tensor` prefix.

____

## convert_gxf_entities_to_video.py

Takes in the encoded GXF tensor files generated by the `video_stream_recorder` (`.gxf_entities` and `.gxf_index`) and emit the raw video feed.

### Prerequisites

```sh
pip install numpy~=1.21
```

This script depends on `gxf_entity_codec.py` which is located in the same folder.

### Usage

The command below will read the `racerx.gxf_entities` and `racerx.gxf_index` files from the existing racerx dataset under `data/racerx` (which is a 854x480 video with framerate 25fps and 3 channels) and use `ffmpeg` to encode the emitted video stream to a video file, `converted_video.mp4`:

```sh
python3 scripts/convert_gxf_entities_to_video.py --directory data/racerx --basename racerx | ffmpeg -f rawvideo -pix_fmt rgb24 -s 854x480 -r 25 -i - -f mp4 -vcodec libx264 -pix_fmt yuv420p -r 25 -y racerx-medium.mp4
```

____

## convert_video_to_gxf_entities.py

Takes in a raw video feed and emits encoded GXF tensor files entities for playback with the `video_stream_replayer` operator.  The tensors will be saved with metadata indicating that the data should be copied to the GPU on read.

### Prerequisites

```sh
pip install numpy~=1.21
```

This script depends on `gxf_entity_codec.py` which is located in the same folder.

### Usage

Example usage converting the output of a tool like `ffmpeg` to encoded GXF tensors:

```sh
ffmpeg -i video_1920x1080.avi -pix_fmt rgb24 -f rawvideo pipe:1 | python3 scripts/convert_video_to_gxf_entities.py --width 1920 --height 1080 --channels 3 --framerate 30
```

Above command will create two files: `tensor.gxf_entities` and `tensor.gxf_index` from the `video_1920x1080.avi` video file.
Use `--directory` to specify the directory where the files will be created.
Use `--basename` to specify a different output name than the default `tensor`

When working with long videos, it can be desired to limit the disc space usage. In this case, `ffmpeg` can be used to convert only a portion of the video.
Use `-ss` option to set the start time.
Use `-t` option to set the duration or the `-to` option to set the end time.

Examples:

```sh
ffmpeg -ss 00:00:05 -i video_1920x1080.avi -t 00:00:05 -pix_fmt rgb24 -f rawvideo pipe:1 | python3 scripts/convert_video_to_gxf_entities.py --width 1920 --height 1080 --channels 3 --framerate 30
```
```sh
ffmpeg -ss 00:00:05 -i video_1920x1080.avi -to 00:00:10 -pix_fmt rgb24 -f rawvideo pipe:1 | python3 scripts/convert_video_to_gxf_entities.py --width 1920 --height 1080 --channels 3 --framerate 30
```

Above commands will parse the video starting at 00:00:05 and ending at 00:00:10.

____

## ctest_time_comparison.py

This script compares time from different tests to make sure they match expected timing comparison.

### Usage

`python3 ./scripts/ctest_time_comparison.py <filename> "TEST1" "LESS" "TEST2"`
____

## download_ngc_data

Download and unzip datasets from NGC. This can optionally run a script to convert video files to GXF tensor files compatible with the `video_stream_replayer` operator.

## Prerequisites

- `wget` or `curl` to download datasets using the format `https://api.ngc.nvidia.com/v2/resources/nvidia/[team]/<name>/versions/<version>/zip`
- [NGC CLI](https://ngc.nvidia.com/setup/installers/cli) to download datasets using the `<org>/[team]/<name>:<version>` format (useful if the above fails or for private registries)

## Usage

The example below will download and unzip the RacerX video from NGC:

```sh
./scripts/download_ngc_data --url https://api.ngc.nvidia.com/v2/resources/nvidia/clara-holoscan/holoscan_racerx_video/versions/20231009/zip
```

Use `--help` for more options such as output dir/name or conversion to GXF tensor files.

____

## generate_extension_uuids.py

Provides a set of UUIDs to be used by `GXF_EXT_FACTORY_SET_INFO` and `GXF_EXT_FACTORY_ADD` to declare a new GXF extension.

``` sh
python3 scripts/generate_extension_uuids.py
```

____

## generate_gxf_manifest.py

Generates a GXF extension registry manifest. Refer to [Graph Composer Registry documentation](https://docs.nvidia.com/metropolis/deepstream/dev-guide/graphtools-docs/docs/text/GraphComposer_Registry.html) for registry details.

Holoscan SDK provides the CMake function `generate_gxf_registry_manifest` to call this script each time a target is updated.
You can also call this script directly for manual testing with your own Holoscan GXF extensions.

Note that GXF manifests are not portable and typically include filepaths relative to the
build environment. All extensions must be available in the local environment to use this script.

The script accepts a number of optional arguments, including manifest content, extension search paths,
a custom Python `.pickle` database path, and more. See `generate_gxf_manifest.py -h` for help.

The GXF registry CLI need not be present in the environment to generate an extension manifest.

### Usage

```sh
python3 scripts/generate_gxf_manifest.py \
    --output <my_manifest.yaml>
    --name <MyHoloscanExtension> \
    --extension-library <path/to/my_extension.so> \
    --uuid <uuid> \
    --version <version> \
    --extension-dependencies [libgxf_std.so,libgxf_ucx.so,...]
    ...
```

____

## graph_surgeon.py

When converting a model from PyTorch to ONNX, it is likely that the input of the model is in the form NCHW (batch, channels, height, width), and needs to be converted to NHWC (batch, height, width, channels). This script performs the conversion and generates a modified model.
Note that this script modifies the names of the output by appending `_old` to the input and output.

### Usage

```bash
python3 scripts/graph_surgeon.py input_model.onnx output_model.onnx
```

____

## gxf_entity_codec.py

Utility classes used by `convert_gxf_entities_to_images.py`, `convert_gxf_entities_to_video.py` and `convert_video_to_gxf_entities.py`.

____

## video_validation.py

This script converts GXF tensor files to frame images to compare each frame with a set of baselines. The difference between them is computed using SSD (Sum of Square difference) for each pixel and an average is reported for a frame.

### Prerequisites

```sh
pip install numpy~=1.21 pillow
```

This script depends on `convert_gxf_entity_to_images.py` which is located in the same folder.

### Usage

See `python3 ./scripts/video_validation.py --help`
