--- title: "Using the RDML7 package" author: "RDML7 authors" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: true toc_depth: 3 vignette: > %\VignetteIndexEntry{Using the RDML7 package} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5 ) ``` # Introduction The **RDML7** package provides tools for importing, exporting, validating, inspecting, modifying, and converting quantitative PCR (qPCR) data represented by the Real-time PCR Data Markup Language (RDML). RDML stores both fluorescence measurements and experimental metadata in a structured format. The format is designed to make qPCR experiments easier to exchange, reproduce, and reanalyse. RDML7 is a major redesign of the original `RDML` R package described in: > Rödiger S, Burdukiewicz M, Spiess A-N, Blagodatskikh K. > *Enabling reproducible real-time quantitative PCR research: the RDML package.* > Bioinformatics 33(24), 4012–4014. > The original package used an R6 object model. RDML7 uses **S7 classes** and a primarily **functional API**. The main workflow is now: ```text file | v readRDML() | v rdmlType | +---- asTable() -------- metadata / curve index | +---- getFData() ------- fluorescence data | +---- setFData() ------- add or replace fluorescence data | +---- summary() ---- summary | +---- validateRDML() --- validation | +---- asDendrogram() --- hierarchy | +---- mergeRDMLs() ----- merge RDML objects | +---- writeRDML() ------ RDML / XML / RDES / other registered formats ``` The old names such as `AsTable()`, `GetFData()`, `SetFData()`, `AsDendrogram()`, `asXML()`, and `MergeRDMLs()` are retained as compatibility wrappers where possible. New code should use the camelCase API. # Main changes from the original RDML package The most important changes are: 1. **S7 object model** instead of R6. 2. **Value semantics**: functions that modify an object return a modified object, which should normally be assigned back. 3. **CamelCase functional API**, for example `asTable()` and `getFData()`. 4. **Centralised file I/O** with `readRDML()` and `writeRDML()`. 5. **Extensible format registry** with automatic format detection. 6. **Direct support for importer modules** through a canonical intermediate representation. 7. **Validation and summary functions** for RDML objects. 8. **RDES import/export** in addition to RDML and vendor/table formats. 9. A revised `asTable()` interface with documented fields and reusable `{field}` name templates. A migration summary is shown below. | Original API | RDML7 API | |---|---| | `RDML$new(filename = file)` | `readRDML(file)` | | `RDML$new()` | `rdmlType()` | | `x$AsTable()` / `AsTable(x)` | `asTable(x)` | | `x$GetFData()` / `GetFData(x)` | `getFData(x)` | | `x$SetFData()` / `SetFData(x)` | `x <- setFData(x, ...)` | | `x$AsDendrogram()` / `AsDendrogram(x)` | `asDendrogram(x)` | | `x$asXML()` | `asXML(x)` or `writeRDML(x, ...)` | | `MergeRDMLs(...)` | `mergeRDMLs(...)` | | file-specific constructor logic | `readRDML()` + format registry | # Loading RDML7 ```{r load} library(RDML7) ``` The package contains example RDML files that can be used throughout this vignette. ```{r example-file} filename <- system.file( "extdata", "lc96_bACTXY.rdml", package = "RDML7" ) filename ``` # Reading files ## `readRDML()` Use `readRDML()` as the main entry point for file import. ```{r read-rdml} lc96 <- readRDML(filename) ``` The default format selection is automatic. If a format is ambiguous, it can be specified explicitly. ```{r explicit-format, eval=FALSE} lc96 <- readRDML( filename, format = "rdml" ) ``` This replaces the old pattern: ```r # Original RDML package # lc96 <- RDML$new(filename = filename) ``` ## Discovering available file formats The set of available formats is no longer hard-coded into the user-facing import function. It is stored in a registry. ```{r formats} formats <- listRDMLFormats() formats ``` `listRDMLFormats()` reports, among other things, the registered format name, extensions, aliases, whether reading/writing/sniffing is available, the priority, API version, capabilities, and whether the handler is built in. To see which reader or writer would be selected for a particular path: ```{r detect-format} detectRDMLFormat( filename, operation = "read" ) ``` RDML7 first uses the extension and, when necessary, registered format sniffers. If several handlers remain equally suitable, use `format =` explicitly. The repository includes readers for RDML/XML and several vendor or table formats. The exact supported set should be queried with `listRDMLFormats()` rather than assumed from a fixed list. # RDML7 object structure RDML7 mirrors the hierarchy of the RDML schema. The principal top-level fields include: - `dateMade` - `dateUpdated` - `id` - `experimenter` - `documentation` - `dye` - `sample` - `target` - `thermalCyclingConditions` - `experiment` The important hierarchy containing fluorescence data is approximately: ```text rdmlType `-- experiment `-- run `-- react `-- data |-- adp amplification data points `-- mdp melting data points ``` ## S7 property access RDML7 schema objects are S7 objects. Use `$` to access their properties. ```{r s7-access} lc96$version lc96$experiment[[1]]$id$id ``` Collections such as experiments, runs, samples, and targets are keyed by their RDML identifiers. This makes it possible to inspect identifiers first and then select an element. ```{r keyed-lists} expname <- names(lc96$experiment)[1] experiment <- lc96$experiment[[expname]] names(experiment$run) ``` For routine data analysis, direct traversal of the full hierarchy is usually unnecessary. `asTable()` and `getFData()` provide a flatter interface. # Summarising and validating RDML data ## `summary()` Use `summary()` for a compact overview of an RDML object. ```{r summary, eval=FALSE} summary(lc96) ``` This is preferable to manually counting nested experiments, runs, reactions, targets, and curve types. ## `validateRDML()` and `isValidRDML()` RDML7 provides validation (as for RDML 1.3 version) functions in addition to S7 property validators. ```{r validate, eval=FALSE} validateRDML(lc96) isValidRDML(lc96) ``` Use validation before exporting data or after programmatically modifying a large RDML object. # Visualising the hierarchy `asDendrogram()` creates a dendrogram representation of the experiment structure. ```{r dendrogram, eval=FALSE} asDendrogram(lc96) ``` This is the RDML7 replacement for the old `x$AsDendrogram()` method. # Creating a table representation with `asTable()` `asTable()` creates one metadata row per RDML `dataType`. This table is the main bridge between the hierarchical RDML object and ordinary tabular R workflows. ```{r as-table} tbl <- asTable(lc96) head(tbl) ``` The standard scalar fields are: - `fdataName` — unique name used to identify a fluorescence series; - `expId` — experiment identifier; - `runId` — run identifier; - `reactId` — reaction identifier; - `position` — plate/tube position; - `sample` — sample identifier; - `target` — target identifier; - `targetDyeId` — dye associated with the target; - `sampleType` — sample type; - `adp` — amplification data are present; - `mdp` — melting data are present. The camelCase names replace the old `fdata.name`, `exp.id`, `run.id`, `react.id`, `sample.type`, and `target.dyeId` columns. ## Selecting standard columns The revised interface uses `columns` to select built-in output columns. ```{r as-table-columns} tbl_small <- asTable( lc96, columns = c( "expId", "runId", "position", "sample", "sampleType", "target" ) ) head(tbl_small) ``` Without `columns`, all built-in columns are returned. ## `namePattern` templates `fdataName` must identify a fluorescence series. RDML7 supports readable `{field}` templates: ```{r as-table-pattern} tbl <- asTable( lc96, namePattern = "{expId}_{runId}_{position}_{sample}_{sampleType}_{target}" ) head(tbl$fdataName) ``` Available built-in template fields are: ```text {expId} {runId} {reactId} {position} {sample} {target} {targetDyeId} {sampleType} {adp} {mdp} ``` Custom columns supplied through `...` are also available to `namePattern`. Including `expId` and `runId` is recommended when an object may contain more than one experiment or run. It substantially reduces accidental duplicate `fdataName` values.To avoid duplication `asTable` automatically adds number indices to duplicated `fdataName` values. But it's generate warning. ## Adding custom columns Custom columns no longer need a separate `addColumns` argument. Supply a named expression directly through `...`. ```{r custom-column} request <- asTable( lc96, columns = c( "expId", "runId", "position", "reactId", "sample", "sampleType", "target" ), sampleTarget = paste( sample, target, sep = ":" )) head(request) ``` The ready-to-use scalar fields available in custom expressions are: ```text expId, runId, reactId, position, sample, target, targetDyeId, sampleType, adp, mdp ``` ### Advanced custom columns For metadata not exposed as a built-in scalar field, `...` also provides the current structured RDML objects: ```text experiment run react data samples targets dateMade dateUpdated id experimenter documentation dye thermalCyclingConditions ``` These objects are intended for advanced extraction. Because they are structured S7/RDML objects, they are not inserted directly into `namePattern`. For example, a scalar property can be extracted from the current `data` object and then used as a template field: ```{r custom-s7-column, eval=FALSE} tbl <- asTable( lc96, cq = data$cq, namePattern = "{position}:{sample}_{target}-{cq}" ) ``` Once `cq` is created as a scalar custom column, `{cq}` becomes available in `namePattern`. This design means users do **not** need to know internal helper functions such as `.rdmlIdChr()`, `.rdmlReactPosition()`, or `.rdmlSampleType()` merely to construct an `fdataName`. ## Example: deriving concentration from a sample name User metadata can also be derived from existing scalar fields. For example, if a concentration is encoded as a separate underscore-delimited component of the sample name: ```{r concentration-column, eval=FALSE} r96 <- readRDML(system.file( "extdata", "cd53_efficiency.r96", package = "RDML7" )) concValues <- c("1", "0.5", "0.1", "0.01") tbl <- asTable( r96, concentration = { parts <- strsplit(sample, "_", fixed = TRUE)[[1]] value <- parts[parts %in% concValues] if (length(value)) { as.numeric(value[[1]]) } else { NA_real_ } } ) head(tbl) ``` This approach avoids falsely extracting a number that is only part of another identifier. ## Legacy `default` and `addColumns` `default` and `addColumns` may be retained for compatibility with older code, but new code should normally use: ```text columns + named expressions in ... ``` This gives `asTable()` a single, predictable column-selection model. # Getting fluorescence data Fluorescence data are stored separately from the flattened metadata table. Use `getFData()` to extract them. You can provide special `request` to access some part of data or access all available without request (executes `asTable()` with default parameters under the hood) - see later. ## Amplification data (`adp`) Default data is `adp`. ```{r get-adp} amp <- getFData(lc96, request) head(amp) ``` For amplification data, the wide table uses `cyc` as its primary coordinate. If cycle temperatures are present, `tmp` may also be available. ## Melting data (`mdp`) ```{r get-mdp} biorad <- readRDML(system.file( "extdata", "BioRad_qPCR_melt.rdml", package = "RDML7" )) melt <- getFData( biorad, dpType = "mdp" ) head(melt) ``` For melting data, the coordinate is `tmp`. ## Long-table output For plotting and grouped statistical analysis, use `longTable = TRUE`. ```{r get-long} ampLong <- getFData(lc96, longTable = TRUE) head(ampLong) ``` The long representation combines fluorescence points with the metadata from the request table. ## Filtering curves before extraction Because `request` is an ordinary table, it can be filtered before calling `getFData()`. ```{r filtering} stdRequest <- request[ request$sampleType == "std", ] stdAmp <- getFData( lc96, request = stdRequest, dpType = "adp", longTable = TRUE ) head(stdAmp) ``` This replaces the original workflow in which `AsTable()` output was passed to `GetFData()`. ## Plotting with ggplot2 ```{r plot-curves, eval=FALSE} library(ggplot2) ggplot( stdAmp, aes( x = cyc, y = fluor, group = fdataName, colour = target ) ) + geom_line() ``` # Adding or replacing fluorescence data RDML7 uses **value semantics**. `setFData()` returns the modified object, so assign it back: ```r x <- setFData(x, ...) ``` Do not rely on the in-place mutation semantics of the old R6 implementation. `setFData()` accepts: - a wide fluorescence table; - a description table; - `fdataType = "adp"` or `"mdp"`; - `conflict = "error"`, `"keep"`, or `"replace"`. The fluorescence table has a first coordinate column (`cyc` or `tmp`) and one column per fluorescence series. The series names must match `description$fdataName`. # Creating an RDML object from tables A minimal RDML object can be created with `rdmlType()` and populated with `setFData()`. ```{r create-from-tables} fdata <- data.frame( cyc = 1:6, c1 = c(10, 11, 13, 18, 35, 80), c2 = c(9, 10, 12, 16, 29, 65) ) description <- data.frame( fdataName = c("c1", "c2"), expId = c("exp1", "exp1"), runId = c("run1", "run1"), reactId = c("1", "2"), sample = c("sample1", "sample2"), sampleType = c("unkn", "unkn"), target = c("gene1", "gene1"), targetDyeId = c("FAM", "FAM"), stringsAsFactors = FALSE ) sim <- rdmlType() sim <- setFData( sim, fdata = fdata, description = description, fdataType = "adp" ) asTable(sim) ``` The required description columns are: ```text fdataName expId runId reactId target ``` Sample and target metadata are used when new schema objects are created. Missing experiments, runs, reactions, samples, targets, and dyes are created as needed. For conflicting existing data: ```{r set-conflicts, eval=FALSE} sim <- setFData( sim, fdata, description, fdataType = "adp", conflict = "replace" ) ``` The conflict policies are: - `"error"` — stop on a conflict; - `"keep"` — keep existing data; - `"replace"` — replace existing data. # Importing vendor and table data File import is now handled by `readRDML()` and the format registry. For example: ```{r vendor-import, eval=FALSE} x <- readRDML("instrument-export-file") ``` If automatic detection is ambiguous: ```{r vendor-import-explicit, eval=FALSE} x <- readRDML( "instrument-export-file", format = "registered-format-name" ) ``` Always use: ```{r show-formats-again} listRDMLFormats() ``` to inspect the format names actually registered in the installed version of RDML7. The source tree includes importers for RDML/XML, RDES, ABI, Roche, Rotor-Gene, DTprime, FQD, CSV, Excel/table data, and other supported input families. The registry is the authoritative runtime list. # RDES import and export RDML7 includes RDES support. The example data installed with the package include amplification and melting RDES TSV files. ```{r rdes-files} rdesAmp <- system.file( "extdata", "RDES_v1_0_example_amplification.tsv", package = "RDML7" ) rdesMelt <- system.file( "extdata", "RDES_v1_0_example_melting.tsv", package = "RDML7" ) c(rdesAmp, rdesMelt) ``` Import uses the same generic entry point: ```{r read-rdes, eval=FALSE} rdes <- readRDML(rdesAmp) ``` Export is likewise routed through `writeRDML()` according to the requested output format. # Merging RDML objects Use `mergeRDMLs()` to combine RDML objects. ```{r merge, eval=FALSE} file1 <- system.file( "extdata", "lc96_bACTXY.rdml", package = "RDML7" ) file2 <- system.file( "extdata", "stepone_std.rdml", package = "RDML7" ) base <- readRDML(file1) incoming <- readRDML(file2) merged <- mergeRDMLs( base, incoming, dataConflict = "error" ) asDendrogram(merged) ``` `dataConflict` controls what happens when equivalent data locations contain different values: - `"incoming"` — use the value from the incoming object; - `"base"` — preserve the value already present in the base object; - `"error"` — stop rather than resolve the conflict silently. For merging independent files, `"error"` is useful while developing a pipeline because unexpected ID collisions become visible immediately. A directory of files can be processed in a list: ```{r merge-directory, eval=FALSE} files <- list.files( system.file("extdata", package = "RDML7"), full.names = TRUE ) files <- files[!file.info(files)$isdir] rdmls <- lapply(files, function(file) readRDML(file)) merged <- mergeRDMLs( rdmls, dataConflict = "error" ) merged <- mergeRDMLs( rdmls ) rm(rdmls) asDendrogram(merged) rm(merged) ``` If experiment identifiers are not unique between input files, rename them before merging. # Serialising and writing RDML ## `asXML()` Use `asXML()` when the XML representation itself is needed: ```{r as-xml, eval=FALSE} xml <- asXML(r96) cat( substr( xml, 1, 500 ) ) ``` ## `writeRDML()` Use `writeRDML()` for normal file output: ```{r write-rdml, eval=FALSE} writeRDML( lc96, "lc96-copy.rdml", overwrite = TRUE ) ``` For RDML archives, the package writes the XML payload as `rdml_data.xml` inside the archive. The writer is selected through the same registry used by `readRDML()`. # Validation before export A useful export workflow is: ```{r export-workflow, eval=FALSE} validation <- validateRDML(lc96) if (isValidRDML(lc96)) { writeRDML( lc96, "validated.rdml", overwrite = TRUE ) } ``` # Extending the file-format registry One of the major additions in RDML7 is a public plugin API for file formats. ## Registering a custom format A format can provide: - file extensions; - a reader; - a writer. The public registration function is: ```{r register-format-form, eval=FALSE} registerRDMLFormat( name, extensions = character(), reader = NULL, writer = NULL, ) ``` For example, a third-party package could register a reader: ```{r register-format-example, eval=TRUE} # simple-qpcr-reader.R ----------------------------------------------------- # # Example third-party reader for RDML7's minimal format registry. readSimpleQPCR <- function(fileName, ...) { lines <- readLines( fileName, warn = FALSE, encoding = "UTF-8" ) section <- function(name) { start <- match( paste0("[", name, "]"), lines ) if (is.na(start)) { stop( "Missing [", name, "] section", call. = FALSE ) } nextSections <- grep( "^\\[[^]]+\\]$", lines ) end <- nextSections[ nextSections > start ] end <- if (length(end)) { end[[1L]] - 1L } else { length(lines) } out <- lines[ seq.int( start + 1L, end ) ] out[nzchar(trimws(out))] } # [meta] --------------------------------------------------------------- metaLines <- section("meta") meta <- strsplit( metaLines, "=", fixed = TRUE ) meta <- stats::setNames( vapply( meta, function(x) { paste( x[-1L], collapse = "=" ) }, character(1) ), vapply( meta, `[[`, character(1), 1L ) ) requiredMeta <- c( "experiment", "run", "target", "dye" ) missingMeta <- setdiff( requiredMeta, names(meta) ) if (length(missingMeta)) { stop( "Missing metadata: ", paste( missingMeta, collapse = ", " ), call. = FALSE ) } # [reactions] ---------------------------------------------------------- reactions <- utils::read.delim( text = paste( section("reactions"), collapse = "\n" ), stringsAsFactors = FALSE, check.names = FALSE ) requiredReactions <- c( "fdataName", "reactId", "sample", "sampleType", "quantity" ) missingReactions <- setdiff( requiredReactions, names(reactions) ) if (length(missingReactions)) { stop( "Missing reaction columns: ", paste( missingReactions, collapse = ", " ), call. = FALSE ) } # [adp] ---------------------------------------------------------------- fdata <- utils::read.delim( text = paste( section("adp"), collapse = "\n" ), stringsAsFactors = FALSE, check.names = FALSE ) if (!identical(names(fdata)[[1L]], "cyc")) { stop( "First [adp] column must be `cyc`", call. = FALSE ) } missingCurves <- setdiff( reactions$fdataName, names(fdata) ) if (length(missingCurves)) { stop( "Missing fluorescence columns: ", paste( missingCurves, collapse = ", " ), call. = FALSE ) } description <- data.frame( fdataName = reactions$fdataName, expId = meta[["experiment"]], runId = meta[["run"]], reactId = as.character( reactions$reactId ), sample = reactions$sample, sampleType = reactions$sampleType, target = meta[["target"]], targetDyeId = meta[["dye"]], quantity = as.numeric( reactions$quantity ), stringsAsFactors = FALSE ) buildRDMLFromFData( fdata = fdata, description = description, fdataType = "adp", publisher = "simpleqpcr" ) } registerRDMLFormat( name = "simpleqpcr", extensions = "sqpcr", reader = readSimpleQPCR ) ``` The new format then participates in normal dispatch: ```{r custom-format-dispatch, eval=TRUE} sqpcr <- system.file( "extdata/sqpcr", "cd53_efficiency.sqpcr", package = "RDML7" ) sqpcr <- readRDML(sqpcr) summary(sqpcr) ``` To remove a third-party format: ```{r unregister-format, eval=TRUE} unregisterRDMLFormat("simpleqpcr") ``` Built-in handlers are protected from accidental removal. # Canonical importer API For custom file formats, see the [*Writing custom RDML importers*](custom-importers.html) vignette. # Loss-aware conversions Different external formats do not necessarily represent every RDML feature. The importer/exporter API can describe known information loss using `newRDMLLossRecord()`. When developing a format plugin, declare known lossy transformations rather than silently discarding unsupported information. This allows higher-level I/O code to decide whether loss should produce a warning, an error, or be explicitly allowed. # `editRDML()` `editRDML()` remains available as an editing interface. ```{r rdml-edit, eval=FALSE} editRDML(lc96) ``` For reproducible analyses, scripted operations with `asTable()`, `getFData()`, `setFData()`, and the S7 object model should remain the primary workflow, while an editor is useful for interactive inspection and manual changes. # Working with external analysis packages RDML7 intentionally keeps fluorescence extraction separate from downstream qPCR analysis. Once curves have been obtained with `getFData()`, they can be passed to other R packages. For example: ```{r downstream, eval=TRUE} curves <- getFData( lc96, dpType = "adp", longTable = FALSE ) # The first column contains cycle coordinates. cycles <- curves[[1]] # Remaining columns contain fluorescence series and can be passed to # modelling, preprocessing, Cq estimation, or visualisation code. ``` This separation allows RDML7 to focus on data interchange, metadata, validation, conversion, and object manipulation while specialised packages perform curve analysis. See details and examples in [*Using RDML with other R packages*](usingRDML_with_other_R_packages.html) vignette. # Recommended workflow For most analyses, the following pattern is sufficient: ```{r workflow, eval=FALSE} library(RDML7) # 1. Import x <- readRDML("experiment.rdml") # 2. Inspect and validate summary(x) validateRDML(x) # 3. Build the curve index / metadata table meta <- asTable( x, namePattern = "{expId}_{runId}_{position}_{sample}_{sampleType}_{target}" ) # 4. Select curves selected <- meta[ meta$sampleType == "std" & meta$adp, ] # 5. Extract fluorescence amp <- getFData( x, request = selected, dpType = "adp", longTable = TRUE ) # 6. Analyse or modify data # ... # 7. Write a new RDML file if needed writeRDML( x, "experiment-processed.rdml", overwrite = TRUE ) ``` # Legacy API compatibility Legacy wrappers are exported to ease migration of existing analysis scripts. Examples include: ```text AsTable -> asTable GetFData -> getFData SetFData -> setFData AsDendrogram -> asDendrogram asXML -> asXML MergeRDMLs -> mergeRDMLs ``` Some old dotted argument names are also translated by compatibility code to their CamelCase equivalents, for example: ```text show.progress -> showProgress conditions.sep -> conditionsSep fdata.type -> fdataType serial.number -> serialNumber name.pattern -> namePattern long.table -> longTable sample.type -> sampleType target.dyeId -> targetDyeId dp.type -> dpType plot.dendrogram -> plotDendrogram data.conflict -> dataConflict ``` Compatibility wrappers are intended to keep old scripts usable. New code and new documentation should use the canonical names. # Function overview The following groups summarise the most important public RDML7 functionality. ## Core object and schema ```text rdmlType() idType() idReferenceType() experimentType() runType() reactType() dataType() sampleType() targetType() dyeType() thermalCyclingConditionsType() ... ``` ## File I/O ```text readRDML() writeRDML() asXML() detectRDMLFormat() listRDMLFormats() ``` ## Metadata and fluorescence ```text asTable() getFData() setFData() buildRDMLFromFData() ``` ## Inspection and validation ```text summary() validateRDML() isValidRDML() asDendrogram() ``` ## Manipulation ```text mergeRDMLs() editRDML() ``` ## Extensible import/export ```text registerRDMLFormat() unregisterRDMLFormat() loadRDMLModule() newRDMLImportSeries() newRDMLImportData() buildRDMLImport() newRDMLLossRecord() ``` # RDML7 public API naming convention rules - Actions start with a verb: `readRDML()`, `validateRDML()`. - Conversion/coercion uses standard R `as...`: `asTable()`, `asDendrogram()`, `asXML()`. - Predicates start with `is...`: `isValidRDML()`. - Helper-object constructors start with `new...`. - RDML schema/S7 type constructors keep the schema noun name: `rdmlType()`, `sampleType()`, `targetType()`, `dataType()`, etc. # Summary RDML7 keeps the main ideas of the original RDML package—structured qPCR metadata, fluorescence extraction, table-based filtering, creation of RDML objects from user data, and interoperability with other R analysis packages—but substantially changes the implementation and public API. The central concepts for new code are: 1. read files with `readRDML()`; 2. treat `rdmlType` and nested schema elements as S7 objects; 3. use `asTable()` as the metadata/curve index; 4. use `{field}` templates for readable and documented `fdataName` generation; 5. add custom `asTable()` columns directly through `...`; 6. use `getFData()` and `setFData()` for fluorescence; 7. remember that modification uses value semantics; 8. validate with `validateRDML()` / `isValidRDML()`; 9. write through `writeRDML()`; 10. discover and extend formats through the format registry. For the current runtime list of supported file handlers, always prefer: ```r listRDMLFormats() ``` over a hard-coded list in analysis code. ## Further reading - `vignette("CreateRDML", package = "RDML")` - `vignette("RDES", package = "RDML")` - `vignette("format-plugins", package = "RDML")` - `vignette("usingRDML_with_other_R_packages", package = "RDML")`