Skip to content

Commit

Permalink
docs(InputsOutputs): Initial addition
Browse files Browse the repository at this point in the history
  • Loading branch information
thewtex committed Aug 23, 2022
1 parent d8c89b5 commit 321c339
Show file tree
Hide file tree
Showing 16 changed files with 441 additions and 7 deletions.
31 changes: 29 additions & 2 deletions .github/workflows/examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ jobs:
browser: chrome
start: npm start

build-hello-pipeline-example:
name: Hello Pipeline Build
build-test-hello-pipeline-example:
name: Hello Pipeline Build Test
runs-on: ubuntu-20.04

defaults:
Expand Down Expand Up @@ -113,6 +113,33 @@ jobs:
npm run test:quiet
npm run test:help
build-test-inputs-outputs-example:
name: Inputs Outputs Build test
runs-on: ubuntu-20.04

defaults:
run:
working-directory: ./examples/InputsOutputs

steps:
- uses: actions/checkout@v3

- uses: actions/setup-node@v2
with:
node-version: '16'

- name: Install
run: |
npm install
- name: Build
run: |
npm run build
- name: Test
run: |
npm run test
test-umd-example:
name: UMD
runs-on: ubuntu-20.04
Expand Down
Binary file added doc/content/examples/cthead1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 12 additions & 3 deletions doc/content/examples/hello_pipeline.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
title: Hello Pipeline World!
---

## Introduction

