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 (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! example before you start your Hello Pipeline adventure.

Write the code

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

mkdir hello-pipeline
cd hello-pipeline

Let’s write some code! Populate hello-pipeline.cxx first with the headers we need:

#include "itkPipeline.h"
#include "itkInputImage.h"
#include "itkImage.h"

The itkPipeline.h and itkInputImage.h headers come from the itk-wasm WebAssemblyInterface ITK module.

The itkImage.h header is ITK’s standard n-dimensional image data structure.

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

int main(int argc, char * argv[]) {
  // Create the pipeline for parsing arguments. Provide a description.
  itk::wasm::Pipeline pipeline("hello-pipeline", "A hello world itk::wasm::Pipeline", argc, argv);

  return EXIT_SUCCESS;
}

The itk::wasm::Pipeline extends the most-excellent CLI11 modern C++ command line parser. In addition to all of CLI11’s functionality, itk::wasm::Pipeline’s adds:

  • Support for execution in Wasm modules along with command line execution

  • Support for spatial data structures such as Image’s, Mesh’s, PolyData, and Transform’s

  • Support for multiple dimensions and pixel types

  • Colored help output

Add a standard CLI11 flag to the pipeline:

  itk::wasm::Pipeline pipeline("hello-pipeline", "A hello world itk::wasm::Pipeline", argc, argv);


  bool quiet = false;
  pipeline.add_flag("-q,--quiet", quiet, "Do not print image information");

Add an input image argument to the pipeline:

  pipeline.add_flag("-q,--quiet", quiet, "Do not print image information");


  constexpr unsigned int Dimension = 2;
  using PixelType = unsigned char;
  using ImageType = itk::Image<PixelType, Dimension>;

  // Add a input image argument.
  using InputImageType = itk::wasm::InputImage<ImageType>;
  InputImageType inputImage;
  pipeline.add_option("input-image", inputImage,
    "The input image")->required()->type_name("INPUT_IMAGE");

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:

  pipeline.add_option("InputImage", inputImage,
    "The input image")->required()->type_name("INPUT_IMAGE");


  ITK_WASM_PARSE(pipeline);

If -q or --quiet is set, the quiet variable will be set to true. Missing or invalid arguments will print an error and exit. The -h and --help flags are automatically generated from pipeline arguments to print usage information.

Finally, run our pipeline:

  std::cout << "Hello pipeline world!\n" << std::endl;

  if (!quiet)
  {
    // Obtain the itk::Image * from the itk::wasm::InputImage with `.Get()`.
    std::cout << "Input image: " << *inputImage.Get() << std::endl;
  }

  return EXIT_SUCCESS;

Next, provide a CMake build configuration in CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(hello-pipeline)

# Use C++17 or newer with itk-wasm
set(CMAKE_CXX_STANDARD 20)

# We always want to build against the WebAssemblyInterface module.
set(itk_components
  WebAssemblyInterface
  )
# 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
    WebAssemblyInterface
    ITKIOPNG
    # ITKImageIO # Adds support for all available image IO modules
    )
endif()
find_package(ITK REQUIRED
  COMPONENTS ${itk_components}
  )
include(${ITK_USE_FILE})

add_executable(hello-pipeline hello-pipeline.cxx)
target_link_libraries(hello-pipeline PUBLIC ${ITK_LIBRARIES})

Create WebAssembly binary

Build the WASI binary:

npx itk-wasm -i itkwasm/wasi build

Run WebAssembly binary

Check the generated help output:

npx itk-wasm run hello-pipeline.wasi.wasm -- -- --help

Hello pipeline 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.

> npx itk-wasm run hello-pipeline.wasi.wasm -- -- cthead1.png

Hello pipeline world!

Input image: Image (0x2b910)
  RTTI typeinfo:   itk::Image<unsigned char, 2u>
  Reference Count: 1
  Modified Time: 54
  Debug: Off
  Object Name:
  Observers:
    none
  Source: (none)
  Source output name: (none)
  Release Data: Off
  Data Released: False
  Global Release Data: Off
  PipelineMTime: 22
  UpdateMTime: 53
  RealTimeStamp: 0 seconds
  LargestPossibleRegion:
    Dimension: 2
    Index: [0, 0]
    Size: [256, 256]
  BufferedRegion:
    Dimension: 2
    Index: [0, 0]
    Size: [256, 256]
  RequestedRegion:
    Dimension: 2
    Index: [0, 0]
    Size: [256, 256]
  Spacing: [1, 1]
  Origin: [0, 0]
  Direction:
1 0
0 1

  IndexToPointMatrix:
1 0
0 1

  PointToIndexMatrix:
1 0
0 1

  Inverse Direction:
1 0
0 1

  PixelContainer:
    ImportImageContainer (0x2ba60)
      RTTI typeinfo:   itk::ImportImageContainer<unsigned long, unsigned char>
      Reference Count: 1
      Modified Time: 50
      Debug: Off
      Object Name:
      Observers:
        none
      Pointer: 0x2c070
      Container manages memory: true
      Size: 65536
      Capacity: 65536

And with the --quiet flag:

> npx itk-wasm run hello-pipeline.wasi.wasm -- -- --quiet cthead1.png

Hello pipeline world!

Congratulations! You just executed a C++ pipeline capable of processsing a scientific image in WebAssembly. 🎉