Author: Chris Reudenbach
Date: 2025-12-22

# OTB Wrapper in link2GI

#### Chris Reudenbach

#### 2025-12-22

    knitr::opts_chunk$set(
    collapse = TRUE,
    comment  = "#>",
    eval     = FALSE
    )

The Orfeo ToolBox (OTB) provides a collection of command-line applications for remote sensing and image processing. `link2GI` wraps these applications in R with a focus on reproducibility, transparency, and robustness across OTB versions.

`link2GI` supports two layers:

- a legacy wrapper layer (kept for backward compatibility)
- a new Self-describing API (recommended) that derives parameter metadata from OTB’s own `-help` output

# Two APIs: legacy vs. Self-describing

## Legacy API (`parseOTBFunction()` / `runOTB()`)

The legacy layer builds a modifiable R list by parsing the output of:

    otbcli <algorithm> -help

Characteristics:

- permissive and convenient
- minimal upfront constraints
- useful for older scripts

Limitations:

- required parameters and defaults are inferred heuristically
- sensitive to OTB help formatting changes
- less suitable for strict, version-stable workflows

## Self-describing API (recommended)

The Self-describing API treats the OTB CLI help output as the single source of truth for parameters and their status (mandatory vs. optional). It supports a strict “write-to-disk” workflow and avoids implicit assumptions.

Core functions:

- `otb_capabilities()`: retrieve CLI help text (source of truth)
- `otb_args_spec()`: normalized parameter spec table parsed from `-help`
- `otb_build_cmd()`: create a `runOTB()`-compatible command template (valid keys only)
- `otb_set_out()`: set file-based outputs safely (including `[pixel]` outputs)

# Minimal workflow (Self-describing)

    library(link2GI)

    otb <- link2GI::linkOTB(searchLocation = "~/apps/otb911/")

    cmd <- link2GI::otb_build_cmd(
    "DimensionalityReduction",
    otb,
    include_optional = "defaults",
    require_output   = TRUE
    )

    cmd[["in"]]     <- "in.tif"
    cmd[["method"]] <- "pca"
    cmd[["nbcomp"]] <- "3"

    cmd <- link2GI::otb_set_out(cmd, otb, key = "out", path = "out.tif")
    str(cmd)

# Typical transparent workflow (Self-describing)

This example demonstrates a complete workflow:

1. link an installed OTB
2. read and display the raw `-help` output (source of truth)
3. inspect the parsed parameter table (`spec`)
4. build a command template from valid keys
5. set input and explicit output
6. run via `runOTB()` and verify the output file

NOTE (CRAN/vignette hygiene): this vignette writes outputs into `tempdir()`.

## Example: `DimensionalityReduction` (PCA)

This example avoids guessing parameter names. The only manual step is choosing the correct parameter key for “number of components” by reading the `spec` table printed from your local

    # OTB build (for OTB 9.1.1 this is `nbcomp`).

    library(link2GI)
    library(terra)

    # 0) Link OTB

    otb <- link2GI::linkOTB(searchLocation="~/apps/otb911/")

    # 1) Choose algorithm

    algo <- "DimensionalityReduction"

    # 2) Read the OTB help text (source of truth)

    caps <- link2GI::otb_capabilities(algo = algo, gili = otb, include_param_help = FALSE)
    cat(paste(head(caps$text, 60), collapse = "\n"), "\n")

    # 3) Parsed parameter table

    spec <- link2GI::otb_args_spec(algo, otb)
    print(spec[, c("key", "class", "mandatory", "default")], row.names = FALSE)

    # 4) Build a template containing only valid keys (mandatory + defaults)

    cmd <- link2GI::otb_build_cmd(
    algo,
    otb,
    include_optional = "defaults",
    require_output   = TRUE
    )

    # 5) Identify component-count key by inspecting the spec table (no heuristics)

    pca_related <- spec[grepl("^method\.pca\.|^nbcomp$|outdim|comp", spec$key), ]
    print(pca_related[, c("key", "mandatory", "default", "desc")], row.names = FALSE)

    # 6) Set PCA method + number of components (OTB 9.1.1: nbcomp)

    cmd[["method"]] <- "pca"
    cmd[["nbcomp"]] <- "3"

    # 7) Input + output (explicit on-disk paths)

    infile  <- system.file("ex/elev.tif", package = "terra")
    out_dir <- file.path(tempdir(), "link2gi_otb_vignette")
    dir.create(out_dir, recursive = TRUE, showWarnings = FALSE)

    cmd[["in"]]  <- infile
    cmd[["out"]] <- file.path(out_dir, "pca.tif")

    # Show the exact CLI that would be executed

    cat(link2GI::runOTB(cmd, otb, retCommand = TRUE), "\n")

    # Run (writes to disk)

    res <- link2GI::runOTB(cmd, otb, quiet = FALSE)

    # Verify output exists

    file.exists(cmd[["out"]])

# Legacy workflow (supported but not recommended)

The legacy workflow builds a command list by parsing `-help` output and then executes it. Because the legacy parser may expose parameter names that vary between OTB versions/apps, the safe pattern is:

1. parse the command list
2. inspect `names(cmd)` / `cmd$help` (if present)
3. set inputs/outputs and required parameters explicitly
4. run

    library(link2GI)

    otb <- link2GI::linkOTB()

    algo <- "EdgeExtraction"
    cmd  <- link2GI::parseOTBFunction(algo = algo, gili = otb)

    # inspect keys (legacy API may expose app-specific names)

    names(cmd)

    # then set required parameters according to the returned structure:

    # cmd[["<input_key>"]]  <- "in.tif"

    # cmd[["<output_key>"]] <- "out.tif"

    # cmd[["<other_key>"]]  <- "value"

    # finally execute

    # link2GI::runOTB(cmd, gili = otb, quiet = FALSE)

# What changed (Self-describing)

The new Self-describing API removes fragile heuristics and centralizes metadata on:

- OTB’s own `Parameters:` block emitted by `<Algo> -help`

Consequences:

- `otb_capabilities()` / `otb_args_spec()` become the source of truth
- `otb_build_cmd()` prevents invalid keys (reduces “parameter does not exist” failures)
- `runOTB()` on Linux/macOS uses the OTB launcher plus explicit environment, matching the help/spec stack
- Windows isolation is handled by `runOTB_isolated()` via `otbenv.ps1/.bat`

# Compatibility and deprecations

## Compatibility

- Existing scripts using `parseOTBFunction()` plus `runOTB()` should continue to work (best-effort).
- The Self-describing API is stable as long as OTB provides a parseable `Parameters:` section.

## Deprecations (explicit list)

- `parseOTBFunction()` is legacy (supported, but not recommended for new code).
- `parseOTBAlgorithms()` is legacy (if present in your build; prefer `otb_capabilities()`).

For new projects, prefer:

`otb_capabilities()` + `otb_args_spec()` + `otb_build_cmd()` + `otb_set_out()` then `runOTB()`.