diff --git a/NAMESPACE b/NAMESPACE
index 290fbd65..ceeb45e3 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -14,10 +14,12 @@ export("peaks<-")
export("sampleNames<-")
export(DelayedOperation)
export(GCIMSChromatogram)
+export(GCIMSChromatogramSet)
export(GCIMSDataset)
export(GCIMSDataset_fromList)
export(GCIMSSample)
export(GCIMSSpectrum)
+export(GCIMSSpectrumSet)
export(add_peaklist_rect)
export(align)
export(alignDt)
@@ -64,13 +66,17 @@ export(smooth)
export(updateObject)
exportClasses(DelayedOperation)
exportClasses(GCIMSChromatogram)
+exportClasses(GCIMSChromatogramSet)
exportClasses(GCIMSDataset)
exportClasses(GCIMSSample)
exportClasses(GCIMSSpectrum)
+exportClasses(GCIMSSpectrumSet)
+exportMethods("[[")
exportMethods("baseline<-")
exportMethods("description<-")
exportMethods("intensity<-")
exportMethods("peaks<-")
+exportMethods("sampleNames<-")
exportMethods(align)
exportMethods(baseline)
exportMethods(decimate)
@@ -80,16 +86,21 @@ exportMethods(estimateBaseline)
exportMethods(filterDt)
exportMethods(filterRt)
exportMethods(findPeaks)
+exportMethods(getChromatogram)
exportMethods(getRIC)
+exportMethods(getSpectrum)
exportMethods(getTIS)
exportMethods(integratePeaks)
exportMethods(intensity)
+exportMethods(length)
+exportMethods(pData)
exportMethods(peaks)
exportMethods(plot)
exportMethods(plotRIC)
exportMethods(plotTIS)
exportMethods(prealign)
exportMethods(rtime)
+exportMethods(sampleNames)
exportMethods(smooth)
exportMethods(updateObject)
import(methods)
diff --git a/NEWS.md b/NEWS.md
index be0305db..0783a7a7 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,12 @@
# GCIMS (development version)
+- `getChromatogram()` and `getSpectrum()` are now S4 generics with methods
+ for `GCIMSDataset`, in addition to the existing `GCIMSSample` method.
+ Calling them on a dataset returns a new `GCIMSChromatogramSet`/
+ `GCIMSSpectrumSet` object: one chromatogram/spectrum per sample (each kept
+ on its own native axis, no interpolation across samples), together with a
+ copy of `pData()`. Both new classes have a `plot()` method that can color
+ by `SampleID` or by any `pData()` column via `color_by`.
- `GCIMSSample()` now validates the object right after construction, so
passing an intensity matrix in the wrong orientation (drift time/retention
time swapped) errors immediately with a clear message instead of
diff --git a/R/aaa-AllGenerics.R b/R/aaa-AllGenerics.R
index ef475684..9859d476 100644
--- a/R/aaa-AllGenerics.R
+++ b/R/aaa-AllGenerics.R
@@ -45,6 +45,24 @@ NULL
#'
setGeneric("dtime", function(object, ...) standardGeneric("dtime"))
+#' @describeIn GCIMS-generics Get a chromatogram
+#'
+#' @return A [GCIMSChromatogram] (one sample) or a [GCIMSChromatogramSet]
+#' (several samples, one chromatogram each, with a copy of `pData()`)
+#' @param object An object to extract a chromatogram from
+#' @param ... Further arguments, possibly used by downstream methods.
+#' @export
+setGeneric("getChromatogram", function(object, ...) standardGeneric("getChromatogram"))
+
+#' @describeIn GCIMS-generics Get a spectrum
+#'
+#' @return A [GCIMSSpectrum] (one sample) or a [GCIMSSpectrumSet]
+#' (several samples, one spectrum each, with a copy of `pData()`)
+#' @param object An object to extract a spectrum from
+#' @param ... Further arguments, possibly used by downstream methods.
+#' @export
+setGeneric("getSpectrum", function(object, ...) standardGeneric("getSpectrum"))
+
#' @describeIn GCIMS-generics Get the Total Ion Spectrum
#'
#' @return The Total Ion Spectrum as a numeric vector or a matrix
diff --git a/R/aaa-class-GCIMSChromatogramSet.R b/R/aaa-class-GCIMSChromatogramSet.R
new file mode 100644
index 00000000..ef641ef7
--- /dev/null
+++ b/R/aaa-class-GCIMSChromatogramSet.R
@@ -0,0 +1,128 @@
+#' GCIMSChromatogramSet class
+#'
+#' @description
+#' GCIMSChromatogramSet is an S4 class to store one [GCIMSChromatogram] per
+#' sample of a [GCIMSDataset], together with a copy of `pData()` so plots can
+#' use the dataset's annotations.
+#'
+#' Samples are not required to share a common retention time axis: each
+#' chromatogram keeps its own, exactly as extracted from its sample. No
+#' interpolation is performed.
+#'
+#' @slot chromatograms A named list of [GCIMSChromatogram] objects, one per
+#' sample, named after their `SampleID`.
+#' @slot pData A `DataFrame` with the phenotype data, or `NULL`.
+#'
+#' @export
+#' @family GCIMSChromatogram
+methods::setClass(
+ Class = "GCIMSChromatogramSet",
+ slots = c(
+ chromatograms = "list",
+ pData = "DataFrameOrNULL"
+ )
+)
+
+methods::setMethod(
+ "initialize", "GCIMSChromatogramSet",
+ function(.Object, chromatograms = list(), pData = NULL) {
+ if (!rlang::is_named(chromatograms) && length(chromatograms) > 0) {
+ cli_abort("chromatograms should be a named list, with the SampleID of each chromatogram as its name")
+ }
+ if (!all(purrr::map_lgl(chromatograms, inherits, "GCIMSChromatogram"))) {
+ cli_abort("All elements of chromatograms should be GCIMSChromatogram objects")
+ }
+ if (!is.null(pData)) {
+ if (!"SampleID" %in% colnames(pData)) {
+ cli_abort("pData should have a SampleID column")
+ }
+ if (!setequal(as.character(pData[["SampleID"]]), names(chromatograms))) {
+ cli_abort(
+ c(
+ "pData$SampleID does not match the names of chromatograms",
+ "i" = "Both should refer to exactly the same set of samples"
+ )
+ )
+ }
+ # Guarantee pData's row order matches the chromatograms order, so the
+ # two never need to be reconciled again afterwards (e.g. in plot()):
+ pData <- pData[match(names(chromatograms), as.character(pData[["SampleID"]])), , drop = FALSE]
+ }
+ .Object@chromatograms <- chromatograms
+ .Object@pData <- pData
+ .Object
+ }
+)
+
+#' Create a [GCIMSChromatogramSet-class] object
+#'
+#' @param chromatograms A named list of [GCIMSChromatogram] objects, one per
+#' sample, named after their `SampleID`.
+#' @param pData A `data.frame`/`DataFrame`/tibble with the phenotype data, or `NULL`.
+#' @return A [GCIMSChromatogramSet-class] object
+#' @export
+#' @family GCIMSChromatogram
+GCIMSChromatogramSet <- function(chromatograms = list(), pData = NULL) {
+ if (!is.null(pData) && !inherits(pData, "DataFrame")) {
+ pData <- S4Vectors::DataFrame(pData)
+ }
+ methods::new("GCIMSChromatogramSet", chromatograms = chromatograms, pData = pData)
+}
+
+#' @describeIn GCIMSChromatogramSet-class Get the sample names
+#' @param object A [GCIMSChromatogramSet] object
+#' @return A character vector with the sample names
+#' @export
+setMethod("sampleNames", "GCIMSChromatogramSet", function(object) {
+ nms <- names(object@chromatograms)
+ if (is.null(nms)) character(0) else nms
+})
+
+#' @describeIn GCIMSChromatogramSet-class Set the sample names
+#' @param object A [GCIMSChromatogramSet] object
+#' @param value A character vector of length the number of chromatograms with the new sample names
+#' @return The [GCIMSChromatogramSet] object, with samples renamed in both
+#' `chromatograms` and `pData()`
+#' @export
+setReplaceMethod("sampleNames", "GCIMSChromatogramSet", function(object, value) {
+ if (length(value) != length(object@chromatograms)) {
+ cli_abort(
+ c(
+ "Invalid sample names",
+ "x" = "The number of sample names given ({length(value)}) != Number of samples ({length(object@chromatograms)})"
+ )
+ )
+ }
+ if (anyNA(value) || anyDuplicated(value)) {
+ cli_abort("Sample names must be unique and not missing")
+ }
+ names(object@chromatograms) <- value
+ if (!is.null(object@pData)) {
+ object@pData[["SampleID"]] <- value
+ }
+ object
+})
+
+#' @describeIn GCIMSChromatogramSet-class Get the phenotype data
+#' @param object A [GCIMSChromatogramSet] object
+#' @return A tibble with the phenotype data, or `NULL` if not set
+#' @export
+setMethod("pData", "GCIMSChromatogramSet", function(object) {
+ if (is.null(object@pData)) {
+ return(NULL)
+ }
+ tibble::as_tibble(object@pData)
+})
+
+#' @describeIn GCIMSChromatogramSet-class Number of chromatograms (samples) in the set
+#' @param x A [GCIMSChromatogramSet] object
+#' @return An integer with the number of chromatograms
+#' @export
+setMethod("length", "GCIMSChromatogramSet", function(x) length(x@chromatograms))
+
+#' @describeIn GCIMSChromatogramSet-class Extract the chromatogram of a single sample
+#' @param x A [GCIMSChromatogramSet] object
+#' @param i A number or a string with the sample index or name
+#' @return The [GCIMSChromatogram] of the requested sample
+#' @export
+setMethod("[[", "GCIMSChromatogramSet", function(x, i) x@chromatograms[[i]])
diff --git a/R/aaa-class-GCIMSSample.R b/R/aaa-class-GCIMSSample.R
index 9db41b03..d0442711 100644
--- a/R/aaa-class-GCIMSSample.R
+++ b/R/aaa-class-GCIMSSample.R
@@ -307,7 +307,7 @@ subset.GCIMSSample <- function(
#' getChromatogram(x)
#' # Take the maximum intensity in the region for each retention time:
#' sp1 <- getChromatogram(x, aggregate = function(x) apply(x, 2, max))
-getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) {
+setMethod("getChromatogram", "GCIMSSample", function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) {
dt <- dtime(object)
rt <- rtime(object)
idx <- dt_rt_range_normalization(dt, rt, dt_range, rt_range, dt_idx, rt_idx)
@@ -328,7 +328,7 @@ getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = N
description = object@description,
baseline = basel
)
-}
+})
#' Get IMS spectrum from a sample
#'
@@ -348,7 +348,7 @@ getChromatogram <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = N
#'
#' # Take the maximum intensity in the region for each drift time:
#' sp1 <- getSpectrum(x, aggregate = function(x) apply(x, 1, max))
-getSpectrum <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) {
+setMethod("getSpectrum", "GCIMSSample", function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) {
dt <- dtime(object)
rt <- rtime(object)
idx <- dt_rt_range_normalization(dt, rt, dt_range, rt_range, dt_idx, rt_idx)
@@ -369,5 +369,5 @@ getSpectrum <- function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL,
description = object@description,
baseline = basel
)
-}
+})
diff --git a/R/aaa-class-GCIMSSpectrumSet.R b/R/aaa-class-GCIMSSpectrumSet.R
new file mode 100644
index 00000000..cb5a51ec
--- /dev/null
+++ b/R/aaa-class-GCIMSSpectrumSet.R
@@ -0,0 +1,128 @@
+#' GCIMSSpectrumSet class
+#'
+#' @description
+#' GCIMSSpectrumSet is an S4 class to store one [GCIMSSpectrum] per sample of
+#' a [GCIMSDataset], together with a copy of `pData()` so plots can use the
+#' dataset's annotations.
+#'
+#' Samples are not required to share a common drift time axis: each spectrum
+#' keeps its own, exactly as extracted from its sample. No interpolation is
+#' performed.
+#'
+#' @slot spectra A named list of [GCIMSSpectrum] objects, one per sample,
+#' named after their `SampleID`.
+#' @slot pData A `DataFrame` with the phenotype data, or `NULL`.
+#'
+#' @export
+#' @family GCIMSSpectrum
+methods::setClass(
+ Class = "GCIMSSpectrumSet",
+ slots = c(
+ spectra = "list",
+ pData = "DataFrameOrNULL"
+ )
+)
+
+methods::setMethod(
+ "initialize", "GCIMSSpectrumSet",
+ function(.Object, spectra = list(), pData = NULL) {
+ if (!rlang::is_named(spectra) && length(spectra) > 0) {
+ cli_abort("spectra should be a named list, with the SampleID of each spectrum as its name")
+ }
+ if (!all(purrr::map_lgl(spectra, inherits, "GCIMSSpectrum"))) {
+ cli_abort("All elements of spectra should be GCIMSSpectrum objects")
+ }
+ if (!is.null(pData)) {
+ if (!"SampleID" %in% colnames(pData)) {
+ cli_abort("pData should have a SampleID column")
+ }
+ if (!setequal(as.character(pData[["SampleID"]]), names(spectra))) {
+ cli_abort(
+ c(
+ "pData$SampleID does not match the names of spectra",
+ "i" = "Both should refer to exactly the same set of samples"
+ )
+ )
+ }
+ # Guarantee pData's row order matches the spectra order, so the two
+ # never need to be reconciled again afterwards (e.g. in plot()):
+ pData <- pData[match(names(spectra), as.character(pData[["SampleID"]])), , drop = FALSE]
+ }
+ .Object@spectra <- spectra
+ .Object@pData <- pData
+ .Object
+ }
+)
+
+#' Create a [GCIMSSpectrumSet-class] object
+#'
+#' @param spectra A named list of [GCIMSSpectrum] objects, one per sample,
+#' named after their `SampleID`.
+#' @param pData A `data.frame`/`DataFrame`/tibble with the phenotype data, or `NULL`.
+#' @return A [GCIMSSpectrumSet-class] object
+#' @export
+#' @family GCIMSSpectrum
+GCIMSSpectrumSet <- function(spectra = list(), pData = NULL) {
+ if (!is.null(pData) && !inherits(pData, "DataFrame")) {
+ pData <- S4Vectors::DataFrame(pData)
+ }
+ methods::new("GCIMSSpectrumSet", spectra = spectra, pData = pData)
+}
+
+#' @describeIn GCIMSSpectrumSet-class Get the sample names
+#' @param object A [GCIMSSpectrumSet] object
+#' @return A character vector with the sample names
+#' @export
+setMethod("sampleNames", "GCIMSSpectrumSet", function(object) {
+ nms <- names(object@spectra)
+ if (is.null(nms)) character(0) else nms
+})
+
+#' @describeIn GCIMSSpectrumSet-class Set the sample names
+#' @param object A [GCIMSSpectrumSet] object
+#' @param value A character vector of length the number of spectra with the new sample names
+#' @return The [GCIMSSpectrumSet] object, with samples renamed in both
+#' `spectra` and `pData()`
+#' @export
+setReplaceMethod("sampleNames", "GCIMSSpectrumSet", function(object, value) {
+ if (length(value) != length(object@spectra)) {
+ cli_abort(
+ c(
+ "Invalid sample names",
+ "x" = "The number of sample names given ({length(value)}) != Number of samples ({length(object@spectra)})"
+ )
+ )
+ }
+ if (anyNA(value) || anyDuplicated(value)) {
+ cli_abort("Sample names must be unique and not missing")
+ }
+ names(object@spectra) <- value
+ if (!is.null(object@pData)) {
+ object@pData[["SampleID"]] <- value
+ }
+ object
+})
+
+#' @describeIn GCIMSSpectrumSet-class Get the phenotype data
+#' @param object A [GCIMSSpectrumSet] object
+#' @return A tibble with the phenotype data, or `NULL` if not set
+#' @export
+setMethod("pData", "GCIMSSpectrumSet", function(object) {
+ if (is.null(object@pData)) {
+ return(NULL)
+ }
+ tibble::as_tibble(object@pData)
+})
+
+#' @describeIn GCIMSSpectrumSet-class Number of spectra (samples) in the set
+#' @param x A [GCIMSSpectrumSet] object
+#' @return An integer with the number of spectra
+#' @export
+setMethod("length", "GCIMSSpectrumSet", function(x) length(x@spectra))
+
+#' @describeIn GCIMSSpectrumSet-class Extract the spectrum of a single sample
+#' @param x A [GCIMSSpectrumSet] object
+#' @param i A number or a string with the sample index or name
+#' @return The [GCIMSSpectrum] of the requested sample
+#' @export
+setMethod("[[", "GCIMSSpectrumSet", function(x, i) x@spectra[[i]])
diff --git a/R/getChromatogram_getSpectrum-GCIMSDataset.R b/R/getChromatogram_getSpectrum-GCIMSDataset.R
new file mode 100644
index 00000000..bf91d9f1
--- /dev/null
+++ b/R/getChromatogram_getSpectrum-GCIMSDataset.R
@@ -0,0 +1,67 @@
+#' Get a chromatogram from each sample of a dataset
+#'
+#' @param object A [GCIMSDataset] object
+#' @inheritParams dt_rt_range_normalization
+#' @param aggregate Function that takes the subsetted intensity matrix of each
+#' sample according to the region of interest and aggregates the drift times,
+#' returning a vector representing the chromatogram intensity. `colSums` by
+#' default.
+#' @return A [GCIMSChromatogramSet], with one [GCIMSChromatogram] per sample
+#' (each on its own retention time axis, no interpolation across samples) and
+#' a copy of `pData(object)`
+#' @export
+setMethod(
+ "getChromatogram",
+ "GCIMSDataset",
+ function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = colSums) {
+ object$realize()
+ sample_names <- sampleNames(object)
+ chromatograms <- stats::setNames(
+ purrr::map(sample_names, function(sample_name) {
+ sample <- object$getSample(sample_name)
+ getChromatogram(
+ sample,
+ dt_range = dt_range, rt_range = rt_range,
+ dt_idx = dt_idx, rt_idx = rt_idx,
+ aggregate = aggregate
+ )
+ }),
+ sample_names
+ )
+ GCIMSChromatogramSet(chromatograms = chromatograms, pData = pData(object))
+ }
+)
+
+#' Get a spectrum from each sample of a dataset
+#'
+#' @param object A [GCIMSDataset] object
+#' @inheritParams dt_rt_range_normalization
+#' @param aggregate Function that takes the subsetted intensity matrix of each
+#' sample according to the region of interest and aggregates the retention
+#' times, returning a vector representing the spectrum intensity. `rowSums`
+#' by default.
+#' @return A [GCIMSSpectrumSet], with one [GCIMSSpectrum] per sample (each on
+#' its own drift time axis, no interpolation across samples) and a copy of
+#' `pData(object)`
+#' @export
+setMethod(
+ "getSpectrum",
+ "GCIMSDataset",
+ function(object, dt_range = NULL, rt_range = NULL, dt_idx = NULL, rt_idx = NULL, aggregate = rowSums) {
+ object$realize()
+ sample_names <- sampleNames(object)
+ spectra <- stats::setNames(
+ purrr::map(sample_names, function(sample_name) {
+ sample <- object$getSample(sample_name)
+ getSpectrum(
+ sample,
+ dt_range = dt_range, rt_range = rt_range,
+ dt_idx = dt_idx, rt_idx = rt_idx,
+ aggregate = aggregate
+ )
+ }),
+ sample_names
+ )
+ GCIMSSpectrumSet(spectra = spectra, pData = pData(object))
+ }
+)
diff --git a/R/plot-GCIMSChromatogramSet.R b/R/plot-GCIMSChromatogramSet.R
new file mode 100644
index 00000000..ed2472a2
--- /dev/null
+++ b/R/plot-GCIMSChromatogramSet.R
@@ -0,0 +1,51 @@
+#' @describeIn GCIMSChromatogramSet-class plot method
+#' @param x A [GCIMSChromatogramSet] object to plot
+#' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to
+#' color the chromatograms
+#' @param ... Ignored
+#' @return A ggplot2 plot object
+#' @export
+setMethod(
+ "plot",
+ "GCIMSChromatogramSet",
+ function(x, color_by = "SampleID", ...) {
+ sample_names <- sampleNames(x)
+ if (length(sample_names) == 0) {
+ cli_abort("Can't plot an empty GCIMSChromatogramSet")
+ }
+ df <- dplyr::bind_rows(purrr::map(
+ sample_names,
+ function(sample_id) {
+ chrom <- x[[sample_id]]
+ data.frame(
+ SampleID = sample_id,
+ retention_time_s = rtime(chrom),
+ intensity = unname(intensity(chrom))
+ )
+ }
+ ))
+
+ pd <- pData(x)
+ if (!is.null(pd) && "SampleID" %in% colnames(pd)) {
+ df <- dplyr::left_join(df, pd, by = "SampleID")
+ }
+ if (!color_by %in% colnames(df)) {
+ cli_abort("{.val {color_by}} is not a column of {.code pData(x)} (or {.val SampleID})")
+ }
+
+ ggplot2::ggplot(df) +
+ ggplot2::geom_line(
+ mapping = ggplot2::aes(
+ x = .data$retention_time_s,
+ y = .data$intensity,
+ color = .data[[color_by]],
+ group = .data$SampleID
+ )
+ ) +
+ ggplot2::labs(
+ x = "Retention time (s)",
+ y = "Intensity (a.u.)",
+ color = color_by
+ )
+ }
+)
diff --git a/R/plot-GCIMSSpectrumSet.R b/R/plot-GCIMSSpectrumSet.R
new file mode 100644
index 00000000..2fb9cc05
--- /dev/null
+++ b/R/plot-GCIMSSpectrumSet.R
@@ -0,0 +1,51 @@
+#' @describeIn GCIMSSpectrumSet-class plot method
+#' @param x A [GCIMSSpectrumSet] object to plot
+#' @param color_by The name of a `pData(x)` column (or `"SampleID"`) used to
+#' color the spectra
+#' @param ... Ignored
+#' @return A ggplot2 plot object
+#' @export
+setMethod(
+ "plot",
+ "GCIMSSpectrumSet",
+ function(x, color_by = "SampleID", ...) {
+ sample_names <- sampleNames(x)
+ if (length(sample_names) == 0) {
+ cli_abort("Can't plot an empty GCIMSSpectrumSet")
+ }
+ df <- dplyr::bind_rows(purrr::map(
+ sample_names,
+ function(sample_id) {
+ spec <- x[[sample_id]]
+ data.frame(
+ SampleID = sample_id,
+ drift_time_ms = dtime(spec),
+ intensity = unname(intensity(spec))
+ )
+ }
+ ))
+
+ pd <- pData(x)
+ if (!is.null(pd) && "SampleID" %in% colnames(pd)) {
+ df <- dplyr::left_join(df, pd, by = "SampleID")
+ }
+ if (!color_by %in% colnames(df)) {
+ cli_abort("{.val {color_by}} is not a column of {.code pData(x)} (or {.val SampleID})")
+ }
+
+ ggplot2::ggplot(df) +
+ ggplot2::geom_line(
+ mapping = ggplot2::aes(
+ x = .data$drift_time_ms,
+ y = .data$intensity,
+ color = .data[[color_by]],
+ group = .data$SampleID
+ )
+ ) +
+ ggplot2::labs(
+ x = "Drift time (ms)",
+ y = "Intensity (a.u.)",
+ color = color_by
+ )
+ }
+)
diff --git a/man/DelayedDatasetBase.Rd b/man/DelayedDatasetBase.Rd
index 4329201d..d1731efe 100644
--- a/man/DelayedDatasetBase.Rd
+++ b/man/DelayedDatasetBase.Rd
@@ -28,6 +28,7 @@ This class is not exported, but if you want to use it reach us at
\item \href{#method-DelayedDatasetBase-registerOptimization}{\code{DelayedDatasetBase$registerOptimization()}}
\item \href{#method-DelayedDatasetBase-appendDelayedOp}{\code{DelayedDatasetBase$appendDelayedOp()}}
\item \href{#method-DelayedDatasetBase-hasDelayedOps}{\code{DelayedDatasetBase$hasDelayedOps()}}
+\item \href{#method-DelayedDatasetBase-dropSolePendingOp}{\code{DelayedDatasetBase$dropSolePendingOp()}}
\item \href{#method-DelayedDatasetBase-getSample}{\code{DelayedDatasetBase$getSample()}}
\item \href{#method-DelayedDatasetBase-history_as_list}{\code{DelayedDatasetBase$history_as_list()}}
\item \href{#method-DelayedDatasetBase-pending_as_list}{\code{DelayedDatasetBase$pending_as_list()}}
@@ -142,6 +143,31 @@ Returns \code{TRUE} if the dataset has pending operations, \code{FALSE} otherwis
}
}
\if{html}{\out{
}}
+\if{html}{\out{}}
+\if{latex}{\out{\hypertarget{method-DelayedDatasetBase-dropSolePendingOp}{}}}
+\subsection{Method \code{dropSolePendingOp()}}{
+If the \emph{only} pending operation is the one named \code{name}, discard it.
+This is narrowly scoped on purpose: it is only safe to replace a
+pending operation with a newer, equivalent one when nothing else is
+queued alongside it (otherwise the operation could be re-run out of
+the order it was originally queued in, relative to whatever else is
+pending).
+\subsection{Usage}{
+\if{html}{\out{}}\preformatted{DelayedDatasetBase$dropSolePendingOp(name)}\if{html}{\out{
}}
+}
+
+\subsection{Arguments}{
+\if{html}{\out{}}
+\describe{
+\item{\code{name}}{The operation name to drop, if it is the sole pending one}
+}
+\if{html}{\out{
}}
+}
+\subsection{Returns}{
+\code{TRUE} if the operation was dropped, \code{FALSE} otherwise
+}
+}
+\if{html}{\out{
}}
\if{html}{\out{}}
\if{latex}{\out{\hypertarget{method-DelayedDatasetBase-getSample}{}}}
\subsection{Method \code{getSample()}}{
diff --git a/man/DelayedDatasetDisk.Rd b/man/DelayedDatasetDisk.Rd
index 9e7297bd..b1e6a4b2 100644
--- a/man/DelayedDatasetDisk.Rd
+++ b/man/DelayedDatasetDisk.Rd
@@ -41,6 +41,7 @@ This class is not exported, but if you want to use it reach us at
Inherited methods
GCIMS::DelayedDatasetBase$appendDelayedOp()
+GCIMS::DelayedDatasetBase$dropSolePendingOp()
GCIMS::DelayedDatasetBase$hasDelayedOps()
GCIMS::DelayedDatasetBase$history_as_list()
GCIMS::DelayedDatasetBase$pending_as_list()
diff --git a/man/DelayedDatasetRAM.Rd b/man/DelayedDatasetRAM.Rd
index 726afabe..09d95983 100644
--- a/man/DelayedDatasetRAM.Rd
+++ b/man/DelayedDatasetRAM.Rd
@@ -36,6 +36,7 @@ This class is not exported, but if you want to use it reach us at
Inherited methods
GCIMS::DelayedDatasetBase$appendDelayedOp()
+GCIMS::DelayedDatasetBase$dropSolePendingOp()
GCIMS::DelayedDatasetBase$hasDelayedOps()
GCIMS::DelayedDatasetBase$history_as_list()
GCIMS::DelayedDatasetBase$pending_as_list()
diff --git a/man/GCIMS-generics.Rd b/man/GCIMS-generics.Rd
index cd7d4818..0224a63c 100644
--- a/man/GCIMS-generics.Rd
+++ b/man/GCIMS-generics.Rd
@@ -3,6 +3,8 @@
\name{GCIMS-generics}
\alias{GCIMS-generics}
\alias{dtime}
+\alias{getChromatogram}
+\alias{getSpectrum}
\alias{getTIS}
\alias{getRIC}
\alias{plotTIS}
@@ -19,6 +21,10 @@
\usage{
dtime(object, ...)
+getChromatogram(object, ...)
+
+getSpectrum(object, ...)
+
getTIS(object, ...)
getRIC(object, ...)
@@ -53,6 +59,12 @@ integratePeaks(object, ...)
\value{
A numeric vector with the drift time
+A \link{GCIMSChromatogram} (one sample) or a \link{GCIMSChromatogramSet}
+(several samples, one chromatogram each, with a copy of \code{pData()})
+
+A \link{GCIMSSpectrum} (one sample) or a \link{GCIMSSpectrumSet}
+(several samples, one spectrum each, with a copy of \code{pData()})
+
The Total Ion Spectrum as a numeric vector or a matrix
(depending if the object is one sample or several)
@@ -87,6 +99,10 @@ to an existing generics-only package if you need so.
\itemize{
\item \code{dtime()}: Get drift time vector
+\item \code{getChromatogram()}: Get a chromatogram
+
+\item \code{getSpectrum()}: Get a spectrum
+
\item \code{getTIS()}: Get the Total Ion Spectrum
\item \code{getRIC()}: Get the Reverse Ion Chromatogram
diff --git a/man/GCIMSChromatogram-class.Rd b/man/GCIMSChromatogram-class.Rd
index 87a348bb..29903b82 100644
--- a/man/GCIMSChromatogram-class.Rd
+++ b/man/GCIMSChromatogram-class.Rd
@@ -101,6 +101,8 @@ or \code{NULL} if not set. Use \code{\link[=estimateBaseline]{estimateBaseline()
\seealso{
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
diff --git a/man/GCIMSChromatogram.Rd b/man/GCIMSChromatogram.Rd
index beecdae5..e8be0545 100644
--- a/man/GCIMSChromatogram.Rd
+++ b/man/GCIMSChromatogram.Rd
@@ -48,6 +48,8 @@ GCIMSChromatogram(
\seealso{
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
diff --git a/man/GCIMSChromatogramSet-class.Rd b/man/GCIMSChromatogramSet-class.Rd
new file mode 100644
index 00000000..2b5f82d9
--- /dev/null
+++ b/man/GCIMSChromatogramSet-class.Rd
@@ -0,0 +1,100 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/aaa-class-GCIMSChromatogramSet.R,
+% R/plot-GCIMSChromatogramSet.R
+\docType{class}
+\name{GCIMSChromatogramSet-class}
+\alias{GCIMSChromatogramSet-class}
+\alias{sampleNames,GCIMSChromatogramSet-method}
+\alias{sampleNames<-,GCIMSChromatogramSet,ANY-method}
+\alias{pData,GCIMSChromatogramSet-method}
+\alias{length,GCIMSChromatogramSet-method}
+\alias{[[,GCIMSChromatogramSet-method}
+\alias{plot,GCIMSChromatogramSet,ANY-method}
+\title{GCIMSChromatogramSet class}
+\usage{
+\S4method{sampleNames}{GCIMSChromatogramSet}(object)
+
+\S4method{sampleNames}{GCIMSChromatogramSet,ANY}(object) <- value
+
+\S4method{pData}{GCIMSChromatogramSet}(object)
+
+\S4method{length}{GCIMSChromatogramSet}(x)
+
+\S4method{[[}{GCIMSChromatogramSet}(x, i)
+
+\S4method{plot}{GCIMSChromatogramSet,ANY}(x, color_by = "SampleID", ...)
+}
+\arguments{
+\item{object}{A \link{GCIMSChromatogramSet} object}
+
+\item{value}{A character vector of length the number of chromatograms with the new sample names}
+
+\item{x}{A \link{GCIMSChromatogramSet} object to plot}
+
+\item{i}{A number or a string with the sample index or name}
+
+\item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to
+color the chromatograms}
+
+\item{...}{Ignored}
+}
+\value{
+A character vector with the sample names
+
+The \link{GCIMSChromatogramSet} object, with samples renamed in both
+\code{chromatograms} and \code{pData()}
+
+A tibble with the phenotype data, or \code{NULL} if not set
+
+An integer with the number of chromatograms
+
+The \link{GCIMSChromatogram} of the requested sample
+
+A ggplot2 plot object
+}
+\description{
+GCIMSChromatogramSet is an S4 class to store one \link{GCIMSChromatogram} per
+sample of a \link{GCIMSDataset}, together with a copy of \code{pData()} so plots can
+use the dataset's annotations.
+
+Samples are not required to share a common retention time axis: each
+chromatogram keeps its own, exactly as extracted from its sample. No
+interpolation is performed.
+}
+\section{Functions}{
+\itemize{
+\item \code{sampleNames(GCIMSChromatogramSet)}: Get the sample names
+
+\item \code{sampleNames(object = GCIMSChromatogramSet) <- value}: Set the sample names
+
+\item \code{pData(GCIMSChromatogramSet)}: Get the phenotype data
+
+\item \code{length(GCIMSChromatogramSet)}: Number of chromatograms (samples) in the set
+
+\item \code{[[}: Extract the chromatogram of a single sample
+
+\item \code{plot(x = GCIMSChromatogramSet, y = ANY)}: plot method
+
+}}
+\section{Slots}{
+
+\describe{
+\item{\code{chromatograms}}{A named list of \link{GCIMSChromatogram} objects, one per
+sample, named after their \code{SampleID}.}
+
+\item{\code{pData}}{A \code{DataFrame} with the phenotype data, or \code{NULL}.}
+}}
+
+\seealso{
+Other GCIMSChromatogram:
+\code{\link{GCIMSChromatogram}},
+\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{dtime,GCIMSChromatogram-method}},
+\code{\link{estimateBaseline,GCIMSChromatogram-method}},
+\code{\link{findPeaks,GCIMSChromatogram-method}},
+\code{\link{intensity,GCIMSChromatogram-method}},
+\code{\link{rtime,GCIMSChromatogram-method}},
+\code{\link{smooth,GCIMSChromatogram-method}}
+}
+\concept{GCIMSChromatogram}
diff --git a/man/GCIMSChromatogramSet.Rd b/man/GCIMSChromatogramSet.Rd
new file mode 100644
index 00000000..5cb15967
--- /dev/null
+++ b/man/GCIMSChromatogramSet.Rd
@@ -0,0 +1,33 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/aaa-class-GCIMSChromatogramSet.R
+\name{GCIMSChromatogramSet}
+\alias{GCIMSChromatogramSet}
+\title{Create a \linkS4class{GCIMSChromatogramSet} object}
+\usage{
+GCIMSChromatogramSet(chromatograms = list(), pData = NULL)
+}
+\arguments{
+\item{chromatograms}{A named list of \link{GCIMSChromatogram} objects, one per
+sample, named after their \code{SampleID}.}
+
+\item{pData}{A \code{data.frame}/\code{DataFrame}/tibble with the phenotype data, or \code{NULL}.}
+}
+\value{
+A \linkS4class{GCIMSChromatogramSet} object
+}
+\description{
+Create a \linkS4class{GCIMSChromatogramSet} object
+}
+\seealso{
+Other GCIMSChromatogram:
+\code{\link{GCIMSChromatogram}},
+\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet-class}},
+\code{\link{dtime,GCIMSChromatogram-method}},
+\code{\link{estimateBaseline,GCIMSChromatogram-method}},
+\code{\link{findPeaks,GCIMSChromatogram-method}},
+\code{\link{intensity,GCIMSChromatogram-method}},
+\code{\link{rtime,GCIMSChromatogram-method}},
+\code{\link{smooth,GCIMSChromatogram-method}}
+}
+\concept{GCIMSChromatogram}
diff --git a/man/GCIMSSpectrumSet-class.Rd b/man/GCIMSSpectrumSet-class.Rd
new file mode 100644
index 00000000..6687177a
--- /dev/null
+++ b/man/GCIMSSpectrumSet-class.Rd
@@ -0,0 +1,93 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/aaa-class-GCIMSSpectrumSet.R,
+% R/plot-GCIMSSpectrumSet.R
+\docType{class}
+\name{GCIMSSpectrumSet-class}
+\alias{GCIMSSpectrumSet-class}
+\alias{sampleNames,GCIMSSpectrumSet-method}
+\alias{sampleNames<-,GCIMSSpectrumSet,ANY-method}
+\alias{pData,GCIMSSpectrumSet-method}
+\alias{length,GCIMSSpectrumSet-method}
+\alias{[[,GCIMSSpectrumSet-method}
+\alias{plot,GCIMSSpectrumSet,ANY-method}
+\title{GCIMSSpectrumSet class}
+\usage{
+\S4method{sampleNames}{GCIMSSpectrumSet}(object)
+
+\S4method{sampleNames}{GCIMSSpectrumSet,ANY}(object) <- value
+
+\S4method{pData}{GCIMSSpectrumSet}(object)
+
+\S4method{length}{GCIMSSpectrumSet}(x)
+
+\S4method{[[}{GCIMSSpectrumSet}(x, i)
+
+\S4method{plot}{GCIMSSpectrumSet,ANY}(x, color_by = "SampleID", ...)
+}
+\arguments{
+\item{object}{A \link{GCIMSSpectrumSet} object}
+
+\item{value}{A character vector of length the number of spectra with the new sample names}
+
+\item{x}{A \link{GCIMSSpectrumSet} object to plot}
+
+\item{i}{A number or a string with the sample index or name}
+
+\item{color_by}{The name of a \code{pData(x)} column (or \code{"SampleID"}) used to
+color the spectra}
+
+\item{...}{Ignored}
+}
+\value{
+A character vector with the sample names
+
+The \link{GCIMSSpectrumSet} object, with samples renamed in both
+\code{spectra} and \code{pData()}
+
+A tibble with the phenotype data, or \code{NULL} if not set
+
+An integer with the number of spectra
+
+The \link{GCIMSSpectrum} of the requested sample
+
+A ggplot2 plot object
+}
+\description{
+GCIMSSpectrumSet is an S4 class to store one \link{GCIMSSpectrum} per sample of
+a \link{GCIMSDataset}, together with a copy of \code{pData()} so plots can use the
+dataset's annotations.
+
+Samples are not required to share a common drift time axis: each spectrum
+keeps its own, exactly as extracted from its sample. No interpolation is
+performed.
+}
+\section{Functions}{
+\itemize{
+\item \code{sampleNames(GCIMSSpectrumSet)}: Get the sample names
+
+\item \code{sampleNames(object = GCIMSSpectrumSet) <- value}: Set the sample names
+
+\item \code{pData(GCIMSSpectrumSet)}: Get the phenotype data
+
+\item \code{length(GCIMSSpectrumSet)}: Number of spectra (samples) in the set
+
+\item \code{[[}: Extract the spectrum of a single sample
+
+\item \code{plot(x = GCIMSSpectrumSet, y = ANY)}: plot method
+
+}}
+\section{Slots}{
+
+\describe{
+\item{\code{spectra}}{A named list of \link{GCIMSSpectrum} objects, one per sample,
+named after their \code{SampleID}.}
+
+\item{\code{pData}}{A \code{DataFrame} with the phenotype data, or \code{NULL}.}
+}}
+
+\seealso{
+Other GCIMSSpectrum:
+\code{\link{GCIMSSpectrumSet}},
+\code{\link{findPeaks,GCIMSSpectrum-method}}
+}
+\concept{GCIMSSpectrum}
diff --git a/man/GCIMSSpectrumSet.Rd b/man/GCIMSSpectrumSet.Rd
new file mode 100644
index 00000000..07c1f519
--- /dev/null
+++ b/man/GCIMSSpectrumSet.Rd
@@ -0,0 +1,26 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/aaa-class-GCIMSSpectrumSet.R
+\name{GCIMSSpectrumSet}
+\alias{GCIMSSpectrumSet}
+\title{Create a \linkS4class{GCIMSSpectrumSet} object}
+\usage{
+GCIMSSpectrumSet(spectra = list(), pData = NULL)
+}
+\arguments{
+\item{spectra}{A named list of \link{GCIMSSpectrum} objects, one per sample,
+named after their \code{SampleID}.}
+
+\item{pData}{A \code{data.frame}/\code{DataFrame}/tibble with the phenotype data, or \code{NULL}.}
+}
+\value{
+A \linkS4class{GCIMSSpectrumSet} object
+}
+\description{
+Create a \linkS4class{GCIMSSpectrumSet} object
+}
+\seealso{
+Other GCIMSSpectrum:
+\code{\link{GCIMSSpectrumSet-class}},
+\code{\link{findPeaks,GCIMSSpectrum-method}}
+}
+\concept{GCIMSSpectrum}
diff --git a/man/dtime-GCIMSChromatogram-method.Rd b/man/dtime-GCIMSChromatogram-method.Rd
index f7a629be..623c1e03 100644
--- a/man/dtime-GCIMSChromatogram-method.Rd
+++ b/man/dtime-GCIMSChromatogram-method.Rd
@@ -19,6 +19,8 @@ Get the drift time of the chromatogram
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
\code{\link{intensity,GCIMSChromatogram-method}},
diff --git a/man/estimateBaseline-GCIMSChromatogram-method.Rd b/man/estimateBaseline-GCIMSChromatogram-method.Rd
index 78ad84f6..53c155c7 100644
--- a/man/estimateBaseline-GCIMSChromatogram-method.Rd
+++ b/man/estimateBaseline-GCIMSChromatogram-method.Rd
@@ -45,6 +45,8 @@ The length of the regions are given in seconds in the \code{region_s} parameter.
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
\code{\link{intensity,GCIMSChromatogram-method}},
diff --git a/man/findPeaks-GCIMSChromatogram-method.Rd b/man/findPeaks-GCIMSChromatogram-method.Rd
index 9b35abc9..1ac05f26 100644
--- a/man/findPeaks-GCIMSChromatogram-method.Rd
+++ b/man/findPeaks-GCIMSChromatogram-method.Rd
@@ -31,6 +31,8 @@ Peak detection for a GCIMSChromatogram
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{intensity,GCIMSChromatogram-method}},
diff --git a/man/findPeaks-GCIMSSpectrum-method.Rd b/man/findPeaks-GCIMSSpectrum-method.Rd
index 208d758f..99c23fc6 100644
--- a/man/findPeaks-GCIMSSpectrum-method.Rd
+++ b/man/findPeaks-GCIMSSpectrum-method.Rd
@@ -27,4 +27,9 @@ The modified \link{GCIMSSpectrum}, with a peak list
\description{
Peak detection for a GCIMSSpectrum
}
+\seealso{
+Other GCIMSSpectrum:
+\code{\link{GCIMSSpectrumSet}},
+\code{\link{GCIMSSpectrumSet-class}}
+}
\concept{GCIMSSpectrum}
diff --git a/man/getChromatogram-GCIMSDataset-method.Rd b/man/getChromatogram-GCIMSDataset-method.Rd
new file mode 100644
index 00000000..491b06e5
--- /dev/null
+++ b/man/getChromatogram-GCIMSDataset-method.Rd
@@ -0,0 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/getChromatogram_getSpectrum-GCIMSDataset.R
+\name{getChromatogram,GCIMSDataset-method}
+\alias{getChromatogram,GCIMSDataset-method}
+\title{Get a chromatogram from each sample of a dataset}
+\usage{
+\S4method{getChromatogram}{GCIMSDataset}(
+ object,
+ dt_range = NULL,
+ rt_range = NULL,
+ dt_idx = NULL,
+ rt_idx = NULL,
+ aggregate = colSums
+)
+}
+\arguments{
+\item{object}{A \link{GCIMSDataset} object}
+
+\item{dt_range}{The minimum and maximum drift times to extract (length 2 vector)}
+
+\item{rt_range}{The minimum and maximum retention times to extract (length 2 vector)}
+
+\item{dt_idx}{A numeric vector with the drift time indices to extract (or a logical vector of the length of drift time)}
+
+\item{rt_idx}{A numeric vector with the retention time indices to extract (or a logical vector of the length of retention time)}
+
+\item{aggregate}{Function that takes the subsetted intensity matrix of each
+sample according to the region of interest and aggregates the drift times,
+returning a vector representing the chromatogram intensity. \code{colSums} by
+default.}
+}
+\value{
+A \link{GCIMSChromatogramSet}, with one \link{GCIMSChromatogram} per sample
+(each on its own retention time axis, no interpolation across samples) and
+a copy of \code{pData(object)}
+}
+\description{
+Get a chromatogram from each sample of a dataset
+}
diff --git a/man/getChromatogram.Rd b/man/getChromatogram-GCIMSSample-method.Rd
similarity index 91%
rename from man/getChromatogram.Rd
rename to man/getChromatogram-GCIMSSample-method.Rd
index ac5252c4..fb8a94dc 100644
--- a/man/getChromatogram.Rd
+++ b/man/getChromatogram-GCIMSSample-method.Rd
@@ -1,10 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aaa-class-GCIMSSample.R
-\name{getChromatogram}
-\alias{getChromatogram}
+\name{getChromatogram,GCIMSSample-method}
+\alias{getChromatogram,GCIMSSample-method}
\title{Get the extracted ion chromatogram}
\usage{
-getChromatogram(
+\S4method{getChromatogram}{GCIMSSample}(
object,
dt_range = NULL,
rt_range = NULL,
diff --git a/man/getSpectrum-GCIMSDataset-method.Rd b/man/getSpectrum-GCIMSDataset-method.Rd
new file mode 100644
index 00000000..051c792f
--- /dev/null
+++ b/man/getSpectrum-GCIMSDataset-method.Rd
@@ -0,0 +1,39 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/getChromatogram_getSpectrum-GCIMSDataset.R
+\name{getSpectrum,GCIMSDataset-method}
+\alias{getSpectrum,GCIMSDataset-method}
+\title{Get a spectrum from each sample of a dataset}
+\usage{
+\S4method{getSpectrum}{GCIMSDataset}(
+ object,
+ dt_range = NULL,
+ rt_range = NULL,
+ dt_idx = NULL,
+ rt_idx = NULL,
+ aggregate = rowSums
+)
+}
+\arguments{
+\item{object}{A \link{GCIMSDataset} object}
+
+\item{dt_range}{The minimum and maximum drift times to extract (length 2 vector)}
+
+\item{rt_range}{The minimum and maximum retention times to extract (length 2 vector)}
+
+\item{dt_idx}{A numeric vector with the drift time indices to extract (or a logical vector of the length of drift time)}
+
+\item{rt_idx}{A numeric vector with the retention time indices to extract (or a logical vector of the length of retention time)}
+
+\item{aggregate}{Function that takes the subsetted intensity matrix of each
+sample according to the region of interest and aggregates the retention
+times, returning a vector representing the spectrum intensity. \code{rowSums}
+by default.}
+}
+\value{
+A \link{GCIMSSpectrumSet}, with one \link{GCIMSSpectrum} per sample (each on
+its own drift time axis, no interpolation across samples) and a copy of
+\code{pData(object)}
+}
+\description{
+Get a spectrum from each sample of a dataset
+}
diff --git a/man/getSpectrum.Rd b/man/getSpectrum-GCIMSSample-method.Rd
similarity index 92%
rename from man/getSpectrum.Rd
rename to man/getSpectrum-GCIMSSample-method.Rd
index f4cb1634..3bc4ed76 100644
--- a/man/getSpectrum.Rd
+++ b/man/getSpectrum-GCIMSSample-method.Rd
@@ -1,10 +1,10 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/aaa-class-GCIMSSample.R
-\name{getSpectrum}
-\alias{getSpectrum}
+\name{getSpectrum,GCIMSSample-method}
+\alias{getSpectrum,GCIMSSample-method}
\title{Get IMS spectrum from a sample}
\usage{
-getSpectrum(
+\S4method{getSpectrum}{GCIMSSample}(
object,
dt_range = NULL,
rt_range = NULL,
diff --git a/man/intensity-GCIMSChromatogram-method.Rd b/man/intensity-GCIMSChromatogram-method.Rd
index a00c6e25..bd6dbac8 100644
--- a/man/intensity-GCIMSChromatogram-method.Rd
+++ b/man/intensity-GCIMSChromatogram-method.Rd
@@ -23,6 +23,8 @@ Get the intensity vector
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
diff --git a/man/rtime-GCIMSChromatogram-method.Rd b/man/rtime-GCIMSChromatogram-method.Rd
index b71b0bee..550dd3f2 100644
--- a/man/rtime-GCIMSChromatogram-method.Rd
+++ b/man/rtime-GCIMSChromatogram-method.Rd
@@ -19,6 +19,8 @@ Get the retention time vector
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
diff --git a/man/smooth-GCIMSChromatogram-method.Rd b/man/smooth-GCIMSChromatogram-method.Rd
index 4aa4fbe5..1e42abd8 100644
--- a/man/smooth-GCIMSChromatogram-method.Rd
+++ b/man/smooth-GCIMSChromatogram-method.Rd
@@ -23,6 +23,8 @@ Smoothing a GCIMS chromatogram using a Savitzky-Golay filter
Other GCIMSChromatogram:
\code{\link{GCIMSChromatogram}},
\code{\link{GCIMSChromatogram-class}},
+\code{\link{GCIMSChromatogramSet}},
+\code{\link{GCIMSChromatogramSet-class}},
\code{\link{dtime,GCIMSChromatogram-method}},
\code{\link{estimateBaseline,GCIMSChromatogram-method}},
\code{\link{findPeaks,GCIMSChromatogram-method}},
diff --git a/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R
new file mode 100644
index 00000000..90404b8c
--- /dev/null
+++ b/tests/testthat/test-aaa-class-GCIMSChromatogramSet.R
@@ -0,0 +1,110 @@
+make_chrom <- function(sample_id) {
+ GCIMSChromatogram(
+ retention_time = 1:5,
+ intensity = (1:5) * 10,
+ description = sample_id
+ )
+}
+
+test_that("GCIMSChromatogramSet stores chromatograms and pData", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+ pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B"))
+
+ cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd)
+
+ expect_s4_class(cs, "GCIMSChromatogramSet")
+ expect_equal(sampleNames(cs), c("s1", "s2"))
+ expect_equal(length(cs), 2L)
+ expect_equal(pData(cs)$Group, c("A", "B"))
+ expect_identical(cs[["s1"]], chroms$s1)
+})
+
+test_that("GCIMSChromatogramSet can be built with no pData", {
+ chroms <- list(s1 = make_chrom("s1"))
+
+ cs <- GCIMSChromatogramSet(chromatograms = chroms)
+
+ expect_null(pData(cs))
+ expect_equal(sampleNames(cs), "s1")
+})
+
+test_that("GCIMSChromatogramSet can be empty", {
+ cs <- GCIMSChromatogramSet()
+
+ expect_equal(length(cs), 0L)
+ expect_equal(sampleNames(cs), character(0))
+})
+
+test_that("GCIMSChromatogramSet rejects an unnamed list of chromatograms", {
+ chroms <- list(make_chrom("s1"), make_chrom("s2"))
+
+ expect_error(GCIMSChromatogramSet(chromatograms = chroms), "named list")
+})
+
+test_that("GCIMSChromatogramSet rejects elements that are not GCIMSChromatogram objects", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = "not a chromatogram")
+
+ expect_error(GCIMSChromatogramSet(chromatograms = chroms), "GCIMSChromatogram objects")
+})
+
+test_that("GCIMSChromatogramSet rejects pData whose SampleID doesn't match the chromatogram names", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+
+ # Different sample entirely:
+ pd_mismatched <- data.frame(SampleID = c("s1", "other"), Group = c("A", "B"))
+ expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_mismatched), "does not match")
+
+ # Missing a sample:
+ pd_missing <- data.frame(SampleID = "s1", Group = "A")
+ expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_missing), "does not match")
+
+ # No SampleID column at all:
+ pd_no_sampleid <- data.frame(Group = c("A", "B"))
+ expect_error(GCIMSChromatogramSet(chromatograms = chroms, pData = pd_no_sampleid), "SampleID column")
+})
+
+test_that("construction realigns pData's row order to match the chromatograms list order", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+ # pData given in the opposite order, with annotations tied to SampleID:
+ pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A"))
+
+ cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd_reversed)
+
+ expect_equal(sampleNames(cs), c("s1", "s2"))
+ expect_equal(pData(cs)$SampleID, c("s1", "s2"))
+ # Annotations stay correctly attached to their sample after realignment:
+ expect_equal(pData(cs)$Group, c("A", "B"))
+})
+
+test_that("sampleNames<-() renames both the chromatograms list and pData$SampleID", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+ pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B"))
+ cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd)
+
+ sampleNames(cs) <- c("a", "b")
+
+ expect_equal(sampleNames(cs), c("a", "b"))
+ expect_equal(pData(cs)$SampleID, c("a", "b"))
+ expect_equal(pData(cs)$Group, c("A", "B"))
+ expect_identical(cs[["a"]], chroms$s1)
+ expect_identical(cs[["b"]], chroms$s2)
+})
+
+test_that("sampleNames<-() works without pData", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+ cs <- GCIMSChromatogramSet(chromatograms = chroms)
+
+ sampleNames(cs) <- c("a", "b")
+
+ expect_equal(sampleNames(cs), c("a", "b"))
+ expect_null(pData(cs))
+})
+
+test_that("sampleNames<-() rejects the wrong length or non-unique/missing names", {
+ chroms <- list(s1 = make_chrom("s1"), s2 = make_chrom("s2"))
+ cs <- GCIMSChromatogramSet(chromatograms = chroms)
+
+ expect_error(sampleNames(cs) <- "a", "Number of samples")
+ expect_error(sampleNames(cs) <- c("a", "a"), "unique")
+ expect_error(sampleNames(cs) <- c("a", NA), "unique and not missing")
+})
diff --git a/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R
new file mode 100644
index 00000000..02c88687
--- /dev/null
+++ b/tests/testthat/test-aaa-class-GCIMSSpectrumSet.R
@@ -0,0 +1,110 @@
+make_spec <- function(sample_id) {
+ GCIMSSpectrum(
+ drift_time = 1:5,
+ intensity = (1:5) * 10,
+ description = sample_id
+ )
+}
+
+test_that("GCIMSSpectrumSet stores spectra and pData", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+ pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B"))
+
+ ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd)
+
+ expect_s4_class(ss, "GCIMSSpectrumSet")
+ expect_equal(sampleNames(ss), c("s1", "s2"))
+ expect_equal(length(ss), 2L)
+ expect_equal(pData(ss)$Group, c("A", "B"))
+ expect_identical(ss[["s1"]], spectra$s1)
+})
+
+test_that("GCIMSSpectrumSet can be built with no pData", {
+ spectra <- list(s1 = make_spec("s1"))
+
+ ss <- GCIMSSpectrumSet(spectra = spectra)
+
+ expect_null(pData(ss))
+ expect_equal(sampleNames(ss), "s1")
+})
+
+test_that("GCIMSSpectrumSet can be empty", {
+ ss <- GCIMSSpectrumSet()
+
+ expect_equal(length(ss), 0L)
+ expect_equal(sampleNames(ss), character(0))
+})
+
+test_that("GCIMSSpectrumSet rejects an unnamed list of spectra", {
+ spectra <- list(make_spec("s1"), make_spec("s2"))
+
+ expect_error(GCIMSSpectrumSet(spectra = spectra), "named list")
+})
+
+test_that("GCIMSSpectrumSet rejects elements that are not GCIMSSpectrum objects", {
+ spectra <- list(s1 = make_spec("s1"), s2 = "not a spectrum")
+
+ expect_error(GCIMSSpectrumSet(spectra = spectra), "GCIMSSpectrum objects")
+})
+
+test_that("GCIMSSpectrumSet rejects pData whose SampleID doesn't match the spectra names", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+
+ # Different sample entirely:
+ pd_mismatched <- data.frame(SampleID = c("s1", "other"), Group = c("A", "B"))
+ expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_mismatched), "does not match")
+
+ # Missing a sample:
+ pd_missing <- data.frame(SampleID = "s1", Group = "A")
+ expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_missing), "does not match")
+
+ # No SampleID column at all:
+ pd_no_sampleid <- data.frame(Group = c("A", "B"))
+ expect_error(GCIMSSpectrumSet(spectra = spectra, pData = pd_no_sampleid), "SampleID column")
+})
+
+test_that("construction realigns pData's row order to match the spectra list order", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+ # pData given in the opposite order, with annotations tied to SampleID:
+ pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A"))
+
+ ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd_reversed)
+
+ expect_equal(sampleNames(ss), c("s1", "s2"))
+ expect_equal(pData(ss)$SampleID, c("s1", "s2"))
+ # Annotations stay correctly attached to their sample after realignment:
+ expect_equal(pData(ss)$Group, c("A", "B"))
+})
+
+test_that("sampleNames<-() renames both the spectra list and pData$SampleID", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+ pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B"))
+ ss <- GCIMSSpectrumSet(spectra = spectra, pData = pd)
+
+ sampleNames(ss) <- c("a", "b")
+
+ expect_equal(sampleNames(ss), c("a", "b"))
+ expect_equal(pData(ss)$SampleID, c("a", "b"))
+ expect_equal(pData(ss)$Group, c("A", "B"))
+ expect_identical(ss[["a"]], spectra$s1)
+ expect_identical(ss[["b"]], spectra$s2)
+})
+
+test_that("sampleNames<-() works without pData", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+ ss <- GCIMSSpectrumSet(spectra = spectra)
+
+ sampleNames(ss) <- c("a", "b")
+
+ expect_equal(sampleNames(ss), c("a", "b"))
+ expect_null(pData(ss))
+})
+
+test_that("sampleNames<-() rejects the wrong length or non-unique/missing names", {
+ spectra <- list(s1 = make_spec("s1"), s2 = make_spec("s2"))
+ ss <- GCIMSSpectrumSet(spectra = spectra)
+
+ expect_error(sampleNames(ss) <- "a", "Number of samples")
+ expect_error(sampleNames(ss) <- c("a", "a"), "unique")
+ expect_error(sampleNames(ss) <- c("a", NA), "unique and not missing")
+})
diff --git a/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R
new file mode 100644
index 00000000..aaa5298d
--- /dev/null
+++ b/tests/testthat/test-getChromatogram_getSpectrum-GCIMSDataset.R
@@ -0,0 +1,109 @@
+make_dataset_mismatched_axes <- function() {
+ # s1 and s2 deliberately have retention/drift time axes of different
+ # length and range, to prove that getChromatogram()/getSpectrum() on a
+ # GCIMSDataset never interpolates samples onto a common grid: each
+ # extracted chromatogram/spectrum keeps its own sample's native axis.
+ s1 <- GCIMSSample(
+ drift_time = seq(1, 5, by = 1),
+ retention_time = seq(1, 10, by = 1),
+ data = matrix(seq_len(5 * 10), nrow = 5, ncol = 10)
+ )
+ s2 <- GCIMSSample(
+ drift_time = seq(1, 5, by = 1),
+ retention_time = seq(1, 20, by = 2),
+ data = matrix(seq_len(5 * 10), nrow = 5, ncol = 10)
+ )
+ pd <- data.frame(SampleID = c("s1", "s2"), Group = c("A", "B"))
+ GCIMSDataset$new_from_list(samples = list(s1 = s1, s2 = s2), pData = pd, on_ram = TRUE, scratch_dir = NULL)
+}
+
+test_that("getChromatogram(dataset) returns one GCIMSChromatogram per sample, each on its own retention time axis", {
+ ds <- make_dataset_mismatched_axes()
+
+ cs <- getChromatogram(ds, dt_range = c(2, 4))
+
+ expect_s4_class(cs, "GCIMSChromatogramSet")
+ expect_equal(sampleNames(cs), c("s1", "s2"))
+ expect_equal(rtime(cs[["s1"]]), 1:10)
+ expect_equal(rtime(cs[["s2"]]), seq(1, 20, by = 2))
+ expect_equal(unname(intensity(cs[["s1"]])), unname(getChromatogram(ds$getSample("s1"), dt_range = c(2, 4))@intensity))
+})
+
+test_that("getChromatogram(dataset) attaches pData(dataset)", {
+ ds <- make_dataset_mismatched_axes()
+
+ cs <- getChromatogram(ds)
+
+ expect_equal(pData(cs)$SampleID, c("s1", "s2"))
+ expect_equal(pData(cs)$Group, c("A", "B"))
+})
+
+test_that("getSpectrum(dataset) returns one GCIMSSpectrum per sample, each on its own drift time axis", {
+ ds <- make_dataset_mismatched_axes()
+
+ ss <- getSpectrum(ds, rt_range = c(2, 5))
+
+ expect_s4_class(ss, "GCIMSSpectrumSet")
+ expect_equal(sampleNames(ss), c("s1", "s2"))
+ expect_equal(dtime(ss[["s1"]]), 1:5)
+ expect_equal(dtime(ss[["s2"]]), 1:5)
+})
+
+test_that("plot(GCIMSChromatogramSet) colors by SampleID by default and combines mismatched axes without error", {
+ ds <- make_dataset_mismatched_axes()
+ cs <- getChromatogram(ds)
+
+ p <- plot(cs)
+
+ expect_s3_class(p, "ggplot")
+ expect_setequal(as.character(unique(p$data$SampleID)), c("s1", "s2"))
+ expect_equal(range(p$data$retention_time_s[p$data$SampleID == "s2"]), c(1, 19))
+})
+
+test_that("plot(GCIMSChromatogramSet) attributes each line to the right sample even when pData's row order differs from the chromatograms list order", {
+ chroms <- list(
+ s1 = GCIMSChromatogram(retention_time = 1:3, intensity = c(100, 100, 100)),
+ s2 = GCIMSChromatogram(retention_time = 1:3, intensity = c(1, 1, 1))
+ )
+ pd_reversed <- data.frame(SampleID = c("s2", "s1"), Group = c("B", "A"))
+ cs <- GCIMSChromatogramSet(chromatograms = chroms, pData = pd_reversed)
+
+ p <- plot(cs)
+
+ expect_equal(unique(p$data$intensity[p$data$SampleID == "s1"]), 100)
+ expect_equal(unique(p$data$intensity[p$data$SampleID == "s2"]), 1)
+})
+
+test_that("plot(GCIMSChromatogramSet, color_by=) colors by an arbitrary pData column", {
+ ds <- make_dataset_mismatched_axes()
+ cs <- getChromatogram(ds)
+
+ p <- plot(cs, color_by = "Group")
+
+ expect_equal(sort(unique(p$data$Group)), c("A", "B"))
+})
+
+test_that("plot(GCIMSChromatogramSet, color_by=) errors clearly for an unknown column", {
+ ds <- make_dataset_mismatched_axes()
+ cs <- getChromatogram(ds)
+
+ expect_error(plot(cs, color_by = "NotAColumn"), "not a column")
+})
+
+test_that("plot(GCIMSSpectrumSet) colors by SampleID by default", {
+ ds <- make_dataset_mismatched_axes()
+ ss <- getSpectrum(ds)
+
+ p <- plot(ss)
+
+ expect_s3_class(p, "ggplot")
+ expect_setequal(as.character(unique(p$data$SampleID)), c("s1", "s2"))
+})
+
+test_that("plot() errors clearly on an empty set", {
+ cs <- GCIMSChromatogramSet()
+ expect_error(plot(cs), "empty")
+
+ ss <- GCIMSSpectrumSet()
+ expect_error(plot(ss), "empty")
+})
diff --git a/vignettes/introduction-to-gcims.Rmd b/vignettes/introduction-to-gcims.Rmd
index f00a556c..dca72331 100644
--- a/vignettes/introduction-to-gcims.Rmd
+++ b/vignettes/introduction-to-gcims.Rmd
@@ -242,6 +242,22 @@ dataset <- smooth(dataset, rt_length_s = 3, dt_length_ms = 0.14)
dataset$realize()
```
+`getChromatogram()` and `getSpectrum()` also work directly on a `GCIMSDataset`,
+extracting one chromatogram/spectrum per sample at once (each on its own
+retention/drift time axis) together with the dataset's annotations, so you
+don't need to pick a single sample to see the effect of smoothing across the
+whole dataset:
+
+```{r}
+all_chroms <- getChromatogram(dataset, dt_range = 10.4)
+plot(all_chroms)
+```
+
+```{r}
+all_spectra <- getSpectrum(dataset, rt_range = 97.11)
+plot(all_spectra)
+```
+
# Decimation