Author: R. Heller and M. Mannes

# Reading and analysing .Q-View files with qviewparsR

#### R. Heller and M. Mannes

# qviewparsR

Pure-R parser for `.Q-View` multiplex ELISA project files.

A `.Q-View` file is a single-file binary container that bundles a plain-text manifest, an embedded H2 SQL database with project metadata / analyte panel / sample assignments / replicate pixel intensities (and – when generated – a fully rendered CSV report stored as a CLOB), and one or more LOB segments holding the raw chemiluminescent plate images.

`qviewparsR` extracts everything except the raw images and returns it as tidy tibbles. There is no Java runtime, no H2 database driver, no compiled code – all logic is pure R.

## Installation

`qviewparsR` is pure R – no compiled code, no Java runtime, no H2 database driver, and no system libraries – so it installs identically on Windows, macOS, and Linux. The only hard prerequisite is R >= 4.1.0.

From CRAN (once released):

    install.packages("qviewparsR")

Development version from GitHub:

    # install.packages("pak")
    pak::pak("CTTIR/qviewparsR")

Platform notes: on Windows no Rtools is required (the package compiles nothing and its CRAN dependencies arrive as binaries); on macOS nothing beyond R is needed; on Linux the package itself is pure R, but a few dependencies (`dplyr`, `readr`, `tidyr`, `openxlsx2`) contain C++ and build from source unless you use a binary repository (for example the Posit Public Package Manager or r2u), which avoids needing a compiler.

The package depends on a small tidyverse-aligned core (`cli`, `dplyr`, `lifecycle`, `openxlsx2`, `readr`, `rlang`, `tibble`, `tidyr`). Plotting requires `ggplot2`; the publication overview adds `patchwork`; the Shiny front-end adds `shiny`, `bslib`, `DT`, `withr`.

## A complete walk-through

    library(qviewparsR)

    qv <- read_qview("path/to/plate.Q-View")
    qv                          # one-screen summary

    qv$analytes                 # spot_number, analyte, unit, lod, lloq, uloq
    qv$well_groups              # one row per sample / calibrator / control
    qv$pixel_intensities        # long-format replicate readings
    qv$summary_statistics       # per-group mean / std-dev / CV rows
    qv$plate_layout             # one row per plate well

    summary(qv)                 # mean / SD / CV per well type x analyte

`read_qview()` always returns a list of class `qview` with eleven slots described in `?read_qview`. Empty slots are zero-row tibbles rather than `NULL`, so downstream code can rely on shape stability.

## Reading flat report exports

Sometimes only the flat report Q-View writes next to the binary container is kept – the `..._auto_report` or `..._auto_all-parameters_report` file, as `.csv` or `.xlsx`. When the original `.Q-View` project is unavailable, `read_qview_report()` parses that export and returns the same `qview` object `read_qview()` builds:

    rep <- read_qview_report("path/to/plate_auto_report.csv")
    is_qview(rep)               # TRUE -- same class as read_qview()
    rep$concentrations          # long-format, with an out-of-range `flag`

It differs from `read_qview()` in two deliberate ways: it captures the plain `"Reduced Concentration"` point estimate (one row per sample, with `statistic == "reduced"`), and it preserves out-of-range cells instead of dropping them – a `"< 52.50"` cell becomes `concentration = 52.50` with `flag = "<"` (`">"` for the upper bound, `"incalculable"` for `Incalculable`), so limit-of-quantification information survives import.

## The naming convention

The producing software rewrites identifiers from the original well-assignment template before storing them. The mapping is systematic and reversible:

Template value | Stored as
--- | ---
`Cal 1` … `Cal N` | `ICal 1` … `ICal N`
`Low` | `GLow`
`High` | `HHigh`
`FD24277364`, `1211498458`, … | `NFD24277364`, `N1211498458`, …

`strip_qview_prefix()` reverses the rewrite. Pass `strip_prefix = TRUE` to `read_qview()` to apply it across every sample-id column at once:

    qv <- read_qview("path/to/plate.Q-View", strip_prefix = TRUE)
    unique(qv$well_groups$sample_id)

    strip_qview_prefix(c("ICal 1", "GLow", "HHigh", "NFD24277364"))
    #> [1] "Cal 1"      "Low"        "High"       "FD24277364"

## Coercion and tidy-data idioms

