---
title: "growkar-introduction"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{growkar-introduction}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r vignette-setup, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
```

```{r setup}
library(growkar)
library(dplyr)
library(knitr)
data(yeast_growth_data)
```

## Background

High-throughput microbial growth assays are widely used to characterize
phenotypic responses to genetic perturbations, environmental conditions, and
drug treatments. These assays generate time-resolved measurements across large
numbers of samples, analogous to other high-throughput experimental platforms in
functional genomics.

While tools exist for transcriptomic and epigenomic data analysis within the
Bioconductor ecosystem, there is a relative lack of standardized infrastructure
for analyzing growth-based phenotypic data and integrating it with omics
datasets.

The `growkar` package addresses this gap by providing a scalable and
reproducible framework for high-throughput microbial phenotyping from growth
assays. It uses Bioconductor data structures to represent assay measurements
and derived phenotypes, while accepting tidy and wide plate-reader exports as
import adapters.

The primary workflow is:

1. Import assay data and build a standardized `SummarizedExperiment`.
2. Validate the SE structure before downstream analysis.
3. Explore growth curves visually.
4. Compute empirical summaries and detect the exponential phase.
5. Fit logistic or Gompertz models where a full-curve summary is useful.

## Class design

`growkar` uses two S4 classes and no S3 classes:

- **`GrowthExperiment`** is the data container. It is an S4 class that extends
  `SummarizedExperiment`, adding a validity method that enforces the canonical
  growth-assay layout (an `od` assay, numeric `time` in `rowData()`). Because
  it *is* a `SummarizedExperiment`, every Bioconductor method works on it
  unchanged, and `as(x, "SummarizedExperiment")` is always available for
  handing objects to other packages.
- **`GrowthFit`** represents a *model result* — a logistic or Gompertz fit for
  one sample. It is not a data container; instances are stored in
  `metadata(ge)` beside the experiment they were derived from.

The package defines no S3 classes and registers no S3 methods.

Build a `GrowthExperiment` with the constructor or with standard S4 coercion:

```{r growth-experiment-construct}
# Constructor (control over column-name resolution):
ge <- GrowthExperiment(yeast_growth_data)
ge

# Equivalent standard coercion:
ge2 <- as(yeast_growth_data, "GrowthExperiment")
identical(ge, ge2)

# It is a SummarizedExperiment, so it hands off cleanly to other packages:
se <- as(ge, "SummarizedExperiment")
is(ge, "SummarizedExperiment")
```

Tidy tables and wide plate-reader exports are handled purely as *import
adapters* by `as_tidy_growth_data()`, which resolves vendor column labels and
normalizes them to `sample`, `time`, and `od` before the container is built.
Once data are in the container, tidy manipulation is delegated to the tidyomics
stack rather than reimplemented — see
[Interoperability with tidyomics](#interoperability-with-tidyomics) below.

## Bioconductor container workflow

Derived summaries are stored in `metadata()` of the same object, so growth
phenotypes travel with the container:

```{r summarized-experiment}
ge <- growth_metrics(ge, method = "rolling_window", average_replicates = TRUE)

S4Vectors::metadata(ge)$growth_metrics
```

This workflow is useful when growth phenotypes need to be carried forward into
other Bioconductor analyses or linked to omics-derived sample annotations.

The canonical layout in `growkar` is:

- `assay(ge, "od")`: OD matrix with timepoints in rows and samples in columns
- `rowData(ge)`: timepoint metadata
- `colData(ge)`: sample metadata
- `metadata(ge)`: derived results such as growth metrics, phase windows, and fits

`validate_growth_experiment()` checks that an object follows this layout:

```{r validate-se}
validate_growth_experiment(ge)
```

Helper accessors make these components easier to inspect:

```{r se-accessors}
growth_assay(ge)[1:3, 1:3]
timepoints(ge)
sample_data(ge)
```

## Importing plate-reader exports

`as_tidy_growth_data()` is the import adapter. It accepts wide plate-reader
exports (time in the first column, samples in the remaining columns) as well as
long tables, resolves common instrument column labels such as `Time [h]` and
`OD600`, and infers `condition`/`replicate` from suffixed sample names.

```{r tidy-conversion}
tidy_growth <- as_tidy_growth_data(yeast_growth_data)

head(tidy_growth)
```

The canonical columns are `sample`, `time`, and `od`. Additional metadata such
as `condition` and `replicate` are carried alongside them.

```{r validate-input}
validate_growth_data(tidy_growth)
```

Imported data are then converted into the canonical container:

```{r summarized-experiment-direct}
GrowthExperiment(yeast_growth_data)
```

## Interoperability with tidyomics

`growkar` does not reimplement tidy verbs or a tidy display layer for
`SummarizedExperiment`. It depends on
[tidySummarizedExperiment](https://bioconductor.org/packages/tidySummarizedExperiment)
and follows its conventions, including the `.feature` and `.sample` labels, so
`growkar` objects can be filtered, mutated, summarized, and plotted with the
standard tidyverse grammar while the underlying object remains a
`SummarizedExperiment` usable by any other Bioconductor package.

```{r tidyomics}
library(tidySummarizedExperiment)

