## ── USER CONFIGURATION ──────────────────────────────────────────────────────
#
# Input files (CQ1 per-cell CSV exports, one per channel):
NUCLEUS_FILE <- "data/nucleus.csv" # DAPI / nuclear channel
VIRUS_FILE <- "data/virus.csv" # Virus / reporter channel
# Column names in NUCLEUS_FILE / VIRUS_FILE.
# These are AUTHORITATIVE: if a name below is present in the file it is used.
# Autodetection by pattern is a fallback for when it is not, and it stops with
# an error if the pattern is ambiguous (HCS exports routinely carry several
# "Area" columns) rather than silently taking the first match.
NUCLEUS_DAPI_COL <- "(nucleus) MeanIntensity CH1"
NUCLEUS_AREA_COL <- "(nucleus) Area"
NUCLEUS_SPHERICITY_COL <- "(nucleus) Sphericity"
VIRUS_INTENSITY_COL <- "(Virus) MeanIntensity CH2"
# REQUIRE_SPHERICITY: Sphericity drives the doublet-discrimination gate. When
# TRUE (recommended) a missing Sphericity column is a hard error. When FALSE
# the doublet step is SKIPPED and clearly reported as skipped \u2014 it is never
# faked by assigning every object a perfect sphericity of 1.0, which would
# make the gate a silent no-op while still printing the QC panel.
REQUIRE_SPHERICITY <- TRUE
# Study labels
VIRUS_NAME <- "Virus"
COMPOUND_NAME <- "Compound"
DOSE_UNIT <- "\u00b5M"
# Cell gating thresholds
AREA_MIN <- 30 # floor to retain pyknotic nuclei
AREA_MAX <- 700 # ceiling to exclude clumps (3+ merged nuclei)
INTENSITY_MIN <- 300 # DAPI intensity floor
# CIRCULARITY_MIN: Sphericity cutoff separating single nuclei from doublets and
# segmentation debris. Real nuclear sphericity runs ~0.7-1.0 for singlets, so
# a cutoff far below that range retains essentially everything and turns this
# step into a no-op. Inspect the "Doublet & Shape Discrimination" panel below
# and put the line in the valley between the two modes.
CIRCULARITY_MIN <- 0.75
# MAHALANOBIS_LEVEL: Confidence level for the cell-cycle / DNA-content gate.
MAHALANOBIS_LEVEL <- 0.95
# GATE_REFERENCE_TREATMENTS: Which wells define the "normal" DNA-content
# distribution the Mahalanobis gate is fitted on. Fitting on ALL wells lets
# cytotoxic high-dose wells shift the ellipse that decides which of their own
# cells to keep \u2014 a feedback loop. Untreated / mock wells are the right
# reference. Set to NULL to fall back to pooling every singlet.
GATE_REFERENCE_TREATMENTS <- c("Mock", "Mock+Ab")
# MIN_GATE_REFERENCE_CELLS: If the reference population has fewer cells than
# this, the gate falls back to all singlets (with a visible warning).
MIN_GATE_REFERENCE_CELLS <- 200
# WELL_NAME_REGEX / WELL_NAME_REPLACEMENT: Normalizes plate well names to the
# "<row><col>" form used by PLATE_MAP (e.g. "b-03" -> "B3"). The row class
# covers A-P and the column 1-24, so 96-, 384- and 1536-well plates all work.
# Any well that fails to normalize is reported, not silently discarded.
WELL_NAME_REGEX <- "^([A-P])-?0*([1-9][0-9]?).*$"
WELL_NAME_REPLACEMENT <- "\\1\\2"
# Well-level QC
# MIN_CELLS_PER_WELL: Wells with fewer recovered nuclei than this are EXCLUDED
# from the dose-response fits (their percent-infection estimate is too noisy
# to weight sensibly). They remain in the per-well table, marked as excluded.
MIN_CELLS_PER_WELL <- 50
# PRECISION_WARN_CELLS: Wells below this are flagged "Low Precision" in the
# table but still contribute to the fits.
PRECISION_WARN_CELLS <- 200
# Dose-response fitting
# ZERO_DOSE_TICK_DIVISOR: The zero-dose control has no place on a log axis; it
# is drawn at min(positive dose) / this divisor.
ZERO_DOSE_TICK_DIVISOR <- 10
# INCLUDE_ZERO_DOSE_IN_FIT: Whether the zero-dose (Virus Only) control enters
# the 4PL regression as if it were a real dose at that fictitious tick.
# FALSE (default) is correct: the plotted tick position is an artifact of the
# log axis, and feeding it to the model pulls the `top` asymptote and shifts
# EC50. The control is still plotted, and still anchors the normalization.
INCLUDE_ZERO_DOSE_IN_FIT <- FALSE
# EC50_CI_DRAWS: Number of parametric draws used for the EC50/CC50 confidence
# interval (see ec50_ci_parametric() below).
EC50_CI_DRAWS <- 2000
# LOG_TICK_DOSE_COUNT: Above this many DISTINCT doses, the x axis switches from
# one break per dose to decade breaks.
LOG_TICK_DOSE_COUNT <- 6
# Reproducibility: single seed for every stochastic step in this template
# (the balanced GMM training sample and the parametric CI draws).
RANDOM_SEED <- 42
# Output
OUTPUT_DIR <- "Plots"
PLOT_DPI <- 300
# Data Exclusions
DOSES_TO_EXCLUDE <- integer(0) # e.g., c(0.75)
WELLS_TO_EXCLUDE <- character(0) # e.g., c("C3", "E4")
# ── Plate map ─────────────────────────────────────────────────────────────────
# Edit this tribble to match your actual well layout.
# Treatment options:
# "Mock" — uninfected, no drug (absolute negative control)
# "Mock+Ab" — uninfected + neutralizing Ab (gate reference)
# "Virus Only" — infected, no drug (positive control for infection)
# "Toxicity Control" — drug only, no virus (for CC50)
# "Virus + Compound" — infected + drug (experimental)
#
PLATE_MAP <- tibble::tribble(
~WellName, ~Treatment, ~Concentration_uM,
"C3", "Mock", NA,
"C4", "Mock", NA,
"C5", "Mock", NA,
"C6", "Mock", NA,
"C11", "Mock+Ab", NA,
"C7", "Virus Only", 0,
"C8", "Virus Only", 0,
"D1", "Toxicity Control", 0.01,
"D2", "Toxicity Control", 0.1,
"D3", "Toxicity Control", 0.5,
"D4", "Toxicity Control", 1,
"D5", "Toxicity Control", 2,
"D6", "Toxicity Control", 4,
"D7", "Toxicity Control", 8,
"D8", "Toxicity Control", 12,
"D9", "Toxicity Control", 16,
"D10", "Toxicity Control", 20,
"D11", "Toxicity Control", 30,
"D12", "Toxicity Control", 50,
"E1", "Virus + Compound", 0.01,
"E2", "Virus + Compound", 0.1,
"E3", "Virus + Compound", 0.5,
"E4", "Virus + Compound", 1,
"E5", "Virus + Compound", 2,
"E6", "Virus + Compound", 4,
"E7", "Virus + Compound", 8,
"E8", "Virus + Compound", 12,
"E9", "Virus + Compound", 16,
"E10", "Virus + Compound", 20,
"E11", "Virus + Compound", 30,
"E12", "Virus + Compound", 50,
"F1", "Virus + Compound", 0.01,
"F2", "Virus + Compound", 0.1,
"F3", "Virus + Compound", 0.5,
"F4", "Virus + Compound", 1,
"F5", "Virus + Compound", 2,
"F6", "Virus + Compound", 4,
"F7", "Virus + Compound", 8,
"F8", "Virus + Compound", 12,
"F9", "Virus + Compound", 16,
"F10", "Virus + Compound", 20,
"F11", "Virus + Compound", 30,
"F12", "Virus + Compound", 50
)
## ────────────────────────────────────────────────────────────────────────────