`as_tibble()` returns the long-format `pixel_intensities` table – the primary tabular payload – so a parsed object drops straight into a dplyr / ggplot2 pipeline:

    library(dplyr)

    qv |>
      as_tibble() |>
      filter(replicate == 1L) |>
      group_by(analyte, unit) |>
      summarise(median_pi = median(pixel_intensity, na.rm = TRUE),
                .groups = "drop")

## Visualisation

The `plot.qview()` method offers three quick-look views, all coloured on the same viridis ramp the Shiny app uses:

    plot(qv, type = "plate_map")          # 96-well plate, fill = well type
    plot(qv, type = "intensity_heatmap")  # facet per analyte, fill = PI
    plot(qv, type = "replicate_scatter")  # rep 1 vs rep 2 per analyte

Each call returns a `ggplot` object, so themes, scales, and labels can be added on top:

    library(ggplot2)
    plot(qv, type = "plate_map") +
      theme_bw(base_size = 12) +
      labs(title = NULL, subtitle = "QC overview")

## Exporting

Three writers cover the common destinations. All return the parsed object invisibly, so they compose with `|>`:

    qv |>
      write_qview_xlsx("plate.xlsx") |>      # one sheet per parsed table
      write_qview_csv ("plate_csv/") |>      # one CSV per parsed table
      write_qview_rds ("plate.rds")          # full lossless R round-trip

`write_qview_xlsx()` and `write_qview_rds()` accept `overwrite = TRUE` to replace existing destinations.

The legacy aliases `qview_to_xlsx()` and `qview_to_csv_dir()` still work but emit `lifecycle::deprecate_warn()`; switch to the new names.

## Cross-validating against a template CSV

Every well assignment is already embedded in the `.Q-View` file. If you also have the original well-assignment template CSV the producing application imported, `read_qview_template()` parses it into a tibble that aligns with `qv$plate_layout`:

    tmpl <- read_qview_template("path/to/template.csv")

    qv$plate_layout |>
      dplyr::left_join(tmpl, by = "well", suffix = c("_qview", "_template")) |>
      dplyr::filter(sample_id_qview != sample_id_template)

Any rows surviving the filter expose template-vs-Q-View mismatches.

## Interactive front-end

For non-coding collaborators, `qview_app()` launches a Shiny app in the same monochrome aesthetic (light + dark mode) with the hex sticker in the upper-left corner:

    qview_app()             # 512 MB upload cap by default
    qview_app(max_upload_mb = 1024)

The app accepts a `.Q-View` upload (and optionally a template CSV) and exposes:

- a publication-ready 2x2 Overview figure (plate layout / pixel intensity distribution / replicate concordance / mean PI by well type) with high-DPI PNG and vector PDF export;
- every parsed table with its own xlsx download;
- one-click xlsx / rds / csv-zip of the whole project;
- a built-in dark/light toggle and per-card max/restore.

## Error handling

Every exported function validates inputs early and raises a structured `cli::cli_abort()` error pointing at the user’s call rather than at internal helpers, e.g.:

    Error in `read_qview()`:
    ! `path` must be an existing file.
    x "missing.Q-View" does not exist.

    Error in `read_qview()`:
    ! `path` is not a valid `.Q-View` project file.
    x "junk.bin" is missing the expected container header.
    i Expected a numeric container version followed by "Q-View Project".

The `i` bullet always carries an actionable hint when one exists.

## Citation

    citation("qviewparsR")

> Heller R, Mannes M (2026). qviewparsR: Read .Q-View Multiplex ELISA Project Files. R package version 0.1.9. https://github.com/CTTIR/qviewparsR

# Use of LLM tools

Portions of this package were prepared with assistance from large language model tooling for narrowly defined, non-authorial tasks: copyediting, prose smoothing, Markdown/LaTeX formatting, scaffolding of boilerplate files (CI configs, build scripts), code refactoring. The tools used were Chat AI, the LLM service of KISSKI (GWDG), and a self-hosted Mistral Small (24B, Apache-2.0) run locally via Ollama and the `ollamar` R package — local inference only, with no data sent to third parties for the self-hosted model.

All scientific claims, methodological choices, analyses, interpretations, and conclusions are the author’s own. No LLM-generated text was incorporated without review and revision, and every reference was verified against its DOI, arXiv ID, or ISBN.