## ----setup, include=FALSE----------------------------------------------------- knitr::opts_chunk$set( collapse = TRUE, comment = "#>", warning = FALSE, message = FALSE, fig.width = 7.2, fig.height = 3.2, fig.align = "center", dpi = 110 ) library(fibermargin) field_palette <- function(labels) { classes <- sort(unique(as.character(labels[!is.na(labels)]))) stats::setNames(grDevices::hcl.colors(length(classes), "Dark 3"), classes) } plot_field <- function(xy, labels, main = NULL, cex = 0.55) { palette <- field_palette(labels) graphics::plot( xy[, 1L], xy[, 2L], col = palette[as.character(labels)], pch = 16, cex = cex, asp = 1, xlab = colnames(xy)[1L], ylab = colnames(xy)[2L], main = main ) } plot_mask <- function(mask, main = NULL, palette = field_palette(mask)) { values <- matrix( match(as.character(mask), names(palette)), nrow = nrow(mask), ncol = ncol(mask) ) graphics::image( seq_len(nrow(mask)), seq_len(ncol(mask)), values, col = unname(palette), useRaster = TRUE, axes = FALSE, xlab = "", ylab = "", main = main, asp = 1 ) } ## ----api-index---------------------------------------------------------------- api <- data.frame( Function = c( "refine_spatial_labels", "clean_categorical_mask", "corrupt_categorical_mask", "evaluate_mask_cleaning", "spatial_benchmark", "evaluate_spatial_refinement", "benchmark_spatial_refiners", "simulate_spatial_clusters", "simulate_spatial_domains", "simulate_complex_spatial_domains", "simulate_gradient_regions", "simulate_volumetric_domains", "available_spatial_benchmarks", "load_spatial_benchmark" ), Purpose = c( "Refine labels at irregular 2D/3D coordinates", "Clean a categorical matrix or 3D array", "Generate reproducible mask errors", "Score a cleaned mask", "Validate and package a benchmark", "Score coordinate-indexed refinement", "Run identical inputs through multiple methods", "Simulate separated Gaussian-like clusters", "Simulate structured 2D or curved-layer 3D domains", "Simulate held-out complex geometries", "Simulate the A-B-C gradient design", "Simulate variable-density 3D domains", "List real-data availability and licensing", "Load a bundled frozen real-data scenario" ), stringsAsFactors = FALSE ) knitr::kable(api) ## ----quick-start-------------------------------------------------------------- gradient <- simulate_gradient_regions( n = 1200L, minority = 0.10, dimensions = 2L, samples = 2L, density_profile = "moderate", seed = 12L ) refined_gradient <- refine_spatial_labels( xy = gradient$xy, labels = gradient$labels, samples = gradient$samples, workers = 1L ) gradient_metrics <- evaluate_spatial_refinement( truth = gradient$truth, initial = gradient$labels, refined = refined_gradient, boundary = gradient$boundary, regions = gradient$area, sparse = gradient$sparse, method = "FiberMargin" ) gradient_metrics[, c( "method", "initial_accuracy", "accuracy", "ari", "correction_recall", "damage_rate" )] ## ----quick-start-plot, fig.height=3.0----------------------------------------- old_par <- graphics::par(no.readonly = TRUE) graphics::par(mfrow = c(1, 3), mar = c(3, 3, 2, 1)) plot_field(gradient$xy, gradient$truth, "Reference", cex = 0.35) plot_field(gradient$xy, gradient$labels, "Observed", cex = 0.35) plot_field(gradient$xy, refined_gradient, "FiberMargin", cex = 0.35) graphics::par(old_par) ## ----diagnostics-------------------------------------------------------------- diagnostic_names <- c( "candidate", "margin_score", "required", "repair_margin", "atlas_dispersion", "isolation", "changed" ) diagnostics <- data.frame( observed = head(as.character(gradient$labels), 8L), refined = head(as.character(refined_gradient), 8L), candidate = head(as.character(attr(refined_gradient, "candidate")), 8L), margin_score = head(attr(refined_gradient, "margin_score"), 8L), required = head(attr(refined_gradient, "required"), 8L), repair_margin = head(attr(refined_gradient, "repair_margin"), 8L), changed = head(attr(refined_gradient, "changed"), 8L) ) diagnostics setdiff(diagnostic_names, names(attributes(refined_gradient))) ## ----summary-diagnostics------------------------------------------------------ summary_names <- c( "workers", "dimensions_used", "labels_changed", "changed_fraction", "classes_before", "classes_after", "removed_classes", "sample_sizes" ) lapply(summary_names, function(name) attr(refined_gradient, name)) ## ----diagnostic-contract------------------------------------------------------ identical( as.logical(attr(refined_gradient, "changed")), as.character(refined_gradient) != as.character(gradient$labels) ) attr(refined_gradient, "workers") ## ----binary-specialization---------------------------------------------------- binary_data <- simulate_spatial_clusters( n = 700L, dimensions = 2L, k = 2L, samples = 1L, noise = 0.12, seed = 21L ) binary_refined <- refine_spatial_labels( binary_data$xy, binary_data$labels, samples = binary_data$samples, workers = 1L ) c( input_accuracy = mean(binary_data$labels == binary_data$truth), refined_accuracy = mean(binary_refined == binary_data$truth), changed_fraction = mean(binary_refined != binary_data$labels) ) ## ----constant-z--------------------------------------------------------------- flat_2d <- refine_spatial_labels( gradient$xy, gradient$labels, gradient$samples, workers = 1L ) flat_3d <- refine_spatial_labels( cbind(gradient$xy, z = 0), gradient$labels, gradient$samples, workers = 1L ) identical(flat_2d, flat_3d) attr(flat_3d, "dimensions_used") ## ----genuine-3d--------------------------------------------------------------- volume_example <- simulate_volumetric_domains( n = 1800L, shape = "folded_layers", samples = 2L, noise = 0.18, seed = 23L ) volume_refined <- refine_spatial_labels( volume_example$xy, volume_example$labels, volume_example$samples, workers = 2L ) attr(volume_refined, "dimensions_used") ## ----specimen-isolation------------------------------------------------------- source_rows <- which(gradient$samples == levels(gradient$samples)[1L]) overlapping_xy <- rbind( gradient$xy[source_rows, , drop = FALSE], gradient$xy[source_rows, , drop = FALSE] ) overlapping_labels <- factor( rep(as.character(gradient$labels[source_rows]), 2L), levels = levels(gradient$labels) ) overlapping_samples <- rep( c("section_1", "section_2"), each = length(source_rows) ) joint <- refine_spatial_labels( overlapping_xy, overlapping_labels, samples = overlapping_samples, workers = 2L ) separate <- unsplit( lapply(split(seq_len(nrow(overlapping_xy)), overlapping_samples), function(rows) { refine_spatial_labels( overlapping_xy[rows, , drop = FALSE], overlapping_labels[rows], workers = 1L ) }), overlapping_samples ) identical(as.character(joint), as.character(separate)) ## ----worker-determinism, eval=FALSE------------------------------------------- # one_worker <- refine_spatial_labels( # gradient$xy, gradient$labels, gradient$samples, workers = 1L # ) # four_workers <- refine_spatial_labels( # gradient$xy, gradient$labels, gradient$samples, workers = 4L # ) # attr(one_worker, "workers") <- NULL # attr(four_workers, "workers") <- NULL # identical(one_worker, four_workers) ## ----construct-mask----------------------------------------------------------- mask_size <- 48L mask_grid <- expand.grid(row = seq_len(mask_size), column = seq_len(mask_size)) radius <- sqrt( (mask_grid$row - (mask_size + 1) / 2)^2 + (mask_grid$column - (mask_size + 1) / 2)^2 ) reference_mask <- matrix("background", mask_size, mask_size) reference_mask[radius < 17] <- "ring" reference_mask[radius < 10] <- "core" reference_mask[1:4, 1:4] <- NA_character_ mask_corruptions <- lapply( c("impulse", "boundary", "patch"), function(mechanism) { corrupt_categorical_mask( reference_mask, mechanism = mechanism, rate = 0.15, seed = 100L ) } ) names(mask_corruptions) <- c("impulse", "boundary", "patch") vapply( mask_corruptions, function(x) mean(x != reference_mask, na.rm = TRUE), numeric(1L) ) ## ----clean-mask--------------------------------------------------------------- initial_mask <- mask_corruptions$boundary cleaned_mask <- clean_categorical_mask(initial_mask, workers = 1L) c( void_cells_preserved = all(is.na(cleaned_mask) == is.na(initial_mask)), changed_cells = sum(attr(cleaned_mask, "changed"), na.rm = TRUE) ) dim(attr(cleaned_mask, "repair_margin")) ## ----mask-plot, fig.height=2.7------------------------------------------------ mask_colors <- field_palette(reference_mask) old_par <- graphics::par(no.readonly = TRUE) graphics::par(mfrow = c(1, 3), mar = c(1, 1, 2, 1)) plot_mask(reference_mask, "Reference", mask_colors) plot_mask(initial_mask, "Boundary corruption", mask_colors) plot_mask(cleaned_mask, "FiberMargin", mask_colors) graphics::par(old_par) ## ----sampled-mask, eval=FALSE------------------------------------------------- # mask_samples <- matrix( # ifelse(col(reference_mask) <= mask_size / 2, "left", "right"), # nrow = mask_size, ncol = mask_size # ) # cleaned_by_section <- clean_categorical_mask( # initial_mask, samples = mask_samples, workers = 2L # ) ## ----voxel-mask, eval=FALSE--------------------------------------------------- # voxel_reference <- array("A", dim = c(20, 20, 8)) # voxel_reference[, 8:14, ] <- "B" # voxel_reference[, 15:20, ] <- "C" # voxel_initial <- corrupt_categorical_mask( # voxel_reference, mechanism = "boundary", rate = 0.10, seed = 9L # ) # voxel_cleaned <- clean_categorical_mask(voxel_initial, workers = 2L) ## ----evaluate-mask------------------------------------------------------------ mask_metrics <- evaluate_mask_cleaning( reference = reference_mask, initial = initial_mask, cleaned = cleaned_mask, method = "FiberMargin" ) mask_metrics[, c( "method", "initial_accuracy", "accuracy", "ari", "mean_iou", "mean_boundary_iou", "rare_class_iou", "correction_recall", "damage_rate" )] ## ----repair-damage-identity--------------------------------------------------- with( mask_metrics, c( reported = accuracy, decomposed = initial_accuracy * (1 - damage_rate) + (1 - initial_accuracy) * correction_recall, numerical_error = repair_identity_error ) ) ## ----spatial-benchmark-------------------------------------------------------- custom_benchmark <- spatial_benchmark( xy = gradient$xy, labels = gradient$labels, truth = gradient$truth, samples = gradient$samples, boundary = gradient$boundary, regions = gradient$area, sparse = gradient$sparse, name = "two_gradient_tissues", metadata = list(seed = 12L, purpose = "vignette") ) class(custom_benchmark) names(custom_benchmark) ## ----evaluate-spatial--------------------------------------------------------- manual_metrics <- evaluate_spatial_refinement( truth = custom_benchmark$truth, initial = custom_benchmark$labels, refined = refined_gradient, boundary = custom_benchmark$boundary, regions = custom_benchmark$regions, sparse = custom_benchmark$sparse, elapsed = 0, method = "FiberMargin" ) manual_metrics[, c( "accuracy", "accuracy_gain", "ari", "macro_recall", "worst_recall", "boundary_accuracy", "sparse_region_accuracy", "correction_recall", "damage_rate", "changed_precision" )] ## ----benchmark-refiners------------------------------------------------------- jagged_small <- simulate_spatial_domains( n = 600L, pattern = "jagged_stripes", k = 4L, noise = 0.18, noise_type = "boundary", seed = 31L ) comparison <- benchmark_spatial_refiners( data = list( gradient = custom_benchmark, jagged = jagged_small ), methods = list( FiberMargin = function(xy, labels, samples = NULL) { refine_spatial_labels(xy, labels, samples, workers = 1L) }, IdentityExample = function(xy, labels, ...) labels ), include_initial = TRUE, seed = 5L, on_error = "stop" ) comparison[, c( "dataset", "method", "accuracy", "ari", "correction_recall", "damage_rate", "seconds" )] ## ----simulate-clusters-------------------------------------------------------- cluster_sim <- simulate_spatial_clusters( n = 600L, dimensions = 3L, k = 4L, samples = 2L, noise = 0.10, seed = 2L ) c(dimensions = ncol(cluster_sim$xy), classes = nlevels(cluster_sim$truth)) table(cluster_sim$samples) ## ----simulate-domains--------------------------------------------------------- domain_sim <- simulate_spatial_domains( n = 800L, pattern = "thin_layers", k = 5L, noise = 0.20, dimensions = 2L, samples = 2L, noise_type = "boundary", feature_scale = 0.8, density_profile = "strong", seed = 3L ) table(domain_sim$truth) ## ----simulate-complex--------------------------------------------------------- complex_sim <- simulate_complex_spatial_domains( n = 700L, shape = "tubular_network", density_profile = "extreme", noise_type = "patch", noise = 0.20, samples = 1L, k = 5L, seed = 4L ) c(dimensions = ncol(complex_sim$xy), observations = nrow(complex_sim$xy)) ## ----simulate-gradient-custom------------------------------------------------- gradient_3d <- simulate_gradient_regions( n = 700L, minority = 0.05, dimensions = 3L, samples = 2L, seed = 5L, curvature = 0.06, density_profile = c(1, 2, 0.5, 3) ) table(gradient_3d$area, gradient_3d$truth) ## ----simulate-volume---------------------------------------------------------- volume_sim <- simulate_volumetric_domains( n = 700L, shape = "folded_layers", acquisition = "class_imbalanced", noise_type = "boundary", noise = 0.20, k = 5L, samples = 2L, seed = 6L ) c( dimensions = ncol(volume_sim$xy), observations = nrow(volume_sim$xy), samples = nlevels(volume_sim$samples) ) volume_sim$region_counts ## ----simulator-gallery, fig.height=5.8---------------------------------------- old_par <- graphics::par(no.readonly = TRUE) graphics::par(mfrow = c(2, 2), mar = c(3, 3, 2, 1)) plot_field(cluster_sim$xy, cluster_sim$truth, "Spatial clusters", cex = 0.45) plot_field(domain_sim$xy, domain_sim$truth, "Thin layers", cex = 0.45) plot_field(complex_sim$xy, complex_sim$truth, "Tubular network", cex = 0.45) plot_field(volume_sim$xy, volume_sim$truth, "3D folded layers: x-y", cex = 0.45) graphics::par(old_par) ## ----available-benchmarks----------------------------------------------------- benchmark_status <- available_spatial_benchmarks() benchmark_status[c( "dataset", "included", "observations", "classes", "scenarios" )] ## ----load-dlpfc--------------------------------------------------------------- dlpfc_case <- load_spatial_benchmark("dlpfc", scenario = 1L) c( observations = nrow(dlpfc_case$xy), classes = nlevels(dlpfc_case$truth), specimens = nlevels(dlpfc_case$samples), input_accuracy = mean(dlpfc_case$labels == dlpfc_case$truth) ) dlpfc_case$scenario ## ----load-crc, eval=FALSE----------------------------------------------------- # crc_case <- load_spatial_benchmark( # "crc", scenario = "CRC_random_25_r1", seed = 1040001L # ) # crc_refined <- refine_spatial_labels( # crc_case$xy, crc_case$labels, # samples = crc_case$samples, # workers = 4L # ) # evaluate_spatial_refinement( # crc_case$truth, crc_case$labels, crc_refined, # boundary = crc_case$boundary, # sparse = crc_case$sparse, # method = "FiberMargin" # ) ## ----end-to-end-template, eval=FALSE------------------------------------------ # # One row per spatial location. # xy <- as.matrix(my_data[, c("x", "y")]) # labels <- factor(my_data$annotation) # samples <- factor(my_data$section_id) # # # Repair does not receive the reference labels. # refined <- refine_spatial_labels( # xy = xy, # labels = labels, # samples = samples, # workers = 4L # ) # # # Inspect edits before replacing an annotation column. # audit <- data.frame( # observed = labels, # candidate = attr(refined, "candidate"), # refined = refined, # changed = attr(refined, "changed"), # margin_score = attr(refined, "margin_score"), # required = attr(refined, "required"), # repair_margin = attr(refined, "repair_margin"), # isolation = attr(refined, "isolation") # ) # # my_data$fibermargin_annotation <- refined ## ----end-to-end-evaluation, eval=FALSE---------------------------------------- # evaluate_spatial_refinement( # truth = reference_labels, # initial = labels, # refined = refined, # boundary = boundary_locations, # sparse = rare_or_sparse_locations, # method = "FiberMargin" # )