---
title: "Reading and analysing .Q-View files with qviewparsR"
author: "R. Heller and M. Mannes"
output:
rmarkdown::html_vignette:
css: null
vignette: >
%\VignetteIndexEntry{Reading and analysing .Q-View files with qviewparsR}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
eval = FALSE,
fig.align = "center",
fig.width = 7, fig.height = 4.5,
dpi = 96
)
library(qviewparsR)
```
```{=html}
```
::: qv-hero
::: {.qv-meta}
# 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):
```r
install.packages("qviewparsR")
```
Development version from GitHub:
```r
# 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
```r
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:
```r
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:
```r
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:
```r
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:
```r
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:
```r
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 `|>`:
```r
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`:
```r
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:
```r
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
```r
citation("qviewparsR")
```
> Heller R, Mannes M (2026). *qviewparsR: Read .Q-View Multiplex
> ELISA Project Files*. R package version 0.1.9.
>
# 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](https://kisski.gwdg.de/leistungen/2-02-llm-service/),
the LLM service of KISSKI (GWDG), and a self-hosted **Mistral Small (24B, Apache-2.0)** run locally via
[Ollama](https://ollama.com/) 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.