> Status: `draft`
>
> Template class: SOURCE-BACKED WORKFLOW

## Purpose

Integrate a preprocessed, merged, PCA-ready Seurat object with Harmony using one
explicit batch variable. This initial canonical uses the active RunHarmony-style
route.
RPCA and Seurat IntegrateLayers are not included.

## Input contract and parameters

The input object must already have the selected assay normalized, scaled, and
processed with PCA. Harmony is applied to PCA coordinates; it does not replace
QC or normalization.

```{r}
library(Seurat)
library(harmony)
library(lisi)
library(qs2)
library(readr)
library(ggplot2)

input_object <- "input/seurat_object_pca.qs2"
output_object <- "output/seurat_object_harmony.qs2"
output_plot <- "output/harmony_batch.png"
output_lisi <- "output/harmony_lisi.tsv"
output_lisi_plot <- "output/harmony_lisi.png"

assay_name <- "RNA"
pca_reduction <- "pca"
harmony_reduction <- "harmony"
batch_column <- "sample_id"
n_pcs <- 30
max_iter <- 15
cluster_resolution <- 0.5
seed <- 1234
```

## Analysis

```{r}
object <- qs2::qs_read(input_object)
DefaultAssay(object) <- assay_name
stopifnot(batch_column %in% colnames(object[[]]))
stopifnot(pca_reduction %in% Reductions(object))
stopifnot(ncol(Embeddings(object, pca_reduction)) >= n_pcs)

object <- RunHarmony(
  object = object,
  group.by.vars = batch_column,
  reduction = pca_reduction,
  assay.use = assay_name,
  dims.use = seq_len(n_pcs),
  reduction.save = harmony_reduction,
  max_iter = max_iter,
  plot_convergence = TRUE,
  verbose = TRUE
)
object <- FindNeighbors(object, reduction = harmony_reduction, dims = seq_len(n_pcs))
object <- FindClusters(object, resolution = cluster_resolution, random.seed = seed)
object <- RunUMAP(
  object,
  reduction = harmony_reduction,
  dims = seq_len(n_pcs),
  reduction.name = "umap_harmony",
  seed.use = seed
)
```

## Diagnostics

```{r}
dir.create(dirname(output_plot), recursive = TRUE, showWarnings = FALSE)
p_batch <- DimPlot(
  object,
  reduction = "umap_harmony",
  group.by = batch_column
) + ggtitle("Harmony embedding by batch")
ggsave(output_plot, p_batch, width = 7, height = 5, dpi = 150)
print(table(object[[batch_column]][, 1], object$seurat_clusters))

lisi_dimensions <- seq_len(min(n_pcs, ncol(Embeddings(object, harmony_reduction))))
lisi_input <- as.data.frame(Embeddings(object, harmony_reduction)[, lisi_dimensions, drop = FALSE])
lisi_metadata <- data.frame(batch = object[[batch_column]][, 1], row.names = rownames(lisi_input))
lisi_table <- compute_lisi(lisi_input, lisi_metadata, "batch") |>
  as.data.frame()
lisi_table <- cbind(cell_id = rownames(lisi_table), lisi_table)
print(summary(lisi_table$batch))

p_lisi <- ggplot(lisi_table, aes(x = batch)) +
  geom_histogram(bins = 30, fill = "grey80", color = "black") +
  theme_bw() +
  labs(x = "LISI", y = "Cells")
ggsave(output_lisi_plot, p_lisi, width = 7, height = 5, dpi = 150)
```

## Outputs

```{r}
dir.create(dirname(output_object), recursive = TRUE, showWarnings = FALSE)
qs2::qs_save(object, output_object)
write_tsv(lisi_table, output_lisi)
```

## Method notes

The backbone is the complete active log-normalized Harmony route in a project
workflow, with the LISI diagnostic idea from a second integration workflow.
The default expects PCA computed from log-normalized, scaled RNA data. SCT is
not silently substituted; set assay_name explicitly and verify the matching
PCA state if using SCTransform. Inspect markers, known states, and
pre/post-integration diagnostics for overcorrection.

::: {.callout-note}
## Workflow preference

Integration should be justified by a batch-related problem and checked for
overcorrection. In this template Harmony is the explicit log-normalized,
PCA-based route; the source also contains an SCT/IntegrateLayers alternative,
but that scientifically different preprocessing route is not duplicated here.
:::