This example introduces the `itk::wasm::Pipeline`. An `itk::wasm::Pipeline` transforms elegant standalone C++ command line programs into powerful [WebAssembly](https://webassembly.org/) (WASM) modules with a simple, efficient interface for execution in the browser, other programming languages, and on the command line.

Make sure to complete the [Hello World!](./hello_world.html) example before you start your Hello Pipeline adventure.

## Write the code

First, let's create a new directory to house our project.

```sh
Expand Down Expand Up @@ -49,7 +55,6 @@ Add a standard CLI11 flag to the pipeline:
bool quiet = false;
pipeline.add_flag("-q,--quiet", quiet, "Do not print image information");
}
```

Add an input image argument to the pipeline:
Expand All @@ -68,7 +73,7 @@ Add an input image argument to the pipeline:
pipeline.add_option("InputImage", inputImage, "The input image")->required();
```

The `inputImage` variable is populated from the filesystem if built as a native executable. When running in the browser or in a wrapped language, `inputImage` is read from WebAssembly memory without file IO.
The `inputImage` variable is populated from the filesystem if built as a native executable or a WASI binary run from the command line. When running in the browser or in a wrapped language, `inputImage` is read from WebAssembly memory without file IO.

Parse the command line arguments with the `ITK_WASM_PARSE` macro:

Expand Down Expand Up @@ -129,12 +134,16 @@ add_executable(HelloPipeline HelloPipeline.cxx)
target_link_libraries(HelloPipeline PUBLIC ${ITK_LIBRARIES})
```

## Create WebAssembly binary

[Build the WASI binary](../hello_world.html):

```sh
npx itk-wasm -i itkwasm/wasi build
```

## Run WebAssembly binary

Check the generated help output:

```sh
Expand All @@ -145,7 +154,7 @@ npx itk-wasm run HelloPipeline.wasi.wasm -- -- --help

The two `--`'s are to separate arguments for the WASM module from arguments to the `itk-wasm` CLI and the WebAssembly interpreter.

Try running on an [example image](https://bafybeihibtxtdmwuekb64wnv3ras54lz4ojuqv4gabmigpfdha4dsmcr5y.ipfs.w3s.link/ipfs/bafybeihibtxtdmwuekb64wnv3ras54lz4ojuqv4gabmigpfdha4dsmcr5y/cthead1.png).
Try running on an [example image](https://data.kitware.com/api/v1/file/63041ac8f64de9b9501e5a22/download).

```
> npx itk-wasm run HelloPipeline.wasi.wasm -- -- cthead1.png
Expand Down
9 changes: 8 additions & 1 deletion doc/content/examples/hello_world.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
title: Hello WASM World!
---

This example walks through how to compile a *hello world* executable written in C++ to [WebAssembly](https://webassembly.org/) and how execute it with standalone WebAssembly runtimes, the Node.js JavaScript runtime, and web browser runtimes!
## Introduction

This example walks through how to compile a *hello world* executable written in C++ to [WebAssembly](https://webassembly.org/) and how to execute it with standalone WebAssembly runtimes, the Node.js JavaScript runtime, and web browser runtimes!

Before getting started, make sure [Node.js](https://nodejs.org/en/download/) and [Docker](https://docs.docker.com/install/) are installed. On Linux, make sure you can run [`docker` without `sudo`](https://askubuntu.com/questions/477551/how-can-i-use-docker-without-sudo). On Windows, we recommend [WSL 2 with Docker enabled](https://docs.docker.com/desktop/windows/wsl/).

While we recommend following along step-by-step, the complete examples can also be found in the [`examples/` directory of the project repository](https://github.com/InsightSoftwareConsortium/itk-wasm/tree/master/examples).

## Write the code

First, let's create a new directory to house our project.

Expand Down Expand Up @@ -33,6 +38,8 @@ project(HelloWorld)
add_executable(hello hello.cxx)
```

## Install itk-wasm

We use the `add_executable` command to build executables with itk-wasm. The [Emscripten](https://kripken.github.io/emscripten-site/) and [WASI](https://github.com/WebAssembly/wasi-sdk) toolchains along with itk-wasm build and execution configurations are contained in the itk-wasm [dockcross](https://github.com/dockcross/dockcross) Docker images used by the itk-wasm command line interface (CLI). Note that the same code can also be built and tested with native operating system toolchains. This is useful for development and debugging.

Build the program with the itk-wasm CLI, `itk-wasm`. This is shipped with the `itk-wasm` Node.js package. First install *itk-wasm* with the Node Package Manager, `npm`, the CLI that ships with Node.js.
Expand Down
239 changes: 239 additions & 0 deletions doc/content/examples/inputs_outputs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
title: Pipeline Inputs and Outputs
---

## Introduction

This example dives deeper into `itk::wasm::Pipeline` image inputs and outputs and how they are handled. We will create a pipeline to smooth an image with a median filter, run the WASM from the command line, in Node.js.

Make sure to complete the [Hello Pipeline!](./hello_pipeline.html) example before you start your filtering journey.

## Write the code

First, let's create a new directory to house our project.

```sh
mkdir InputsOutputs
cd InputsOutputs
```

Let's write some code! Populate *InputsOutputs.cxx* with the headers we need:

```c++
#include "itkPipeline.h"
#include "itkInputImage.h"
#include "itkOutputImage.h"

#include "itkImage.h"
#include "itkMedianImageFilter.h"
```

The *itkImage.h* header is [ITK](https://itk.org)'s standard n-dimensional image data structure and the *itkMedianImageFilter.h* also comes from ITK.

The *itkPipeline.h*, *itkInputImage.h*, and *itkOutputImage.h* headers come from the itk-wasm *WebAssemblyInterface* ITK module. These will help process arguments, injest input images, and produce output images, respectively.

Next, create a standard `main` C command line interface function and an `itk::wasm::Pipeline`:

```c++
int main(int argc, char * argv[]) {
// Create the pipeline for parsing arguments. Provide a description.
itk::wasm::Pipeline pipeline("Smooth an image with a median filter", argc, argv);

return EXIT_SUCCESS;
}
```
Add options to the pipeline that define our inputs, outputs, and processing parameters.
```c++
itk::wasm::Pipeline pipeline("Smooth an image with a median filter", argc, argv);
constexpr unsigned int Dimension = 2;
using PixelType = unsigned char;
using ImageType = itk::Image<PixelType, Dimension>;
// Add a flag to specify the radius of the median filter.
unsigned int radius = 1;
pipeline.add_option("-r,--radius", radius, "Kernel radius in pixels");
// Add a input image argument.
using InputImageType = itk::wasm::InputImage<ImageType>;
InputImageType inputImage;
pipeline.add_option("InputImage", inputImage, "The input image")->required();
// Add an output image argument.
using OutputImageType = itk::wasm::OutputImage<ImageType>;
OutputImageType outputImage;
pipeline.add_option("OutputImage", outputImage, "The output image")->required();
```

The `inputImage` variable is populated from the filesystem if built as a native executable or a WASI binary run from the command line. When running in the browser or in a wrapped language, `inputImage` is read from WebAssembly memory without file IO.

When the program completes, `outputImage` is written to the filesystem if built as a native executable or a WASI binary run from the command line. When running in the browser or in a wrapped language, `outputImage` is read from WebAssembly memory without file IO.

Parse the command line arguments with the `ITK_WASM_PARSE` macro:

```c++
pipeline.add_option("OutputImage", outputImage, "The output image")->required();


ITK_WASM_PARSE(pipeline);
```
The `-h` and `--help` flags are automatically generated from pipeline arguments to print usage information.
![InputsOutputs help](./inputs_outputs_help.png)
Finally, process our data:
```c++
using FilterType = itk::MedianImageFilter< ImageType, ImageType >;
auto filter = FilterType::New();
filter->SetInput(inputImage.Get());
filter->SetRadius(radius);
filter->Update();
```

Set the output image before the program completes:

```c++
outputImage.Set(filter->GetOutput());

return EXIT_SUCCESS;
```

Next, provide a [CMake](https://cmake.org/) build configuration at *CMakeLists.txt*:

```cmake
cmake_minimum_required(VERSION 3.16)
project(InputsOutputs)
# Use C++17 or newer with itk-wasm
set(CMAKE_CXX_STANDARD 17)
# We always want to build against the WebAssemblyInterface module.
set(itk_components
WebAssemblyInterface
ITKSmoothing # provides itkMedianImageFilter.h
)
# WASI or native binaries
if (NOT EMSCRIPTEN)
# WebAssemblyInterface supports the .iwi, .iwi.cbor itk-wasm format.
# We can list other ITK IO modules to build against to support other
# formats when building native executable or WASI WebAssembly.
# However, this will bloat the size of the WASI WebAssembly binary, so
# add them judiciously.
set(itk_components
${itk_components}
ITKIOPNG
# ITKImageIO # Adds support for all available image IO modules
)
endif()
find_package(ITK REQUIRED
COMPONENTS ${itk_components}
)
include(${ITK_USE_FILE})
add_executable(InputsOutputs InputsOutputs.cxx)
target_link_libraries(InputsOutputs PUBLIC ${ITK_LIBRARIES})
```

## Create WebAssembly binary

[Build the WASI binary](../hello_world.html):

```sh
npx itk-wasm -b wasi-build -i itkwasm/wasi build
```

Try running on an [example image](https://data.kitware.com/api/v1/file/63041ac8f64de9b9501e5a22/download).

## Run WebAssembly binary

```sh
npx itk-wasm -b wasi-build run InputsOutputs.wasi.wasm -- -- --radius 2 cthead1.png smoothed.png
```

The input image:

![input image](./cthead1.png)

has been smoothed:

![smoothed](./smoothed.png)

## Run in Node.js

To run in the Node.js JavaScript environment, first build with the Emscripten toolchain.

```sh
npx itk-wasm build
```

In our Node.js JavaScript script, we will load the file with dedicated image IO WebAssembly modules. These are provided by the `itk-image-io` package.

```sh
npm install -g itk-image-io
```

Next, let's create a script to call our pipeline, *index.mjs*. Start the script with our imports.

```javascript
import path from 'path'
import { runPipelineNode,
readImageLocalFile,
writeImageLocalFile,
InterfaceTypes } from 'itk-wasm'
```

To switch from filesystem to WebAssembly memory IO, pass the `--memory-io` flag. This flag is supported by all `itk::wasm::Pipeline`'s. Skip the two `node ./index.mjs` arguments from Node invocation.

```javascript
const args = ['--memory-io'].concat(process.argv.slice(2))
```

When using memory IO, interface types, such as images, are specified in the pipeline arguments with integer strings. Inputs and output integer identifiers both start counting from zero.

```javascript
// Assume we have input and output images as the last arguments
const inputFile = args[args.length-2]
const inputImage = await readImageLocalFile(inputFile)
// '0' is the index of the first input corresponding to the `inputs` array below
args[args.length-2] = '0'

const outputFile = args[args.length-1]
// '0' is the index of the first output corresponding to the `desiredOutputs` below
args[args.length-1] = '0'
```

Input images can be read with `readImageLocalFile`. We specify the type and value of the pipeline input interface types. With pipeline outputs, only the type is specified.

```javascript
const inputs = [
{ type: InterfaceTypes.Image, data: inputImage }
]
const desiredOutputs = [
{ type: InterfaceTypes.Image }
]
```

Run the pipeline.

```javascript
// Path to the Emscripten WebAssembly module without extensions
const pipelinePath = path.resolve('web-build', 'InputsOutputs')
const { stdout, stderr, outputs } = await runPipelineNode(pipelinePath, args, desiredOutputs, inputs)
```

And handle the outputs.

```javascript
await writeImageLocalFile(outputs[0].data, outputFile)
```

Invoke the script.

```sh
npx node ./index.mjs --radius 2 ./cthead1.png smoothed.png
```

Congratulations! You just executed a C++ pipeline capable of processsing a scientific image in Node.js. 🎉
Binary file added doc/content/examples/inputs_outputs_help.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/content/examples/smoothed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/tpl/__en__
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ sidebar:
examples: Examples
hello_world: Hello World!
hello_pipeline: Hello Pipeline!
inputs_outputs: Inputs and Outputs
vue: Vue.js

api:
Expand Down
1 change: 1 addition & 0 deletions doc/tpl/__sidebar__
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ examples:
getting_started:
hello_world: hello_world.html
hello_pipeline: hello_pipeline.html
inputs_outputs: inputs_outputs.html
debugging: debugging.html
recipes:
vue: paraview_glance.html
Expand Down
1 change: 0 additions & 1 deletion examples/HelloPipeline/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"name": "itk-wasm-hello-pipeline",
"version": "1.0.0",
"description": "An itk-wasm Hello Pipeline World! example",
"main": "index.mjs",
"type": "module",
"scripts": {
"build": "itk-wasm -b wasi-build -i itkwasm/wasi build",
Expand Down
4 changes: 4 additions & 0 deletions examples/InputsOutputs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package-lock.json
smoothed.png
web-build/
wasi-build/
Loading

0 comments on commit 321c339

Please sign in to comment.