From Pathology Whole-Slide Images to MOFSR

What MOFSR accepts

MOFSR does not convert a whole-slide image into features. Its input boundary is a numeric matrix with features in rows and samples in columns. Raw WSI files such as .svs, .ndpi, .scn, and .mrxs must therefore pass through an image-analysis or representation-learning workflow first.

The recommended general-purpose route is QuPath. QuPath uses OpenSlide and Bio-Formats to read many pyramidal WSI formats, but support varies between scanner variants. In particular, some MRXS variants are only partially supported. Validate pixel size, channels, focal planes, and the full-resolution image before analysis; opening a thumbnail is not sufficient. See the official QuPath format notes and OpenSlide format list.

Route A: QuPath measurements

This route is suitable for interpretable cell, tissue, intensity, morphology, texture, and spatial summary features.

  1. Create a QuPath project and import all slides.
  2. Set the image type, pixel calibration, stain vectors, tissue regions, and tumor/stroma annotations consistently across the cohort.
  3. Apply the same detection, classification, and measurement pipeline to every slide. Do not tune the pipeline using outcome labels.
  4. Save the project, then use Measure > Export measurements to export the same columns for all images. QuPath documents the Measurement Exporter as the preferred multi-image export route.
  5. Aggregate cell- or tile-level rows into one feature vector per biological sample. If a patient has multiple slides, map slide IDs to patient IDs before the final aggregation.

The following base-R helper converts a QuPath TSV export into a MOFSR pathology matrix. Replace sample_from_image when slide filenames are not the biological sample identifiers.

qupath_to_mofsr <- function(
    path,
    sample_column = "Image",
    sample_from_image = function(x) tools::file_path_sans_ext(basename(x))) {
  measurements <- utils::read.delim(
    path,
    check.names = FALSE,
    stringsAsFactors = FALSE
  )
  if (!sample_column %in% names(measurements)) {
    stop(sprintf("Missing sample column: %s", sample_column))
  }

  sample_ids <- sample_from_image(measurements[[sample_column]])
  if (anyNA(sample_ids) || any(sample_ids == "")) {
    stop("Every exported row must map to a non-empty sample ID.")
  }

  numeric_columns <- vapply(measurements, is.numeric, logical(1))
  feature_table <- measurements[, numeric_columns, drop = FALSE]
  if (ncol(feature_table) == 0L) {
    stop("No numeric QuPath measurements were found.")
  }

  aggregated <- stats::aggregate(
    feature_table,
    by = list(Sample = sample_ids),
    FUN = function(x) stats::median(x, na.rm = TRUE)
  )
  pathology <- t(as.matrix(aggregated[, -1, drop = FALSE]))
  colnames(pathology) <- aggregated$Sample

  complete_features <- apply(pathology, 1, function(x) all(is.finite(x)))
  pathology <- pathology[complete_features, , drop = FALSE]
  if (nrow(pathology) == 0L) {
    stop("No complete pathology features remain after aggregation.")
  }
  pathology
}

pathology_matrix <- qupath_to_mofsr("qupath_measurements.tsv")

Median aggregation is a robust starting point, not a universal biological choice. Depending on the study, pre-specify additional summaries such as cell density, class proportions, quantiles, or spatial statistics. Feature definitions, magnification, stain normalization, segmentation model, and aggregation rules should remain fixed across the cohort.

Route B: tile or patch embeddings

For deep-learning representations, use QuPath’s TileExporter to export calibrated, tissue-filtered tiles at a fixed physical resolution. Run the tiles through a pathology encoder, then aggregate tile embeddings to one vector per slide or patient. Save the result as the same features × samples matrix used above.

Record at least the encoder name and version, weights, license, magnification or microns-per-pixel, tile size, tissue/tumor filter, normalization, aggregation method, and random seed. Do not mix embeddings from incompatible encoders or preprocessing pipelines in one modality matrix.

Join pathology with other modalities

Every modality must use the same unique biological sample IDs. MOFSR aligns matrices by column name in RunMOFS() and rejects missing or non-finite values.

common_samples <- Reduce(
  intersect,
  list(
    colnames(rna_matrix),
    colnames(protein_matrix),
    colnames(pathology_matrix)
  )
)
if (length(common_samples) < 3L) {
  stop("Too few matched samples remain for clustering.")
}

data_list <- list(
  RNA = rna_matrix[, common_samples, drop = FALSE],
  Protein = protein_matrix[, common_samples, drop = FALSE],
  Pathology = pathology_matrix[, common_samples, drop = FALSE]
)

stopifnot(
  check_sample_alignment(data_list, strict = TRUE),
  all(vapply(data_list, function(x) all(is.finite(x)), logical(1)))
)

result <- RunMOFS(
  data = data_list,
  methods = c("CPCA", "CIMLR"),
  max.clusters = 6,
  optimal.clusters = 3
)

Minimum quality-control record

  • One immutable mapping from WSI file to slide, patient, and analysis sample ID.
  • Scanner, stain, pixel calibration, image type, and any format-reading caveats.
  • Tissue/tumor selection rule and failed-slide exclusions.
  • Feature or encoder version and exact aggregation rule.
  • Missingness and outlier checks performed before fusion.
  • Confirmation that feature extraction and preprocessing did not use outcome or final cluster labels.