Getting started with qupflowR

qupflowR is being built to connect R to QuPath 0.7.0 through a local Java bridge, with a Shiny app on top of the same R functions and shareable analysis workflows. This vignette describes what the current development version can and cannot do. It is updated whenever a new part becomes usable.

What this version contains

This version is the foundation of the package. It has no functions for QuPath projects, images or objects yet.

Part State in this version
Package skeleton, tests and quality checks first version
Classified error conditions and the error code registry first version
Message catalogue in German and English first version
JSON helpers and JSON Schema validation for the bridge protocol first version, building blocks for the bridge client
Pinned execution of an explicitly trusted external method implemented for Linux; separate from the live bridge
Java bridge (QuPath extension and headless worker) in development, not reachable from R
Connecting to QuPath, reading projects, objects and measurements planned
Shiny app planned
Shareable workflows planned

Loading the package is safe on any machine: library(qupflowR) starts no Java, QuPath, Python or browser process, downloads nothing and does not load optional packages such as shiny, sf or hyperannotatR.

library(qupflowR)

Errors carry stable codes

Every error that qupflowR raises has a code from one registry. The Java bridge reads the same registry, so an error reported by the bridge and the matching R condition agree on the code, the R condition class, the HTTP status and whether a retry is safe.

codes <- qp_error_codes()
codes[codes$code %in% c("HOST_BUSY", "REVISION_CONFLICT"),
      c("code", "http_status", "r_class", "retryable")]
#> # A tibble: 2 × 4
#>   code              http_status r_class           retryable
#>   <chr>                   <int> <chr>             <lgl>    
#> 1 REVISION_CONFLICT         409 qp_conflict_error FALSE    
#> 2 HOST_BUSY                 503 qp_busy_error     TRUE

Code that calls qupflowR can catch a class of problems, or look at the code:

cnd <- tryCatch(
  qp_abort("REVISION_CONFLICT"),
  qp_conflict_error = function(e) e
)
class(cnd)
#> [1] "qp_conflict_error" "qp_error"          "rlang_error"      
#> [4] "error"             "condition"
qp_condition_code(cnd)
#> [1] "REVISION_CONFLICT"

Messages in German or English

User-facing messages come from a catalogue. German is the default; the option qupflowR.language selects English.

qp_msg("error.host_busy", .lang = "de")
#> [1] "Die Bridge ist ausgelastet; die Anfrage kann später wiederholt werden."
qp_msg("error.host_busy", .lang = "en")
#> [1] "The bridge is busy; the request can be retried later."

Optional packages

The Shiny app, geometry conversion with sf and the hyperannotatR interoperability will need packages that are listed in Suggests. They are checked only where a feature uses them. A missing package then produces a qp_capability_error with the code CAPABILITY_UNAVAILABLE that names the packages to install.

JSON values and schema validation

Protocol IDs are character values. Mark a scalar explicitly; unmarked vectors remain arrays, even when they contain only one element.

payload <- list(
  image_id = qp_scalar("synthetic-image"),
  object_ids = c("1", "2"),
  metadata = qp_empty_object()
)
encoded <- qp_json_encode(payload)
encoded
#> {"image_id":"synthetic-image","object_ids":["1","2"],"metadata":{}}
qp_json_decode(encoded)$object_ids
#> [1] "1" "2"
qp_value_state(c(1.5, NA_real_, NaN, Inf, -Inf))
#> [1] "finite"  "missing" "nan"     "pos_inf" "neg_inf"

The schema validator checks protocol structure, not the existence of a running bridge or the correctness of image measurements. An incomplete health document therefore produces a report with errors:

