--- title: "From slide measurements to a traceable report" output: rmarkdown::html_vignette: toc: true css: style.css number_sections: false vignette: > %\VignetteIndexEntry{From slide measurements to a traceable report} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} resource_files: - figures/logo.svg - figures/logo-dark.svg --- ```{r setup, include=FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") library(bloodspottR) ``` ## Scope and measurement meaning This workflow starts with physical-slide measurements. It does not read native whole slides, train a DNN or validate a candidate as one erythrocyte. The reusable package preserves distinctions needed for those later stages: measured red area, spot candidates, unresolved bulk events, explicit support and provenance. Software validation checks contracts and arithmetic, not biological accuracy. The examples use deterministic **synthetic** data. One record is deliberately missing signal measurements and another has zero measured tissue support. ```{r example} results <- bs_example() bs_validate(results) results$slides[, c("physical_slide_id", "organ", "tissue_area_mm2", "red_area_mm2", "candidate_spots")] ``` Canonical tables identify physical slides with nonempty character strings, areas in square millimetres and nonnegative whole-valued counts. The operational count is candidate spots plus unresolved bulk events. Counts use numeric storage and must remain exactly representable within the documented double-precision bound. Existing QC/organ labels are metadata, never inferred from signal strength. ## Pool numerators and support A group's red-positive fraction is `100 * sum(red_area_mm2) / sum(tissue_area_mm2)`. Its candidate density uses the same tissue denominator. Pooled rates are not means of slide rates: a small and a large slide contribute different support. ```{r pooled} by_organ <- bs_summarize(results, strata = "organ") by_organ[, c("organ", "n_slides", "tissue_area_mm2", "red_area_mm2", "red_percent", "spots_per_mm2")] is.na(by_organ$red_percent[by_organ$organ == "Spleen"]) ``` The spleen summary is missing because one contributing measurement is missing. It would be misleading to silently sum only its known values while retaining the full tissue denominator. A true zero numerator on positive support is zero; zero or unknown support gives a missing rate. Excluding incomplete or compromised slides requires a declared analysis decision and a retained membership record. Group jointly by tissue and the supplied quality strata: ```{r strata} results$groups <- bs_summarize(results, strata = c("organ", "stratum")) results$groups[, c("organ", "stratum", "n_slides", "red_percent")] ``` Only observed combinations appear. `slide_ids` retains membership. A `qc_revision` can replace metadata when it uniquely covers every measured slide; it cannot replace numerical measurements. Historical study-specific thresholds and exclusion decisions are not imposed by this package. ## Import a preserved measurement table The legacy importer accepts a canonical CSV or a saved `analysis.json` with a `rows` table. The `p079` profile also maps `tissue_mm2` and `red_mm2`; it does not assign controls or invent missing calibration. Source file identity is hashed. ```{r import} source_file <- tempfile(fileext = ".csv") utils::write.csv(results$slides, source_file, row.names = FALSE, na = "") imported <- bs_import_legacy(source_file, profile = "canonical") identical(imported$slides$physical_slide_id, results$slides$physical_slide_id) bs_status(imported) unlink(source_file) ``` The source hash is an integrity receipt, not proof of authentic acquisition or independent biological validation. Canonical result JSON exported by the package contains a schema-versioned `slides` table for portable exchange. The importer accepts that format as well as the historical `rows` format. ## Compare runs on declared common support Equal slide IDs alone do not prove equal measurement support. Here both runs explicitly reference the same synthetic support identity, so paired changes can be computed. Without matching support, `support = "common"` fails; the union view preserves unmatched records with unverifiable differences missing. ```{r compare} first <- results second <- results first$slides$support_id <- paste0("synthetic-support-", first$slides$physical_slide_id) second$slides$support_id <- first$slides$support_id second$slides$red_area_mm2[2] <- 0.25 comparison <- bs_compare(list(baseline = first, comparison = second)) comparison[, c("physical_slide_id", "support_verified", "red_area_mm2_delta")] results$comparison <- comparison ``` ## Preserve a report and tables Each delivery uses a new destination directory. Existing deliveries are never overwritten; the parent directory must exist. HTML text is escaped, exports keep missing values, and a SHA-256 manifest records generated files. ```{r report} delivery <- tempfile("bloodspottr-example-report-") report <- bs_report(results, delivery, author = "RH", background = "Synthetic demonstration; no biological inference.") list.files(delivery) file.exists(c(report, file.path(delivery, "SHA256SUMS"))) ``` `bs_export_results(results, out)` writes tables without HTML. Set `xlsx = TRUE` for optional **openxlsx2** workbook output. The initial renderer does not embed raw image masks, native detail crops, training weights or individual events; the study-specific TeX/PDF supplement is a separate artifact. ```{r optional-xlsx} if (requireNamespace("openxlsx2", quietly = TRUE)) { workbook_delivery <- tempfile("bloodspottr-example-xlsx-") bs_export_results(results, workbook_delivery, xlsx = TRUE) file.exists(file.path(workbook_delivery, "results.xlsx")) unlink(workbook_delivery, recursive = TRUE) } ``` ## Explore with Shiny ```{r shiny, eval=FALSE} bs_app(results) # Local interactive session app <- bs_app(launch = FALSE) # Empty application object; explicit demo button ``` The application filters by tissue, recorded QC stratum and literal slide ID. Pooled cards and plots follow the selected rows; the source object is unchanged. It exposes exact measurements, supplied comparisons, provenance and filtered CSV exports. Its explicit demonstration label prevents synthetic values from being mistaken for a loaded study. Browser uploads accept CSV/JSON, not executable serialized objects. It does not launch a GPU backend or claim native annotation editing or model training controls. ```{r cleanup, include=FALSE} unlink(delivery, recursive = TRUE) ```