# The SE prints and behaves as a tibble abstraction, without being converted.
se

se |>
  filter(condition == "Cg") |>
  filter(time <= 6) |>
  select(.feature, .sample, od, condition, replicate)

se |>
  group_by(condition) |>
  summarise(max_od = max(od), .groups = "drop")
```

`growkar` results stored in `metadata(se)` are ordinary tibbles, so they slot
directly into the same downstream workflow.

## Exploratory plotting and quality control

Graphing is optional in `growkar`: `ggplot2` is declared in `Suggests`, so the
data-structure and analysis layers install without a graphics stack. The
`plot_*()` functions check for it at call time and return standard `ggplot`
objects that can be customized further. Qualitative palettes come from
`grDevices::palette.colors()` in base R.

```{r plot-averaged-curves, eval = requireNamespace("ggplot2", quietly = TRUE)}
plot_growth_curve(
  se,
  average_replicates = TRUE,
  colour_col = "condition",
  palette_name = "Dark2"
)
```

Replicate-level faceting remains available when averaging is disabled:

```{r plot-replicate-facets, eval = requireNamespace("ggplot2", quietly = TRUE)}
plot_growth_curve(
  se,
  average_replicates = FALSE,
  colour_col = "condition",
  facet_col = "replicate",
  palette_name = "Dark2"
)
```

## Empirical summaries

```{r summarize-metrics}
metrics <- summarize_growth_metrics(
  se,
  method = "rolling_window",
  average_replicates = TRUE
)

knitr::kable(metrics, digits = 3)
```

For a single sample, the empirical growth-rate estimate can be inspected in
more detail:

```{r single-sample-growth-rate}
gr <- compute_growth_rate(se, method = "rolling_window")

knitr::kable(head(gr), digits = 3)
```

The doubling time helper is useful when you already have growth-rate values:

```{r doubling-time-helper}
knitr::kable(
  tibble(
    sample = gr$sample,
    growth_rate = gr$mu,
    doubling_time = compute_doubling_time(gr$mu)
  ),
  digits = 3
)
```

## Exponential phase detection

`detect_exponential_phase()` returns the ranked candidate windows and metadata
describing whether the chosen interval required any degraded fallback.

```{r detect-phase}
phase_tbl <- detect_exponential_phase(se)

knitr::kable(head(phase_tbl), digits = 3)
```

## Model fitting and diagnostics

For a full-curve model-based summary, fit one of the supported parametric
models. `fit_growth_curve()` returns a `GrowthFit` S4 object.

```{r fit-logistic-model}
sample_id <- unique(gr$sample)[1]
fit_input <- as_tidy_growth_data(se) |>
  filter(sample == sample_id)

cg_fit <- fit_growth_curve(fit_input, model = "logistic")

cg_fit
isVirtualClass("GrowthFit")
extract_params(cg_fit)
summary(cg_fit)
```

`GrowthFit` supports the standard modelling generics and a small set of
accessors, so slots never need to be touched directly:

```{r growth-fit-accessors}
fit_sample(cg_fit)
fit_model(cg_fit)
fit_status(cg_fit)
fit_converged(cg_fit)
coef(cg_fit)
head(fitted(cg_fit))
nobs(cg_fit)
```

Failed fits remain machine-readable and do not crash downstream helpers:

```{r failed-fit-example}
flat_fit <- fit_growth_curve(
  tibble(
    sample = "flat",
    time = 0:4,
    od = rep(0.2, 5)
  ),
  model = "logistic"
)

summary(flat_fit)
```

Across a plate, fits are stored in `metadata()` of the same
`SummarizedExperiment`:

```{r fit-plate}
se <- fit_growth_models(se, model = "logistic")

S4Vectors::metadata(se)$growth_model_parameters
```

Observed and fitted values can be visualized together:

```{r plot-fitted-curve, eval = requireNamespace("ggplot2", quietly = TRUE)}
plot_fitted_curve(cg_fit)
```

## Supported API

The supported SE-native interface includes:

- `as_tidy_growth_data()`
- `GrowthExperiment()`
- `validate_growth_experiment()`
- `compute_growth_rate()`
- `summarize_growth_metrics()`
- `detect_exponential_phase()`
- `fit_growth_curve()`

## Limitations and interpretation notes

- Growth-rate estimates rely on log-linear behavior and can be degraded for
  very sparse or flat curves.
- Zero OD values are allowed in the data, but log-based methods may warn and
  ignore them.
- A failed model fit still returns a `GrowthFit` object with status and
  diagnostics rather than throwing a cryptic `nls` error.

## Session information

```{r session-information}
sessionInfo()
```