report <- qp_schema_validate(qp_as_json("{}"), "health")
report$valid
#> [1] FALSE
head(report$errors)
#> # A tibble: 6 × 4
#>   path  keyword  message                                            schema_path 
#>   <chr> <chr>    <chr>                                              <chr>       
#> 1 ""    oneOf    Genau eine Alternative muss passen; passend: 0, 1. health.sche…
#> 2 ""    required Die Pflichteigenschaft `status` fehlt.             health.sche…
#> 3 ""    required Die Pflichteigenschaft `host_type` fehlt.          health.sche…
#> 4 ""    required Die Pflichteigenschaft `qupath_version` fehlt.     health.sche…
#> 5 ""    required Die Pflichteigenschaft `bridge_version` fehlt.     health.sche…
#> 6 ""    required Die Pflichteigenschaft `protocol_version` fehlt.   health.sche…

Requirements

  • R (>= 4.5.0), as declared in DESCRIPTION. Local checks and the hosted R/OS matrix provide separate evidence; neither qualifies the native bridge.
  • The bridge will need QuPath 0.7.0 and a Java 25 runtime. Neither is needed for the functions shown here.

Nothing has been released yet. Planned functions such as connecting to a running QuPath or opening a project do not exist in this version, so this vignette does not show them.

Reproduce an existing external method

qp_run_external() is a separate compatibility boundary for an explicitly trusted, hash-pinned executable and argument vector. It leaves analytical scripts and runtime choices unchanged. Declare all script, runtime, model and input pins; use a new workspace and explicit relative output files. The {{workspace}} argument token expands without a shell. A bounded private process environment prevents accidental use of the user’s desktop/preferences.

The method itself must validate its analytical outputs. The returned execution_complete status records process success, unchanged declared inputs and nonempty output artifacts; it does not imply scientific equivalence or a successful project save. Keep study configuration and data outside the public package. This additive API is not the future queued workflow engine and does not qualify the developing live bridge for other QuPath versions.

The initial execution profile is Linux-only. Its receipt records the complete explicit child environment, and Java property values retain paths containing spaces or quotes. Other platforms remain unavailable until separately qualified.

The following Linux example creates a synthetic method in a temporary directory, pins the executable and script, and checks the output against a known value. It does not read study files, start QuPath or access the network. digest, processx and fs are optional dependencies required by this API. Install them with install.packages(c("digest", "processx", "fs")) before using it. The example is evaluated on Linux when all three are installed; elsewhere it is shown as code only. Real methods must also pin their data, runtime and model dependencies.

local({
  example_dir <- tempfile("qupflowr-example-")
  dir.create(example_dir)
  on.exit(unlink(example_dir, recursive = TRUE), add = TRUE)
  script <- file.path(example_dir, "synthetic-method.R")
  writeLines(c(
    "out <- commandArgs(trailingOnly = TRUE)[1]",
    "writeLines('42', file.path(out, 'result.txt'))"
  ), script)
  program <- normalizePath(file.path(R.home("bin"), "Rscript"))
  paths <- c(program, script)
  pins <- data.frame(
    path = paths,
    sha256 = vapply(paths, function(path) {
      digest::digest(file = path, algo = "sha256")
    }, character(1), USE.NAMES = FALSE)
  )
  result <- qp_run_external(
    program = program,
    args = c("--vanilla", script, "{{workspace}}"),
    pins = pins,
    outputs = "result.txt",
    workspace = file.path(example_dir, "run"),
    timeout = 30,
    trust = TRUE
  )
  observed <- readLines(file.path(result$workspace, "result.txt"))
  if (!identical(observed, "42")) {
    cli::cli_abort("Unexpected synthetic output.", class = "qp_example_error")
  }
  result$status
})
#> [1] "execution_complete"

An existing workspace, a changed pinned file, a nonzero process exit, or a missing/empty declared output fails the run. Inspect the retained run.json and process logs for real failures. Keep those receipts private because they record paths and the explicit child environment.

Further help

Use help(package = "qupflowR") for the function reference and citation("qupflowR") for citation details. The package is GPL (>= 3). Report reproducible issues at https://github.com/CTTIR/qupflowR/issues.