---
title: "Getting started with qupflowR"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Getting started with qupflowR}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
resource_files:
- figures/logo.svg
- figures/logo-dark.svg
---
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>"
)
```
qupflowR is being built to connect R to [QuPath](https://qupath.github.io/)
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.
```{r}
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.
```{r}
codes <- qp_error_codes()
codes[codes$code %in% c("HOST_BUSY", "REVISION_CONFLICT"),
c("code", "http_status", "r_class", "retryable")]
```
Code that calls qupflowR can catch a class of problems, or look at the code:
```{r}
cnd <- tryCatch(
qp_abort("REVISION_CONFLICT"),
qp_conflict_error = function(e) e
)
class(cnd)
qp_condition_code(cnd)
```
## Messages in German or English
User-facing messages come from a catalogue. German is the default; the option
`qupflowR.language` selects English.
```{r}
qp_msg("error.host_busy", .lang = "de")
qp_msg("error.host_busy", .lang = "en")
```
## 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.
```{r json-values}
payload <- list(
image_id = qp_scalar("synthetic-image"),
object_ids = c("1", "2"),
metadata = qp_empty_object()
)
encoded <- qp_json_encode(payload)
encoded
qp_json_decode(encoded)$object_ids
qp_value_state(c(1.5, NA_real_, NaN, Inf, -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:
```{r schema-validation}
report <- qp_schema_validate(qp_as_json("{}"), "health")
report$valid
head(report$errors)
```
## 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` and
`processx` are optional dependencies required by this API. The example is
evaluated on Linux when both are installed; elsewhere it is shown as code only.
Real methods must also pin their data, runtime and model dependencies.
```{r external-method, eval=identical(Sys.info()[["sysname"]], "Linux") && requireNamespace("digest", quietly=TRUE) && requireNamespace("processx", quietly=TRUE)}
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
})
```
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 .