From 4ea0dcdea7245a74209b0e0ebcfa1acfd255763e Mon Sep 17 00:00:00 2001 From: jn Date: Sat, 7 Feb 2026 18:58:39 +0100 Subject: [PATCH 01/11] metrics allow to use proper units --- R/sc_metrics_global.R | 11 +++++++++-- man/sc_metrics_global.Rd | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/R/sc_metrics_global.R b/R/sc_metrics_global.R index 6eeae86..e6322b5 100644 --- a/R/sc_metrics_global.R +++ b/R/sc_metrics_global.R @@ -28,7 +28,9 @@ #' values indicate spatial dominance; positive values indicate value dominance.} #' } #' \describe{ -#' \item{step}{Step size used to generate supercells.} +#' \item{step}{Step size used to generate supercells. Returned in map units when +#' `step_unit = "map"`, otherwise in cells.} +#' \item{step_unit}{Units for `step` ("cells" or "map").} #' \item{compactness}{Compactness value used to generate supercells.} #' \item{n_supercells}{Number of supercells with at least one non-missing pixel.} #' \item{mean_value_dist}{Mean per-supercell value distance from cells to their @@ -102,8 +104,13 @@ sc_metrics_global = function(x, sc, balance = "balance" ) names(out_metrics) = unname(name_map[metrics]) + step_out = prep$step + if (identical(prep$step_unit, "map")) { + step_out = prep$step * prep$spatial_scale + } results = cbind( - data.frame(step = prep$step, compactness = prep$compactness, n_supercells = out[["n_supercells"]]), + data.frame(step = step_out, step_unit = prep$step_unit, + compactness = prep$compactness, n_supercells = out[["n_supercells"]]), out_metrics ) return(results) diff --git a/man/sc_metrics_global.Rd b/man/sc_metrics_global.Rd index ba737e4..b6c12ff 100644 --- a/man/sc_metrics_global.Rd +++ b/man/sc_metrics_global.Rd @@ -46,7 +46,9 @@ Interpretation: values indicate spatial dominance; positive values indicate value dominance.} } \describe{ -\item{step}{Step size used to generate supercells.} +\item{step}{Step size used to generate supercells. Returned in map units when +\code{step_unit = "map"}, otherwise in cells.} +\item{step_unit}{Units for \code{step} ("cells" or "map").} \item{compactness}{Compactness value used to generate supercells.} \item{n_supercells}{Number of supercells with at least one non-missing pixel.} \item{mean_value_dist}{Mean per-supercell value distance from cells to their From 490ba12e328e1fdd7eb65f0cf2564b92c7cb938e Mon Sep 17 00:00:00 2001 From: jn Date: Sat, 7 Feb 2026 20:26:15 +0100 Subject: [PATCH 02/11] improves class assigment --- R/helpers-sc_slic.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/helpers-sc_slic.R b/R/helpers-sc_slic.R index 7734e15..b32fb05 100644 --- a/R/helpers-sc_slic.R +++ b/R/helpers-sc_slic.R @@ -221,7 +221,7 @@ attr(slic_sf, "compactness") = prep$compactness attr(slic_sf, "dist_fun") = prep$dist_fun_input attr(slic_sf, "method") = if (isTRUE(prep$adaptive_compactness)) "slic0" else "slic" - class(slic_sf) = c(class(slic_sf), "supercells") + class(slic_sf) = unique(c("supercells", class(slic_sf))) if (!is.null(iter_attr)) { attr(slic_sf, "iter_diagnostics") = iter_attr } From a821ca699d4298561eaf97b55d6e169f669b9dc6 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 15:07:36 +0100 Subject: [PATCH 03/11] adds in_meters and removes step_units --- DESCRIPTION | 1 + NAMESPACE | 1 + R/helpers-chunks.R | 2 +- R/helpers-general.R | 36 +++++++++++++++ R/helpers-metrics.R | 52 +++++++++++++++------- R/helpers-sc_slic.R | 49 ++++++++++---------- R/in_meters.R | 19 ++++++++ R/sc_metrics_global.R | 38 +++++----------- R/sc_metrics_pixels.R | 28 ++++-------- R/sc_metrics_supercells.R | 25 +++-------- R/sc_slic.R | 12 ++--- R/sc_slic_points.R | 4 +- R/sc_slic_raster.R | 6 +-- R/sc_tune_compactness.R | 47 +++++++++---------- R/supercells.R | 22 +++------ man/in_meters.Rd | 22 +++++++++ man/sc_metrics_global.Rd | 7 ++- man/sc_metrics_pixels.Rd | 2 +- man/sc_metrics_supercells.Rd | 2 +- man/sc_slic.Rd | 10 ++--- man/sc_slic_points.Rd | 8 ++-- man/sc_slic_raster.Rd | 8 ++-- man/sc_tune_compactness.Rd | 9 ++-- man/supercells.Rd | 4 +- tests/testthat/test-sc-create.R | 43 ++++++++++++++++++ tests/testthat/test-sc-metrics.R | 8 ++-- vignettes/articles/v2-changes-since-v1.Rmd | 2 +- vignettes/articles/v2-evaluation.Rmd | 2 +- vignettes/articles/v2-parameters.Rmd | 10 ++--- 29 files changed, 283 insertions(+), 196 deletions(-) create mode 100644 R/in_meters.R create mode 100644 man/in_meters.Rd diff --git a/DESCRIPTION b/DESCRIPTION index d0aeb02..d0c5dbb 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,6 +22,7 @@ Imports: sf, terra (>= 1.4-21), philentropy (>= 0.6.0), + units Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.3 LinkingTo: diff --git a/NAMESPACE b/NAMESPACE index c29ed0f..f53ed7a 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(in_meters) export(sc_metrics_global) export(sc_metrics_pixels) export(sc_metrics_supercells) diff --git a/R/helpers-chunks.R b/R/helpers-chunks.R index cc8b599..8fc1461 100644 --- a/R/helpers-chunks.R +++ b/R/helpers-chunks.R @@ -28,7 +28,7 @@ r = r + max_id } n_centers = nrow(chunks[[i]][["centers"]]) - if (!is.na(n_centers) && n_centers > 0) { + if (n_centers > 0) { max_id = max_id + n_centers } rasters[[i]] = r diff --git a/R/helpers-general.R b/R/helpers-general.R index 0b71fb8..a54d126 100644 --- a/R/helpers-general.R +++ b/R/helpers-general.R @@ -59,3 +59,39 @@ return(vec) } } + +# convert user step to internal cell units and return scaling metadata +.sc_util_step_to_cells = function(x, step) { + if (!inherits(step, "units")) { + return(list(step = step, step_meta = step, spatial_scale = 1, step_scale = step)) + } + + if (!identical(as.character(units::deparse_unit(step)), "m")) { + stop("A units-based 'step' must use meters ('m')", call. = FALSE) + } + if (terra::is.lonlat(x)) { + stop("A units-based 'step' requires a projected CRS; project the input raster first", call. = FALSE) + } + + res = terra::res(x) + if (!isTRUE(all.equal(res[[1]], res[[2]]))) { + warning("Map-unit step requires square cells; res(x) has different x/y resolution", call. = FALSE) + } + + crs_units = sf::st_crs(terra::crs(x))$units_gdal + if (is.null(crs_units) || is.na(crs_units) || !nzchar(crs_units)) { + stop("The raster CRS has unknown linear units; cannot use units-based 'step'", call. = FALSE) + } + crs_units_lc = tolower(trimws(crs_units)) + if (!(crs_units_lc %in% c("meter", "metre", "m"))) { + stop("A units-based 'step' requires a projected CRS with meter units", call. = FALSE) + } + + step_m = as.numeric(units::drop_units(step)) + step_cells = max(1, round(step_m / res[[1]])) + step_meta = units::set_units(step_cells * res[[1]], "m", mode = "standard") + spatial_scale = res[[1]] + + list(step = step_cells, step_meta = step_meta, + spatial_scale = spatial_scale, step_scale = step_cells * spatial_scale) +} diff --git a/R/helpers-metrics.R b/R/helpers-metrics.R index c4419ab..57e7e12 100644 --- a/R/helpers-metrics.R +++ b/R/helpers-metrics.R @@ -1,6 +1,38 @@ # .sc_metrics_prep: normalize inputs and parameters for metrics functions # Inputs: raster and supercells; outputs include prepared matrices and metadata # Handles missing metadata by deriving centers and ids from geometry + +.sc_metrics_resolve_dist_fun = function(sc, dist_fun) { + if (!is.null(dist_fun)) { + return(dist_fun) + } + dist_fun = attr(sc, "dist_fun") + if (is.null(dist_fun)) { + stop("The 'dist_fun' argument is required when it is not stored in 'sc'", call. = FALSE) + } + dist_fun +} + +.sc_metrics_validate_names = function(metrics) { + allowed = c("spatial", "value", "combined", "balance") + if (any(!metrics %in% allowed)) { + stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) + } +} + +.sc_metrics_scale_summary = function(value_dist, spatial_dist, out, prep, scale) { + if (!isTRUE(scale)) { + return(list(value_dist = value_dist, spatial_dist = spatial_dist)) + } + if (isTRUE(prep$adaptive_compactness)) { + value_dist = out[["mean_value_dist_scaled"]] + } else { + value_dist = value_dist / prep$compactness + } + spatial_dist = spatial_dist / prep$step_scale + list(value_dist = value_dist, spatial_dist = spatial_dist) +} + .sc_metrics_prep = function(x, sc, dist_fun, compactness, step, include = c("clusters", "centers", "vals", "dist", "raster")) { @@ -30,10 +62,8 @@ if (is.null(compactness) || is.null(step)) { stop("Both 'compactness' and 'step' are required", call. = FALSE) } - step_unit = attr(sc, "step_unit") - if (!identical(step_unit, "map")) { - step_unit = "cells" - } + step_prep = .sc_util_step_to_cells(raster, step) + step = step_prep$step # prepare data, including handling missing metadata sc_work = sc @@ -58,23 +88,15 @@ x_df = x_df[order(x_df[["supercells"]]), , drop = FALSE] # prepare matrices for C++ function - spatial_scale = 1 - step_scale = step - if (identical(step_unit, "map")) { - res = terra::res(raster) - if (!isTRUE(all.equal(res[[1]], res[[2]]))) { - warning("Map-unit spatial metrics require square cells; using x resolution for scaling.", call. = FALSE) - } - spatial_scale = res[[1]] - step_scale = step * spatial_scale - } + spatial_scale = step_prep$spatial_scale + step_scale = step_prep$step_scale result = list( sc = sc_work, step = step, + step_meta = step_prep$step_meta, compactness = compactness, adaptive_compactness = adaptive_compactness, - step_unit = step_unit, spatial_scale = spatial_scale, step_scale = step_scale ) diff --git a/R/helpers-sc_slic.R b/R/helpers-sc_slic.R index b32fb05..db95124 100644 --- a/R/helpers-sc_slic.R +++ b/R/helpers-sc_slic.R @@ -1,10 +1,10 @@ # shared helpers for sc_slic workflows # prepare and validate slic arguments -.sc_slic_prep_args = function(x, step, step_unit, compactness, dist_fun, avg_fun, clean, minarea, iter, +.sc_slic_prep_args = function(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, k, centers, outcomes, chunks, iter_diagnostics, verbose) { # Validate core arguments and types - .sc_slic_validate_args(step, step_unit, compactness, k, centers, chunks, dist_fun, avg_fun, iter, minarea) + .sc_slic_validate_args(step, compactness, k, centers, chunks, dist_fun, avg_fun, iter, minarea) outcomes = .sc_slic_prep_outcomes(outcomes) # Normalize input to SpatRaster x = .sc_util_prep_raster(x) @@ -12,7 +12,9 @@ warning("The input raster uses a geographic (lon/lat) CRS; consider projecting it before using SLIC", call. = FALSE) } # Resolve step from k when needed - step = .sc_slic_prep_step(x, step, k, step_unit) + step_prep = .sc_slic_prep_step(x, step, k) + step = step_prep$step + step_meta = step_prep$step_meta # Adjust numeric chunks to match step size if (is.numeric(chunks)) { if (chunks < step) { @@ -44,7 +46,7 @@ compactness = 0 } # Package prep results for downstream functions - return(list(x = x, step = step, step_unit = step_unit, + return(list(x = x, step = step, step_meta = step_meta, dist_fun_input = dist_fun, input_centers = input_centers, funs = funs, minarea = minarea, chunk_ext = chunk_ext, @@ -55,12 +57,16 @@ } # validate slic arguments and types -.sc_slic_validate_args = function(step, step_unit, compactness, k, centers, chunks, dist_fun, avg_fun, iter, minarea) { - if (!missing(step_unit)) { - if (!is.character(step_unit) || length(step_unit) != 1 || is.na(step_unit) || - !(step_unit %in% c("cells", "map"))) { - stop("The 'step_unit' argument must be 'cells' or 'map'", call. = FALSE) +.sc_slic_validate_args = function(step, compactness, k, centers, chunks, dist_fun, avg_fun, iter, minarea) { + if (!is.null(step) && !is.numeric(step)) { + stop("The 'step' argument must be numeric or a units object", call. = FALSE) + } + if (inherits(step, "units")) { + if (length(step) != 1 || any(is.na(step))) { + stop("The 'step' argument as units must be a single non-missing value", call. = FALSE) } + } else if (!is.null(step) && (length(step) != 1 || is.na(step) || step <= 0)) { + stop("The 'step' argument must be a positive numeric value", call. = FALSE) } if (!is.numeric(iter) || length(iter) != 1 || is.na(iter) || iter < 0) { stop("The 'iter' argument must be a non-negative numeric value", call. = FALSE) @@ -113,23 +119,15 @@ } # derive step from k when needed -.sc_slic_prep_step = function(x, step, k, step_unit = "cells") { +.sc_slic_prep_step = function(x, step, k) { if (!is.null(step)) { - if (step_unit == "map") { - res = terra::res(x) - if (!isTRUE(all.equal(res[[1]], res[[2]]))) { - stop("Map-unit step requires square cells; res(x) has different x/y resolution", call. = FALSE) - } - step = round(step / res[[1]]) - if (step < 1) { - step = 1 - } - } - return(step) + step_prep = .sc_util_step_to_cells(x, step) + return(list(step = step_prep$step, step_meta = step_prep$step_meta)) } mat = dim(x)[1:2] superpixelsize = round((mat[1] * mat[2]) / k + 0.5) - return(round(sqrt(superpixelsize) + 0.5)) + step = round(sqrt(superpixelsize) + 0.5) + return(list(step = step, step_meta = step)) } # normalize custom centers or create placeholder @@ -216,12 +214,13 @@ slic_sf = .sc_slic_select_outcomes(slic_sf, prep$outcomes) - attr(slic_sf, "step") = prep$step - attr(slic_sf, "step_unit") = prep$step_unit + attr(slic_sf, "step") = prep$step_meta attr(slic_sf, "compactness") = prep$compactness attr(slic_sf, "dist_fun") = prep$dist_fun_input attr(slic_sf, "method") = if (isTRUE(prep$adaptive_compactness)) "slic0" else "slic" - class(slic_sf) = unique(c("supercells", class(slic_sf))) + cls = class(slic_sf) + cls = c(setdiff(cls, "data.frame"), "supercells", "data.frame") + class(slic_sf) = unique(cls) if (!is.null(iter_attr)) { attr(slic_sf, "iter_diagnostics") = iter_attr } diff --git a/R/in_meters.R b/R/in_meters.R new file mode 100644 index 0000000..6849596 --- /dev/null +++ b/R/in_meters.R @@ -0,0 +1,19 @@ +#' Mark step values as meters +#' +#' Creates a units value in meters for use in `step` arguments. +#' Use plain numerics for cell units, and `in_meters()` for map-distance steps. +#' +#' @param x A single positive numeric value. +#' +#' @return A [units::units] object in meters (`m`). +#' +#' @examples +#' in_meters(100) +#' +#' @export +in_meters = function(x) { + if (!is.numeric(x) || length(x) != 1 || is.na(x) || x <= 0) { + stop("The 'x' argument must be a single positive number", call. = FALSE) + } + units::set_units(as.numeric(x), "m") +} diff --git a/R/sc_metrics_global.R b/R/sc_metrics_global.R index e6322b5..68465c5 100644 --- a/R/sc_metrics_global.R +++ b/R/sc_metrics_global.R @@ -28,9 +28,8 @@ #' values indicate spatial dominance; positive values indicate value dominance.} #' } #' \describe{ -#' \item{step}{Step size used to generate supercells. Returned in map units when -#' `step_unit = "map"`, otherwise in cells.} -#' \item{step_unit}{Units for `step` ("cells" or "map").} +#' \item{step}{Step size used to generate supercells. Returned in meters when +#' the input used `step = in_meters(...)`, otherwise in cells.} #' \item{compactness}{Compactness value used to generate supercells.} #' \item{n_supercells}{Number of supercells with at least one non-missing pixel.} #' \item{mean_value_dist}{Mean per-supercell value distance from cells to their @@ -39,7 +38,7 @@ #' \item{mean_spatial_dist}{Mean per-supercell spatial distance from cells to #' their supercell centers, averaged across supercells; units are grid cells #' (row/column index distance). If the input supercells were created with -#' `step_unit = "map"`, distances are reported in map units. Returned as +#' `step = in_meters(...)`, distances are reported in meters. Returned as #' `mean_spatial_dist` (or `mean_spatial_dist_scaled` when `scale = TRUE`).} #' \item{mean_combined_dist}{Mean per-supercell combined distance, computed from #' value and spatial distances using `compactness` and `step`, averaged across @@ -60,15 +59,8 @@ sc_metrics_global = function(x, sc, metrics = c("spatial", "value", "combined", "balance"), scale = TRUE, step, compactness, dist_fun = NULL) { - if (missing(dist_fun) || is.null(dist_fun)) { - dist_fun = attr(sc, "dist_fun") - if (is.null(dist_fun)) { - stop("The 'dist_fun' argument is required when it is not stored in 'sc'", call. = FALSE) - } - } - if (any(!metrics %in% c("spatial", "value", "combined", "balance"))) { - stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) - } + dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) + .sc_metrics_validate_names(metrics) prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) out = sc_metrics_global_cpp(prep$clusters, prep$centers_xy, prep$centers_vals, prep$vals, @@ -80,15 +72,9 @@ sc_metrics_global = function(x, sc, mean_spatial_dist = out[["mean_spatial_dist"]] * prep$spatial_scale mean_combined_dist = out[["mean_combined_dist"]] balance = out[["balance"]] - - if (isTRUE(scale)) { - if (isTRUE(prep$adaptive_compactness)) { - mean_value_dist = out[["mean_value_dist_scaled"]] - } else { - mean_value_dist = mean_value_dist / prep$compactness - } - mean_spatial_dist = mean_spatial_dist / prep$step_scale - } + scaled = .sc_metrics_scale_summary(mean_value_dist, mean_spatial_dist, out, prep, scale) + mean_value_dist = scaled$value_dist + mean_spatial_dist = scaled$spatial_dist metric_values = list( spatial = mean_spatial_dist, @@ -104,13 +90,9 @@ sc_metrics_global = function(x, sc, balance = "balance" ) names(out_metrics) = unname(name_map[metrics]) - step_out = prep$step - if (identical(prep$step_unit, "map")) { - step_out = prep$step * prep$spatial_scale - } + step_out = prep$step_meta results = cbind( - data.frame(step = step_out, step_unit = prep$step_unit, - compactness = prep$compactness, n_supercells = out[["n_supercells"]]), + data.frame(step = step_out, compactness = prep$compactness, n_supercells = out[["n_supercells"]]), out_metrics ) return(results) diff --git a/R/sc_metrics_pixels.R b/R/sc_metrics_pixels.R index 4594f12..5685afa 100644 --- a/R/sc_metrics_pixels.R +++ b/R/sc_metrics_pixels.R @@ -36,7 +36,7 @@ #' \describe{ #' \item{spatial}{Spatial distance from each pixel to its supercell center #' in grid-cell units (row/column index distance). If the input supercells were -#' created with `step_unit = "map"`, distances are reported in map units.} +#' created with `step = in_meters(...)`, distances are reported in meters.} #' \item{value}{Value distance from each pixel to its supercell center in #' the raster value space.} #' \item{combined}{Combined distance using `compactness` and `step`.} @@ -57,17 +57,9 @@ sc_metrics_pixels = function(x, sc, metrics = c("spatial", "value", "combined", "balance"), scale = TRUE, step, compactness, dist_fun = NULL) { - if (missing(dist_fun) || is.null(dist_fun)) { - dist_fun = attr(sc, "dist_fun") - if (is.null(dist_fun)) { - stop("The 'dist_fun' argument is required when it is not stored in 'sc'", call. = FALSE) - } - } - - if (any(!metrics %in% c("spatial", "value", "combined", "balance"))) { - stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) - } - + dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) + .sc_metrics_validate_names(metrics) + prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) out = sc_metrics_pixels_cpp(prep$clusters, prep$centers_xy, prep$centers_vals, prep$vals, @@ -89,14 +81,12 @@ sc_metrics_pixels = function(x, sc, if ("balance" %in% metrics) { balance = log(value_scaled / spatial_scaled) } - } - + } + result = c(spatial, value, combined) + names(result) = c("spatial", "value", "combined") if ("balance" %in% metrics) { - result = c(spatial, value, combined, balance) - names(result) = c("spatial", "value", "combined", "balance") - } else { - result = c(spatial, value, combined) - names(result) = c("spatial", "value", "combined") + result = c(result, balance) + names(result)[4] = "balance" } result = result[[metrics]] diff --git a/R/sc_metrics_supercells.R b/R/sc_metrics_supercells.R index d04a3b9..cdcbf70 100644 --- a/R/sc_metrics_supercells.R +++ b/R/sc_metrics_supercells.R @@ -28,7 +28,7 @@ #' \item{supercells}{Supercell ID.} #' \item{spatial}{Mean spatial distance from cells to the supercell center #' in grid-cell units (row/column index distance). If the input supercells were -#' created with `step_unit = "map"`, distances are reported in map units. +#' created with `step = in_meters(...)`, distances are reported in meters. #' Returned as `mean_spatial_dist` (or `mean_spatial_dist_scaled` when `scale = TRUE`).} #' \item{value}{Mean value distance from cells to the supercell center in #' value space. Returned as `mean_value_dist` (or `mean_value_dist_scaled` @@ -50,15 +50,8 @@ sc_metrics_supercells = function(x, sc, metrics = c("spatial", "value", "combined", "balance"), scale = TRUE, step, compactness, dist_fun = NULL) { - if (missing(dist_fun) || is.null(dist_fun)) { - dist_fun = attr(sc, "dist_fun") - if (is.null(dist_fun)) { - stop("The 'dist_fun' argument is required when it is not stored in 'sc'", call. = FALSE) - } - } - if (any(!metrics %in% c("spatial", "value", "combined", "balance"))) { - stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) - } + dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) + .sc_metrics_validate_names(metrics) prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) x_df = sf::st_drop_geometry(prep$sc) @@ -73,15 +66,9 @@ sc_metrics_supercells = function(x, sc, mean_spatial_dist = out[["mean_spatial_dist"]] * prep$spatial_scale mean_combined_dist = out[["mean_combined_dist"]] balance = log(out[["balance"]]) - - if (isTRUE(scale)) { - if (isTRUE(prep$adaptive_compactness)) { - mean_value_dist = out[["mean_value_dist_scaled"]] - } else { - mean_value_dist = mean_value_dist / prep$compactness - } - mean_spatial_dist = mean_spatial_dist / prep$step_scale - } + scaled = .sc_metrics_scale_summary(mean_value_dist, mean_spatial_dist, out, prep, scale) + mean_value_dist = scaled$value_dist + mean_spatial_dist = scaled$spatial_dist x_ordered = prep$sc[order_idx, , drop = FALSE] x_keep = x_ordered[, "supercells", drop = FALSE] diff --git a/R/sc_slic.R b/R/sc_slic.R index c5ebeb7..876b53c 100644 --- a/R/sc_slic.R +++ b/R/sc_slic.R @@ -17,11 +17,13 @@ #' \item Cluster diagnostics: [sc_metrics_supercells()] for per-supercell summaries. #' \item Global diagnostics: [sc_metrics_global()] for a single-row summary. #' } -#' @seealso [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_plot_iter_diagnostics()`], +#' @seealso [in_meters()], [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_plot_iter_diagnostics()`], #' [`sc_metrics_pixels()`], [`sc_metrics_supercells()`], [`sc_metrics_global()`] #' #' @param x An object of class SpatRaster (terra) or class stars (stars). -#' @param step The distance (number of cells) between initial centers (alternative is `k`). +#' @param step Initial center spacing (alternative is `k`). +#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param compactness A compactness value. Use [`sc_tune_compactness()`] to estimate it. #' Set `compactness = "auto"` to enable adaptive compactness (SLIC0). #' @param dist_fun A distance function name or a custom function. Supported names: @@ -33,8 +35,6 @@ #' @param clean Should connectivity of the supercells be enforced? #' @param minarea Minimal size of a supercell (in cells). #' @param iter Number of iterations. -#' @param step_unit Units for `step`. Use "cells" for pixel units or "map" for map units -#' (converted to cells using raster resolution). #' @param k The number of supercells desired (alternative to `step`). #' @param centers Optional sf object of custom centers. Requires `step`. #' @param outcomes Character vector controlling which fields are returned. @@ -64,14 +64,14 @@ #' plot(sf::st_geometry(vol_slic1), add = TRUE, lwd = 0.2) sc_slic = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, + k = NULL, centers = NULL, outcomes = "values", chunks = FALSE, iter_diagnostics = FALSE, verbose = 0) { if (iter == 0) { stop("iter = 0 returns centers only; polygon output is not available. Use sc_slic_points(iter = 0) to get initial centers.", call. = FALSE) } - prep_args = .sc_slic_prep_args(x, step, step_unit, compactness, dist_fun, avg_fun, clean, minarea, iter, + prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, k, centers, outcomes, chunks, iter_diagnostics, verbose) segment = .sc_slic_segment(prep_args, .sc_run_full_polygons, .sc_run_chunk_polygons) diff --git a/R/sc_slic_points.R b/R/sc_slic_points.R index 9aaa1e0..272f2b7 100644 --- a/R/sc_slic_points.R +++ b/R/sc_slic_points.R @@ -27,13 +27,13 @@ #' plot(sf::st_geometry(vol_pts), add = TRUE, pch = 16, col = "red") sc_slic_points = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, + k = NULL, centers = NULL, outcomes = "values", chunks = FALSE, iter_diagnostics = FALSE, verbose = 0) { if (iter == 0) { clean = FALSE } - prep_args = .sc_slic_prep_args(x, step, step_unit, compactness, dist_fun, avg_fun, clean, minarea, iter, + prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, k, centers, outcomes, chunks, iter_diagnostics, verbose) segment = .sc_slic_segment(prep_args, .sc_run_full_points, .sc_run_chunk_points) diff --git a/R/sc_slic_raster.R b/R/sc_slic_raster.R index 4925ff5..e567dbd 100644 --- a/R/sc_slic_raster.R +++ b/R/sc_slic_raster.R @@ -20,7 +20,7 @@ #' terra::plot(vol_ids) sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, + k = NULL, centers = NULL, outcomes = "supercells", chunks = FALSE, iter_diagnostics = FALSE, verbose = 0) { @@ -31,7 +31,7 @@ sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", stop("sc_slic_raster() supports only outcomes = 'supercells'", call. = FALSE) } # prep arguments - prep_args = .sc_slic_prep_args(x, step, step_unit, compactness, dist_fun, avg_fun, clean, minarea, iter, + prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, k, centers, outcomes, chunks, iter_diagnostics, verbose) # segment once (single) or per chunk (chunked), returning a list of chunk results @@ -61,7 +61,7 @@ sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", r = r + max_id } n_centers = nrow(res[["centers"]]) - if (!is.na(n_centers) && n_centers > 0) { + if (n_centers > 0) { max_id = max_id + n_centers } chunk_files[i] = tempfile(fileext = ".tif") diff --git a/R/sc_tune_compactness.R b/R/sc_tune_compactness.R index acc22d7..b8d36dd 100644 --- a/R/sc_tune_compactness.R +++ b/R/sc_tune_compactness.R @@ -7,8 +7,9 @@ #' while the local estimate uses a median of per-center mean distances. #' #' @param raster A `SpatRaster`. -#' @param step The distance (number of cells) between initial centers (alternative is `k`). -#' @param step_unit Units for `step`. Use "cells" for pixel units or "map" for map units. +#' @param step Initial center spacing (alternative is `k`). +#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param compactness Starting compactness used for the initial short run. #' @param metrics Which compactness metric to return: `"global"` or `"local"`. #' Default: `"global"`. @@ -31,7 +32,7 @@ #' #' @return A one-row data frame with columns `step`, `metric`, and `compactness`. #' -#' @seealso [`sc_slic()`] +#' @seealso [`sc_slic()`], [in_meters()] #' #' @examples #' library(terra) @@ -40,7 +41,7 @@ #' tune$compactness #' #' @export -sc_tune_compactness = function(raster, step = NULL, step_unit = "cells", compactness = 1, +sc_tune_compactness = function(raster, step = NULL, compactness = 1, metrics = "global", dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 1, @@ -49,7 +50,7 @@ sc_tune_compactness = function(raster, step = NULL, step_unit = "cells", compact pts = sc_slic_points(raster, step = step, compactness = compactness, dist_fun = dist_fun, avg_fun = avg_fun, clean = clean, minarea = minarea, iter = iter, - step_unit = step_unit, k = k, centers = centers, + k = k, centers = centers, outcomes = c("supercells", "coordinates", "values"), chunks = FALSE) @@ -57,6 +58,10 @@ sc_tune_compactness = function(raster, step = NULL, step_unit = "cells", compact if (is.null(step_used)) { step_used = step } + step_used_num = step_used + if (inherits(step_used_num, "units")) { + step_used_num = as.numeric(units::drop_units(step_used_num)) + } if (!is.character(metrics) || length(metrics) != 1 || is.na(metrics) || !(metrics %in% c("global", "local"))) { @@ -79,26 +84,22 @@ sc_tune_compactness = function(raster, step = NULL, step_unit = "cells", compact spatial_dist_median = med[1, 1] value_dist_median = med[2, 1] value_dist_median = value_dist_median / value_scale - compactness_value = value_dist_median * step_used / spatial_dist_median + compactness_value = value_dist_median * step_used_num / spatial_dist_median - result = data.frame(step = step_used, metric = "global", compactness = compactness_value) + return(data.frame(step = step_used, metric = "global", compactness = compactness_value)) } - if (identical(metrics, "local")) { - prep = .sc_metrics_prep(raster, pts, dist_fun, compactness, step_used, - include = c("centers", "vals", "dist", "raster")) - mean_value_dist = sc_metrics_local_mean_cpp( - prep$centers_xy, prep$centers_vals, prep$vals, - rows = dim(prep$raster)[1], cols = dim(prep$raster)[2], - step = step_used, - dist_name = prep$dist_name, - dist_fun = prep$dist_fun - ) - mean_value_dist = mean_value_dist / value_scale - compactness_value = stats::median(mean_value_dist, na.rm = TRUE) - - result = data.frame(step = step_used, metric = "local", compactness = compactness_value) - } + prep = .sc_metrics_prep(raster, pts, dist_fun, compactness, step_used, + include = c("centers", "vals", "dist", "raster")) + mean_value_dist = sc_metrics_local_mean_cpp( + prep$centers_xy, prep$centers_vals, prep$vals, + rows = dim(prep$raster)[1], cols = dim(prep$raster)[2], + step = prep$step, + dist_name = prep$dist_name, + dist_fun = prep$dist_fun + ) + mean_value_dist = mean_value_dist / value_scale + compactness_value = stats::median(mean_value_dist, na.rm = TRUE) - return(result) + return(data.frame(step = step_used, metric = "local", compactness = compactness_value)) } diff --git a/R/supercells.R b/R/supercells.R index 41d49aa..1d601ff 100644 --- a/R/supercells.R +++ b/R/supercells.R @@ -17,7 +17,9 @@ #' @param minarea Specifies the minimal size of a supercell (in cells). Only works when `clean = TRUE`. #' By default, when `clean = TRUE`, average area (A) is calculated based on the total number of cells divided by a number of supercells #' Next, the minimal size of a supercell equals to A/(2^2) (A is being right shifted) -#' @param step The distance (number of cells) between initial supercells' centers. You can use either `k` or `step`. +#' @param step Initial center spacing. You can use either `k` or `step`. +#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param transform Transformation to be performed on the input. By default, no transformation is performed. Currently available transformation is "to_LAB": first, the conversion from RGB to the LAB color space is applied, then the supercells algorithm is run, and afterward, a reverse transformation is performed on the obtained results. (This argument is experimental and may be removed in the future). #' @param metadata Logical. Controls whether metadata columns #' ("supercells", "x", "y") are included. @@ -75,9 +77,6 @@ supercells = function(x, k, compactness, dist_fun = "euclidean", avg_fun = "mean } else { "values" } - if (iter == 0) { - clean = FALSE - } centers_arg = NULL if (!missing(k) && inherits(k, "sf")) { @@ -85,13 +84,7 @@ supercells = function(x, k, compactness, dist_fun = "euclidean", avg_fun = "mean k = NULL } - if (!inherits(x, "SpatRaster")) { - if (inherits(x, "stars")) { - x = terra::rast(x) - } else{ - stop("The SpatRaster class is expected as an input", call. = FALSE) - } - } + x = .sc_util_prep_raster(x) trans = .supercells_transform_to_lab(x, transform) x = trans$x @@ -121,11 +114,8 @@ supercells = function(x, k, compactness, dist_fun = "euclidean", avg_fun = "mean args$centers = centers_arg } - if (iter == 0) { - slic_sf = do.call(sc_slic_points, args) - } else { - slic_sf = do.call(sc_slic, args) - } + slic_runner = if (iter == 0) sc_slic_points else sc_slic + slic_sf = do.call(slic_runner, args) if (isTRUE(trans$did_transform)) { names_x = names(x) slic_sf = .supercells_transform_from_lab(slic_sf, names_x) diff --git a/man/in_meters.Rd b/man/in_meters.Rd new file mode 100644 index 0000000..1305cff --- /dev/null +++ b/man/in_meters.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/in_meters.R +\name{in_meters} +\alias{in_meters} +\title{Mark step values as meters} +\usage{ +in_meters(x) +} +\arguments{ +\item{x}{A single positive numeric value.} +} +\value{ +A \link[units:units]{units::units} object in meters (\code{m}). +} +\description{ +Creates a units value in meters for use in \code{step} arguments. +Use plain numerics for cell units, and \code{in_meters()} for map-distance steps. +} +\examples{ +in_meters(100) + +} diff --git a/man/sc_metrics_global.Rd b/man/sc_metrics_global.Rd index b6c12ff..c0378cd 100644 --- a/man/sc_metrics_global.Rd +++ b/man/sc_metrics_global.Rd @@ -46,9 +46,8 @@ Interpretation: values indicate spatial dominance; positive values indicate value dominance.} } \describe{ -\item{step}{Step size used to generate supercells. Returned in map units when -\code{step_unit = "map"}, otherwise in cells.} -\item{step_unit}{Units for \code{step} ("cells" or "map").} +\item{step}{Step size used to generate supercells. Returned in meters when +the input used \code{step = in_meters(...)}, otherwise in cells.} \item{compactness}{Compactness value used to generate supercells.} \item{n_supercells}{Number of supercells with at least one non-missing pixel.} \item{mean_value_dist}{Mean per-supercell value distance from cells to their @@ -57,7 +56,7 @@ supercell centers, averaged across supercells. Returned as \code{mean_value_dist \item{mean_spatial_dist}{Mean per-supercell spatial distance from cells to their supercell centers, averaged across supercells; units are grid cells (row/column index distance). If the input supercells were created with -\code{step_unit = "map"}, distances are reported in map units. Returned as +\code{step = in_meters(...)}, distances are reported in meters. Returned as \code{mean_spatial_dist} (or \code{mean_spatial_dist_scaled} when \code{scale = TRUE}).} \item{mean_combined_dist}{Mean per-supercell combined distance, computed from value and spatial distances using \code{compactness} and \code{step}, averaged across diff --git a/man/sc_metrics_pixels.Rd b/man/sc_metrics_pixels.Rd index 7b2bd20..34bd37b 100644 --- a/man/sc_metrics_pixels.Rd +++ b/man/sc_metrics_pixels.Rd @@ -49,7 +49,7 @@ Metrics: \describe{ \item{spatial}{Spatial distance from each pixel to its supercell center in grid-cell units (row/column index distance). If the input supercells were -created with \code{step_unit = "map"}, distances are reported in map units.} +created with \code{step = in_meters(...)}, distances are reported in meters.} \item{value}{Value distance from each pixel to its supercell center in the raster value space.} \item{combined}{Combined distance using \code{compactness} and \code{step}.} diff --git a/man/sc_metrics_supercells.Rd b/man/sc_metrics_supercells.Rd index fd62e7f..0e67c6f 100644 --- a/man/sc_metrics_supercells.Rd +++ b/man/sc_metrics_supercells.Rd @@ -50,7 +50,7 @@ Metrics: \item{supercells}{Supercell ID.} \item{spatial}{Mean spatial distance from cells to the supercell center in grid-cell units (row/column index distance). If the input supercells were -created with \code{step_unit = "map"}, distances are reported in map units. +created with \code{step = in_meters(...)}, distances are reported in meters. Returned as \code{mean_spatial_dist} (or \code{mean_spatial_dist_scaled} when \code{scale = TRUE}).} \item{value}{Mean value distance from cells to the supercell center in value space. Returned as \code{mean_value_dist} (or \code{mean_value_dist_scaled} diff --git a/man/sc_slic.Rd b/man/sc_slic.Rd index 578ddb6..05fce13 100644 --- a/man/sc_slic.Rd +++ b/man/sc_slic.Rd @@ -13,7 +13,6 @@ sc_slic( clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, outcomes = "values", @@ -25,7 +24,9 @@ sc_slic( \arguments{ \item{x}{An object of class SpatRaster (terra) or class stars (stars).} -\item{step}{The distance (number of cells) between initial centers (alternative is \code{k}).} +\item{step}{Initial center spacing (alternative is \code{k}). +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} @@ -44,9 +45,6 @@ function must accept a numeric vector and return a single numeric value.} \item{iter}{Number of iterations.} -\item{step_unit}{Units for \code{step}. Use "cells" for pixel units or "map" for map units -(converted to cells using raster resolution).} - \item{k}{The number of supercells desired (alternative to \code{step}).} \item{centers}{Optional sf object of custom centers. Requires \code{step}.} @@ -102,6 +100,6 @@ Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P., & Süsstrunk, S. (2012). Nowosad, J., Stepinski, T. (2022). Extended SLIC superpixels algorithm for applications to non-imagery geospatial rasters. International Journal of Applied Earth Observation and Geoinformation, https://doi.org/10.1016/j.jag.2022.102935 } \seealso{ -\code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_plot_iter_diagnostics]{sc_plot_iter_diagnostics()}}, +\code{\link[=in_meters]{in_meters()}}, \code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_plot_iter_diagnostics]{sc_plot_iter_diagnostics()}}, \code{\link[=sc_metrics_pixels]{sc_metrics_pixels()}}, \code{\link[=sc_metrics_supercells]{sc_metrics_supercells()}}, \code{\link[=sc_metrics_global]{sc_metrics_global()}} } diff --git a/man/sc_slic_points.Rd b/man/sc_slic_points.Rd index 4852846..68c6cca 100644 --- a/man/sc_slic_points.Rd +++ b/man/sc_slic_points.Rd @@ -13,7 +13,6 @@ sc_slic_points( clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, outcomes = "values", @@ -25,7 +24,9 @@ sc_slic_points( \arguments{ \item{x}{An object of class SpatRaster (terra) or class stars (stars).} -\item{step}{The distance (number of cells) between initial centers (alternative is \code{k}).} +\item{step}{Initial center spacing (alternative is \code{k}). +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} @@ -44,9 +45,6 @@ function must accept a numeric vector and return a single numeric value.} \item{iter}{Number of iterations.} -\item{step_unit}{Units for \code{step}. Use "cells" for pixel units or "map" for map units -(converted to cells using raster resolution).} - \item{k}{The number of supercells desired (alternative to \code{step}).} \item{centers}{Optional sf object of custom centers. Requires \code{step}.} diff --git a/man/sc_slic_raster.Rd b/man/sc_slic_raster.Rd index ea095ae..0446b2f 100644 --- a/man/sc_slic_raster.Rd +++ b/man/sc_slic_raster.Rd @@ -13,7 +13,6 @@ sc_slic_raster( clean = TRUE, minarea, iter = 10, - step_unit = "cells", k = NULL, centers = NULL, outcomes = "supercells", @@ -25,7 +24,9 @@ sc_slic_raster( \arguments{ \item{x}{An object of class SpatRaster (terra) or class stars (stars).} -\item{step}{The distance (number of cells) between initial centers (alternative is \code{k}).} +\item{step}{Initial center spacing (alternative is \code{k}). +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} @@ -44,9 +45,6 @@ function must accept a numeric vector and return a single numeric value.} \item{iter}{Number of iterations.} -\item{step_unit}{Units for \code{step}. Use "cells" for pixel units or "map" for map units -(converted to cells using raster resolution).} - \item{k}{The number of supercells desired (alternative to \code{step}).} \item{centers}{Optional sf object of custom centers. Requires \code{step}.} diff --git a/man/sc_tune_compactness.Rd b/man/sc_tune_compactness.Rd index ecc9ce4..cf664c5 100644 --- a/man/sc_tune_compactness.Rd +++ b/man/sc_tune_compactness.Rd @@ -7,7 +7,6 @@ sc_tune_compactness( raster, step = NULL, - step_unit = "cells", compactness = 1, metrics = "global", dist_fun = "euclidean", @@ -24,9 +23,9 @@ sc_tune_compactness( \arguments{ \item{raster}{A \code{SpatRaster}.} -\item{step}{The distance (number of cells) between initial centers (alternative is \code{k}).} - -\item{step_unit}{Units for \code{step}. Use "cells" for pixel units or "map" for map units.} +\item{step}{Initial center spacing (alternative is \code{k}). +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{Starting compactness used for the initial short run.} @@ -76,5 +75,5 @@ tune$compactness } \seealso{ -\code{\link[=sc_slic]{sc_slic()}} +\code{\link[=sc_slic]{sc_slic()}}, \code{\link[=in_meters]{in_meters()}} } diff --git a/man/supercells.Rd b/man/supercells.Rd index a64029b..a5f6b3b 100644 --- a/man/supercells.Rd +++ b/man/supercells.Rd @@ -41,7 +41,9 @@ A compactness value depends on the range of input cell values and selected dista \item{transform}{Transformation to be performed on the input. By default, no transformation is performed. Currently available transformation is "to_LAB": first, the conversion from RGB to the LAB color space is applied, then the supercells algorithm is run, and afterward, a reverse transformation is performed on the obtained results. (This argument is experimental and may be removed in the future).} -\item{step}{The distance (number of cells) between initial supercells' centers. You can use either \code{k} or \code{step}.} +\item{step}{Initial center spacing. You can use either \code{k} or \code{step}. +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} \item{minarea}{Specifies the minimal size of a supercell (in cells). Only works when \code{clean = TRUE}. By default, when \code{clean = TRUE}, average area (A) is calculated based on the total number of cells divided by a number of supercells diff --git a/tests/testthat/test-sc-create.R b/tests/testthat/test-sc-create.R index fd8a60e..939470f 100644 --- a/tests/testthat/test-sc-create.R +++ b/tests/testthat/test-sc-create.R @@ -73,3 +73,46 @@ test_that("sc_slic validates arguments", { expect_error(sc_slic(v1, centers = sf::st_sf(geom = sf::st_sfc()), compactness = 1), "step", fixed = TRUE) expect_error(sc_slic(v1, step = 8, compactness = 1, dist_fun = "not_a_dist"), "does not exist", fixed = TRUE) }) + +test_that("sc_slic accepts units-based map step", { + step_map = in_meters(8 * terra::res(v1)[1]) + sc = sc_slic(v1, step = step_map, compactness = 1) + expect_s3_class(attr(sc, "step"), "units") + expect_equal(as.numeric(units::drop_units(attr(sc, "step"))), as.numeric(units::drop_units(step_map))) +}) + +test_that("sc_slic rejects units-based step for lonlat rasters", { + x = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 1, ymin = 0, ymax = 1, crs = "EPSG:4326") + terra::values(x) = seq_len(terra::ncell(x)) + expect_error( + suppressWarnings(sc_slic(x, step = in_meters(1000), compactness = 1)), + "projected CRS", + fixed = TRUE + ) +}) + +test_that("sc_slic rejects non-meter units for step", { + expect_error( + sc_slic(v1, step = units::set_units(1, "km"), compactness = 1), + "must use meters", + fixed = TRUE + ) +}) + +test_that("sc_slic rejects units-based step for projected non-meter CRS", { + x = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 5000, ymin = 0, ymax = 5000, crs = "EPSG:2277") + terra::values(x) = seq_len(terra::ncell(x)) + expect_error( + suppressWarnings(sc_slic(x, step = in_meters(100), compactness = 1)), + "meter units", + fixed = TRUE + ) +}) + +test_that("in_meters validates inputs", { + x = in_meters(100) + expect_s3_class(x, "units") + expect_equal(as.character(units::deparse_unit(x)), "m") + expect_error(in_meters(-1), "single positive number", fixed = TRUE) + expect_error(in_meters(c(1, 2)), "single positive number", fixed = TRUE) +}) diff --git a/tests/testthat/test-sc-metrics.R b/tests/testthat/test-sc-metrics.R index 62088f7..3007593 100644 --- a/tests/testthat/test-sc-metrics.R +++ b/tests/testthat/test-sc-metrics.R @@ -55,15 +55,15 @@ test_that("sc_metrics invalid dist_fun errors", { expect_error(sc_metrics_pixels(v1, sc, dist_fun = "not_a_dist"), "does not exist", fixed = TRUE) }) -test_that("sc_metrics spatial units follow step_unit", { +test_that("sc_metrics spatial units follow step encoding", { v1_map = terra::aggregate(v1, fact = 2, fun = mean, na.rm = TRUE) res_map = terra::res(v1_map)[1] step_cells = 8 - step_map = step_cells * res_map + step_map = in_meters(step_cells * res_map) - sc_cells = sc_slic(v1_map, step = step_cells, compactness = 1, step_unit = "cells", + sc_cells = sc_slic(v1_map, step = step_cells, compactness = 1, outcomes = c("supercells", "coordinates", "values")) - sc_map = sc_slic(v1_map, step = step_map, compactness = 1, step_unit = "map", + sc_map = sc_slic(v1_map, step = step_map, compactness = 1, outcomes = c("supercells", "coordinates", "values")) g_cells = sc_metrics_global(v1_map, sc_cells, scale = FALSE) diff --git a/vignettes/articles/v2-changes-since-v1.Rmd b/vignettes/articles/v2-changes-since-v1.Rmd index c1d469a..a2c703d 100644 --- a/vignettes/articles/v2-changes-since-v1.Rmd +++ b/vignettes/articles/v2-changes-since-v1.Rmd @@ -152,7 +152,7 @@ plot(sf::st_geometry(vol_sc_slic0), add = TRUE, lwd = 0.6, border = "violet") ## Other changes -- New utilities: Added `step_unit` to `sc_slic()`/`sc_slic_points()`/`sc_slic_raster()` to support map-unit step sizes. +- New utilities: Added `in_meters()` helper for `step` in `sc_slic()`/`sc_slic_points()`/`sc_slic_raster()` to support map-distance step sizes. - Behavior: Since version 1.0, the way coordinates are summarized internally has changed, and results in versions after 1.0 may differ slightly from those prior to 1.0. - Performance: Improved speed and memory efficiency. - New utilities: Added experimental `sc_merge_supercells()` for adjacency-constrained greedy merging. diff --git a/vignettes/articles/v2-evaluation.Rmd b/vignettes/articles/v2-evaluation.Rmd index 5d925fb..0820f59 100644 --- a/vignettes/articles/v2-evaluation.Rmd +++ b/vignettes/articles/v2-evaluation.Rmd @@ -47,7 +47,7 @@ Pixel metrics are provided with the `sc_metrics_pixels()` function, which accept The pixel metrics include four layers^[Use the `metrics` argument to request a subset of metrics], each with a specific interpretation and a simple definition: -1. `spatial`: $d_s$, the distance from each pixel to its supercell center in grid-cell units (unless the input supercells were created with `step_unit = "map"`, in which case distances are reported in map units). +1. `spatial`: $d_s$, the distance from each pixel to its supercell center in grid-cell units (unless the input supercells were created with `step = in_meters(...)`, in which case distances are reported in map units). Lower spatial values indicate cells that are closer to the center and more compact supercells, while higher values indicate cells that are farther from the center and may indicate irregular shapes or outliers. 2. `value`: $d_v$, the distance from each pixel to its supercell center in the value space defined by your `dist_fun`. Lower value distances indicate more homogeneous supercells, while higher values indicate more heterogeneous supercells or outliers. diff --git a/vignettes/articles/v2-parameters.Rmd b/vignettes/articles/v2-parameters.Rmd index ac9d977..ac1fa8a 100644 --- a/vignettes/articles/v2-parameters.Rmd +++ b/vignettes/articles/v2-parameters.Rmd @@ -42,13 +42,13 @@ D = \sqrt{\left(\frac{d_s}{\text{step}}\right)^2 + \left(\frac{d_v}{c}\right)^2} $$ where $d_s$ is the spatial distance in grid-cell units, $d_v$ is the value-space distance (from `dist_fun`), and $c$ is the `compactness` value. -When `step_unit = "map"`, the `step` value is converted to cells before segmentation; distances are still computed in grid-cell units. +When `step` is provided as `in_meters(...)`, it is converted to cells before segmentation; distances are still computed in grid-cell units. Larger `compactness` down-weights the value term, making shapes more regular, while smaller values emphasize value similarity. # Choosing step or k You can control the number and size of supercells using either `step` or `k`. -`step` defines the spacing of initial centers in pixel units (or map units when `step_unit = "map"`). +`step` defines the spacing of initial centers in pixel units (or map units when given as `in_meters(...)`). Smaller `step` values produce more and smaller supercells, while larger values produce fewer and larger supercells. For example, `step = 8` creates centers approximately every 8 pixels, which leads to supercells that are roughly 8 by 8 pixels in size, depending on the compactness. @@ -56,12 +56,12 @@ For example, `step = 8` creates centers approximately every 8 pixels, which lead sc_step <- sc_slic(vol, step = 8, compactness = 5) ``` -By default, `step` is in pixel units, but instead we can also specify it in map units with `step_unit = "map"`. +By default, `step` is in pixel units, but instead we can also specify it in map units with `in_meters()`. This allows us to think about the spatial scales of expected supercells in terms of the actual map units. -For example, if your raster has a resolution of 10 meters, then `step = 200` with `step_unit = "map"` would create centers approximately every 200 meters, which corresponds to every 20 pixels. +For example, if your raster has a resolution of 10 meters, then `step = in_meters(200)` would create centers approximately every 200 meters, which corresponds to every 20 pixels. ```{r} -sc_step_map <- sc_slic(vol, step = 200, step_unit = "map", compactness = 5) +sc_step_map <- sc_slic(vol, step = in_meters(200), compactness = 5) ``` `k` specifies the desired number of supercells and the algorithm chooses an approximate step size. From 459662d7658759fc4f48f91e1230caa7d1133db5 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 15:17:29 +0100 Subject: [PATCH 04/11] renames arg and add output column --- R/sc_tune_compactness.R | 21 ++++++++++++--------- man/sc_tune_compactness.Rd | 6 +++--- tests/testthat/test-sc-tune-compactness.R | 14 +++++++++++--- vignettes/articles/v2-intro.Rmd | 4 ++-- vignettes/articles/v2-parameters.Rmd | 8 ++++---- 5 files changed, 32 insertions(+), 21 deletions(-) diff --git a/R/sc_tune_compactness.R b/R/sc_tune_compactness.R index b8d36dd..0246050 100644 --- a/R/sc_tune_compactness.R +++ b/R/sc_tune_compactness.R @@ -11,7 +11,7 @@ #' Provide a plain numeric value for cell units, or use [in_meters()] for #' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param compactness Starting compactness used for the initial short run. -#' @param metrics Which compactness metric to return: `"global"` or `"local"`. +#' @param metric Which compactness metric to return: `"global"` or `"local"`. #' Default: `"global"`. #' @param value_scale Optional scale factor applied to the median value distance #' before computing compactness. Use `"auto"` to divide by `sqrt(nlyr(raster))` @@ -30,7 +30,7 @@ #' @param sample_size Optional limit on the number of pixels used for the summary #' (passed to `terra::global()` as `maxcell`). #' -#' @return A one-row data frame with columns `step`, `metric`, and `compactness`. +#' @return A one-row data frame with columns `step`, `metric`, `dist_fun`, and `compactness`. #' #' @seealso [`sc_slic()`], [in_meters()] #' @@ -42,7 +42,7 @@ #' #' @export sc_tune_compactness = function(raster, step = NULL, compactness = 1, - metrics = "global", + metric = "global", dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 1, k = NULL, centers = NULL, @@ -63,10 +63,11 @@ sc_tune_compactness = function(raster, step = NULL, compactness = 1, step_used_num = as.numeric(units::drop_units(step_used_num)) } - if (!is.character(metrics) || length(metrics) != 1 || is.na(metrics) || - !(metrics %in% c("global", "local"))) { - stop("metrics must be 'global' or 'local'", call. = FALSE) + if (!is.character(metric) || length(metric) != 1 || is.na(metric) || + !(metric %in% c("global", "local"))) { + stop("metric must be 'global' or 'local'", call. = FALSE) } + dist_fun_out = if (is.character(dist_fun)) as.character(dist_fun[[1]]) else "custom" if (identical(value_scale, "auto")) { value_scale = sqrt(terra::nlyr(raster)) @@ -75,7 +76,7 @@ sc_tune_compactness = function(raster, step = NULL, compactness = 1, stop("value_scale must be a single positive number or 'auto'", call. = FALSE) } - if (identical(metrics, "global")) { + if (identical(metric, "global")) { pix_metrics = sc_metrics_pixels(raster, pts, dist_fun = dist_fun, compactness = compactness, step = step_used, scale = FALSE, metrics = c("spatial", "value")) @@ -86,7 +87,8 @@ sc_tune_compactness = function(raster, step = NULL, compactness = 1, value_dist_median = value_dist_median / value_scale compactness_value = value_dist_median * step_used_num / spatial_dist_median - return(data.frame(step = step_used, metric = "global", compactness = compactness_value)) + return(data.frame(step = step_used, metric = "global", dist_fun = dist_fun_out, + compactness = compactness_value)) } prep = .sc_metrics_prep(raster, pts, dist_fun, compactness, step_used, @@ -101,5 +103,6 @@ sc_tune_compactness = function(raster, step = NULL, compactness = 1, mean_value_dist = mean_value_dist / value_scale compactness_value = stats::median(mean_value_dist, na.rm = TRUE) - return(data.frame(step = step_used, metric = "local", compactness = compactness_value)) + return(data.frame(step = step_used, metric = "local", dist_fun = dist_fun_out, + compactness = compactness_value)) } diff --git a/man/sc_tune_compactness.Rd b/man/sc_tune_compactness.Rd index cf664c5..757b3a9 100644 --- a/man/sc_tune_compactness.Rd +++ b/man/sc_tune_compactness.Rd @@ -8,7 +8,7 @@ sc_tune_compactness( raster, step = NULL, compactness = 1, - metrics = "global", + metric = "global", dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, @@ -29,7 +29,7 @@ map-distance steps in meters (automatically converted to cells using raster reso \item{compactness}{Starting compactness used for the initial short run.} -\item{metrics}{Which compactness metric to return: \code{"global"} or \code{"local"}. +\item{metric}{Which compactness metric to return: \code{"global"} or \code{"local"}. Default: \code{"global"}.} \item{dist_fun}{A distance function name or a custom function. Supported names: @@ -58,7 +58,7 @@ before computing compactness. Use \code{"auto"} to divide by \code{sqrt(nlyr(ras (useful for high-dimensional embeddings). Default: \code{"auto"}.} } \value{ -A one-row data frame with columns \code{step}, \code{metric}, and \code{compactness}. +A one-row data frame with columns \code{step}, \code{metric}, \code{dist_fun}, and \code{compactness}. } \description{ Runs a short SLIC segmentation (default \code{iter = 1}) and uses cell-level diff --git a/tests/testthat/test-sc-tune-compactness.R b/tests/testthat/test-sc-tune-compactness.R index 16eee12..207ce12 100644 --- a/tests/testthat/test-sc-tune-compactness.R +++ b/tests/testthat/test-sc-tune-compactness.R @@ -2,18 +2,26 @@ test_that("sc_tune_compactness returns one-row data frame", { tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500) expect_s3_class(tune, "data.frame") expect_equal(nrow(tune), 1) - expect_true(all(c("step", "metric", "compactness") %in% names(tune))) + expect_true(all(c("step", "metric", "dist_fun", "compactness") %in% names(tune))) expect_equal(tune$step, 8) expect_equal(tune$metric, "global") + expect_equal(tune$dist_fun, "euclidean") expect_true(tune$compactness > 0) }) test_that("sc_tune_compactness returns local compactness when requested", { - tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, metrics = "local") + tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, metric = "local") expect_s3_class(tune, "data.frame") expect_equal(nrow(tune), 1) - expect_true(all(c("step", "metric", "compactness") %in% names(tune))) + expect_true(all(c("step", "metric", "dist_fun", "compactness") %in% names(tune))) expect_equal(tune$step, 8) expect_equal(tune$metric, "local") + expect_equal(tune$dist_fun, "euclidean") expect_true(tune$compactness > 0) }) + +test_that("sc_tune_compactness stores custom dist_fun label", { + manhattan = function(a, b) sum(abs(a - b)) + tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, dist_fun = manhattan) + expect_equal(tune$dist_fun, "custom") +}) diff --git a/vignettes/articles/v2-intro.Rmd b/vignettes/articles/v2-intro.Rmd index dfb58f1..985bbe8 100644 --- a/vignettes/articles/v2-intro.Rmd +++ b/vignettes/articles/v2-intro.Rmd @@ -57,7 +57,7 @@ Basic workflows follow the same pattern: choose scale (`step` or `k`), tune or s vol <- terra::rast(system.file("raster/volcano.tif", package = "supercells")) # choose scale and tune compactness -tune <- supercells::sc_tune_compactness(vol, step = 8, metrics = "local") +tune <- supercells::sc_tune_compactness(vol, step = 8, metric = "local") # create supercells vol_sc <- supercells::sc_slic(vol, step = 8, compactness = tune$compactness) @@ -114,7 +114,7 @@ Now, let's try to tune the `compactness` parameter using a pilot run. tune <- sc_tune_compactness( vol, step = 8, - metrics = "local" + metric = "local" ) tune ``` diff --git a/vignettes/articles/v2-parameters.Rmd b/vignettes/articles/v2-parameters.Rmd index ac1fa8a..7a88cec 100644 --- a/vignettes/articles/v2-parameters.Rmd +++ b/vignettes/articles/v2-parameters.Rmd @@ -121,7 +121,7 @@ plot(sc_compact_high[0], add = TRUE, border = "red", lwd = 0.5) The `sc_tune_compactness()` function estimates a reasonable starting value from a short run of the algorithm. -It supports two summaries with `metrics = "global"` and `metrics = "local"`. +It supports two summaries with `metric = "global"` and `metric = "local"`. The global version looks at overall balance between value and spatial distances, while the local version uses a neighborhood-based value scale. More precisely: @@ -133,13 +133,13 @@ More precisely: The local estimate is often more stable for heterogeneous rasters. ```{r} -tune_global <- sc_tune_compactness(vol, step = 8, metrics = "global") -tune_local <- sc_tune_compactness(vol, step = 8, metrics = "local") +tune_global <- sc_tune_compactness(vol, step = 8, metric = "global") +tune_local <- sc_tune_compactness(vol, step = 8, metric = "local") tune_global tune_local ``` -Both results return a one-row data frame with `step`, `metric`, and `compactness`. +Both results return a one-row data frame with `step`, `metric`, `dist_fun`, and `compactness`. You can plug the suggested value into `sc_slic()` by setting `compactness` to the estimated value. ```{r} From bbe6e40d025fd6f396fdf12314e0dc8542a87348 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 15:22:08 +0100 Subject: [PATCH 05/11] cleans internals --- R/helpers-metrics.R | 7 ------- R/sc_metrics_global.R | 5 ++++- R/sc_metrics_pixels.R | 5 ++++- R/sc_metrics_supercells.R | 5 ++++- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/R/helpers-metrics.R b/R/helpers-metrics.R index 57e7e12..52a72f2 100644 --- a/R/helpers-metrics.R +++ b/R/helpers-metrics.R @@ -13,13 +13,6 @@ dist_fun } -.sc_metrics_validate_names = function(metrics) { - allowed = c("spatial", "value", "combined", "balance") - if (any(!metrics %in% allowed)) { - stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) - } -} - .sc_metrics_scale_summary = function(value_dist, spatial_dist, out, prep, scale) { if (!isTRUE(scale)) { return(list(value_dist = value_dist, spatial_dist = spatial_dist)) diff --git a/R/sc_metrics_global.R b/R/sc_metrics_global.R index 68465c5..8e34f3d 100644 --- a/R/sc_metrics_global.R +++ b/R/sc_metrics_global.R @@ -60,7 +60,10 @@ sc_metrics_global = function(x, sc, scale = TRUE, step, compactness, dist_fun = NULL) { dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) - .sc_metrics_validate_names(metrics) + allowed_metrics = c("spatial", "value", "combined", "balance") + if (any(!metrics %in% allowed_metrics)) { + stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) + } prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) out = sc_metrics_global_cpp(prep$clusters, prep$centers_xy, prep$centers_vals, prep$vals, diff --git a/R/sc_metrics_pixels.R b/R/sc_metrics_pixels.R index 5685afa..591f91d 100644 --- a/R/sc_metrics_pixels.R +++ b/R/sc_metrics_pixels.R @@ -58,7 +58,10 @@ sc_metrics_pixels = function(x, sc, scale = TRUE, step, compactness, dist_fun = NULL) { dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) - .sc_metrics_validate_names(metrics) + allowed_metrics = c("spatial", "value", "combined", "balance") + if (any(!metrics %in% allowed_metrics)) { + stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) + } prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) diff --git a/R/sc_metrics_supercells.R b/R/sc_metrics_supercells.R index cdcbf70..871b84b 100644 --- a/R/sc_metrics_supercells.R +++ b/R/sc_metrics_supercells.R @@ -51,7 +51,10 @@ sc_metrics_supercells = function(x, sc, scale = TRUE, step, compactness, dist_fun = NULL) { dist_fun = .sc_metrics_resolve_dist_fun(sc, dist_fun) - .sc_metrics_validate_names(metrics) + allowed_metrics = c("spatial", "value", "combined", "balance") + if (any(!metrics %in% allowed_metrics)) { + stop("metrics must be one or more of: spatial, value, combined, balance", call. = FALSE) + } prep = .sc_metrics_prep(x, sc, dist_fun, compactness, step) x_df = sf::st_drop_geometry(prep$sc) From 60b73d14f9f52c4a15c3003fcb3d4131600efdb0 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 15:46:16 +0100 Subject: [PATCH 06/11] cleans attributes --- R/helpers-metrics.R | 18 ++++++++++++------ R/helpers-sc_slic.R | 7 ++++--- R/sc_metrics_global.R | 2 +- tests/testthat/test-sc-create.R | 6 +++++- tests/testthat/test-sc-metrics.R | 7 +++++++ 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/R/helpers-metrics.R b/R/helpers-metrics.R index 52a72f2..05187f7 100644 --- a/R/helpers-metrics.R +++ b/R/helpers-metrics.R @@ -40,14 +40,9 @@ if (!inherits(sc, "sf")) { stop("The 'sc' argument must be an sf object returned by sc_slic()", call. = FALSE) } - adaptive_compactness = FALSE + if (missing(compactness)) { - method = attr(sc, "method") - adaptive_compactness = isTRUE(identical(method, "slic0")) compactness = attr(sc, "compactness") - } else if (is.character(compactness) && length(compactness) == 1 && !is.na(compactness) && compactness == "auto") { - adaptive_compactness = TRUE - compactness = 0 } if (missing(step)) { step = attr(sc, "step") @@ -55,6 +50,16 @@ if (is.null(compactness) || is.null(step)) { stop("Both 'compactness' and 'step' are required", call. = FALSE) } + + compactness_input = compactness + adaptive_compactness = FALSE + if (is.character(compactness)) { + if (length(compactness) != 1 || is.na(compactness) || compactness != "auto") { + stop("The 'compactness' argument must be numeric or 'auto'", call. = FALSE) + } + adaptive_compactness = TRUE + compactness = 0 + } step_prep = .sc_util_step_to_cells(raster, step) step = step_prep$step @@ -89,6 +94,7 @@ step = step, step_meta = step_prep$step_meta, compactness = compactness, + compactness_input = compactness_input, adaptive_compactness = adaptive_compactness, spatial_scale = spatial_scale, step_scale = step_scale diff --git a/R/helpers-sc_slic.R b/R/helpers-sc_slic.R index db95124..4f221b5 100644 --- a/R/helpers-sc_slic.R +++ b/R/helpers-sc_slic.R @@ -40,6 +40,7 @@ iter_diagnostics = FALSE } verbose_cpp = if (is.numeric(verbose) && length(verbose) == 1 && !is.na(verbose) && verbose >= 2) verbose else 0 + compactness_input = compactness adaptive_compactness = is.character(compactness) && length(compactness) == 1 && !is.na(compactness) && compactness == "auto" if (adaptive_compactness) { @@ -51,7 +52,8 @@ input_centers = input_centers, funs = funs, minarea = minarea, chunk_ext = chunk_ext, iter_diagnostics = iter_diagnostics, outcomes = outcomes, - compactness = compactness, adaptive_compactness = adaptive_compactness, + compactness = compactness, compactness_input = compactness_input, + adaptive_compactness = adaptive_compactness, clean = clean, iter = iter, verbose = verbose, verbose_cpp = verbose_cpp)) } @@ -215,9 +217,8 @@ slic_sf = .sc_slic_select_outcomes(slic_sf, prep$outcomes) attr(slic_sf, "step") = prep$step_meta - attr(slic_sf, "compactness") = prep$compactness + attr(slic_sf, "compactness") = prep$compactness_input attr(slic_sf, "dist_fun") = prep$dist_fun_input - attr(slic_sf, "method") = if (isTRUE(prep$adaptive_compactness)) "slic0" else "slic" cls = class(slic_sf) cls = c(setdiff(cls, "data.frame"), "supercells", "data.frame") class(slic_sf) = unique(cls) diff --git a/R/sc_metrics_global.R b/R/sc_metrics_global.R index 8e34f3d..abaf3e4 100644 --- a/R/sc_metrics_global.R +++ b/R/sc_metrics_global.R @@ -95,7 +95,7 @@ sc_metrics_global = function(x, sc, names(out_metrics) = unname(name_map[metrics]) step_out = prep$step_meta results = cbind( - data.frame(step = step_out, compactness = prep$compactness, n_supercells = out[["n_supercells"]]), + data.frame(step = step_out, compactness = prep$compactness_input, n_supercells = out[["n_supercells"]]), out_metrics ) return(results) diff --git a/tests/testthat/test-sc-create.R b/tests/testthat/test-sc-create.R index 939470f..da3ca85 100644 --- a/tests/testthat/test-sc-create.R +++ b/tests/testthat/test-sc-create.R @@ -4,10 +4,14 @@ test_that("sc_slic returns sf with attributes", { expect_false(any(c("supercells", "x", "y") %in% names(sc))) expect_equal(attr(sc, "step"), attr(sc, "step")) expect_equal(attr(sc, "compactness"), 1) - expect_equal(attr(sc, "method"), "slic") expect_true("supercells" %in% class(sc)) }) +test_that("sc_slic stores adaptive compactness as 'auto'", { + sc = sc_slic(v1, step = 8, compactness = "auto") + expect_equal(attr(sc, "compactness"), "auto") +}) + test_that("sc_slic handles step and centers", { set.seed(2021-11-21) centers = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 50, type = "random")) diff --git a/tests/testthat/test-sc-metrics.R b/tests/testthat/test-sc-metrics.R index 3007593..3330604 100644 --- a/tests/testthat/test-sc-metrics.R +++ b/tests/testthat/test-sc-metrics.R @@ -27,6 +27,13 @@ test_that("sc_metrics uses step and compactness from attributes", { expect_equal(gl$compactness, attr(sc, "compactness")) }) +test_that("sc_metrics uses adaptive compactness from compactness attribute", { + sc = sc_slic(v1, step = 8, compactness = "auto", + outcomes = c("supercells", "coordinates", "values")) + gl = sc_metrics_global(v1, sc) + expect_equal(gl$compactness, "auto") +}) + test_that("sc_metrics_supercells returns sf with metrics", { sc = sc_slic(v1, step = 8, compactness = 1, outcomes = c("supercells", "coordinates", "values")) From 19e169a4f8e5d5d841f3189100af3b3f3472a3d0 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 15:48:35 +0100 Subject: [PATCH 07/11] updates deafult outcomes --- R/sc_slic.R | 5 +++-- man/sc_slic.Rd | 5 +++-- tests/testthat/test-sc-create.R | 2 +- tests/testthat/test-sc-metrics.R | 2 +- vignettes/articles/v2-intro.Rmd | 3 ++- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/R/sc_slic.R b/R/sc_slic.R index 876b53c..71aca80 100644 --- a/R/sc_slic.R +++ b/R/sc_slic.R @@ -39,7 +39,8 @@ #' @param centers Optional sf object of custom centers. Requires `step`. #' @param outcomes Character vector controlling which fields are returned. #' Allowed values are "supercells", "coordinates", and "values". Default is -#' "values". Use `outcomes = c("supercells", "coordinates", "values")` for full output. +#' full output (`c("supercells", "coordinates", "values")`). +#' Use `outcomes = "values"` for value summaries only. #' @param chunks Chunking option. Use `FALSE` for no chunking, `TRUE` for #' automatic chunking based on size, or a numeric value for a fixed chunk size #' (in number of cells per side). @@ -65,7 +66,7 @@ sc_slic = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, k = NULL, centers = NULL, - outcomes = "values", chunks = FALSE, + outcomes = c("supercells", "coordinates", "values"), chunks = FALSE, iter_diagnostics = FALSE, verbose = 0) { if (iter == 0) { diff --git a/man/sc_slic.Rd b/man/sc_slic.Rd index 05fce13..18b1efd 100644 --- a/man/sc_slic.Rd +++ b/man/sc_slic.Rd @@ -15,7 +15,7 @@ sc_slic( iter = 10, k = NULL, centers = NULL, - outcomes = "values", + outcomes = c("supercells", "coordinates", "values"), chunks = FALSE, iter_diagnostics = FALSE, verbose = 0 @@ -51,7 +51,8 @@ function must accept a numeric vector and return a single numeric value.} \item{outcomes}{Character vector controlling which fields are returned. Allowed values are "supercells", "coordinates", and "values". Default is -"values". Use \code{outcomes = c("supercells", "coordinates", "values")} for full output.} +full output (\code{c("supercells", "coordinates", "values")}). +Use \code{outcomes = "values"} for value summaries only.} \item{chunks}{Chunking option. Use \code{FALSE} for no chunking, \code{TRUE} for automatic chunking based on size, or a numeric value for a fixed chunk size diff --git a/tests/testthat/test-sc-create.R b/tests/testthat/test-sc-create.R index da3ca85..798cb3a 100644 --- a/tests/testthat/test-sc-create.R +++ b/tests/testthat/test-sc-create.R @@ -1,7 +1,7 @@ test_that("sc_slic returns sf with attributes", { sc = sc_slic(v1, step = 8, compactness = 1) expect_s3_class(sc, "sf") - expect_false(any(c("supercells", "x", "y") %in% names(sc))) + expect_true(all(c("supercells", "x", "y") %in% names(sc))) expect_equal(attr(sc, "step"), attr(sc, "step")) expect_equal(attr(sc, "compactness"), 1) expect_true("supercells" %in% class(sc)) diff --git a/tests/testthat/test-sc-metrics.R b/tests/testthat/test-sc-metrics.R index 3330604..968ffae 100644 --- a/tests/testthat/test-sc-metrics.R +++ b/tests/testthat/test-sc-metrics.R @@ -8,7 +8,7 @@ test_that("sc_metrics_pixels returns raster with layers", { }) test_that("sc_metrics functions work without metadata columns", { - sc = sc_slic(v1, step = 8, compactness = 1) + sc = sc_slic(v1, step = 8, compactness = 1, outcomes = "values") pix = sc_metrics_pixels(v1, sc) expect_s4_class(pix, "SpatRaster") diff --git a/vignettes/articles/v2-intro.Rmd b/vignettes/articles/v2-intro.Rmd index 985bbe8..a119ab6 100644 --- a/vignettes/articles/v2-intro.Rmd +++ b/vignettes/articles/v2-intro.Rmd @@ -101,7 +101,8 @@ plot(sf::st_geometry(vol_sc), add = TRUE, lwd = 0.6, border = "red") The resulting `sf` object contains one row per supercell. Each row stores summary values of each layer in the original raster, as well as the geometry of the supercell. -By default only summary values are returned, so use `outcomes = c("supercells", "coordinates", "values")` when you also want IDs and center coordinates. +By default, IDs, center coordinates, and summary values are returned (`outcomes = c("supercells", "coordinates", "values")`). +Use `outcomes = "values"` when you want value summaries only. Two related functions provide alternative output formats. Use `sc_slic_points()` to return only supercell centers as points. From a50b93e6b10b75424e435bb37b9788f70842fa13 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 16:18:15 +0100 Subject: [PATCH 08/11] better iteration fun --- NAMESPACE | 3 +- NEWS.md | 1 + R/helpers-sc_slic.R | 25 ++------ R/sc_plot_iter_diagnostics.R | 31 --------- R/sc_slic.R | 16 ++--- R/sc_slic_convergence.R | 73 +++++++++++++++++++++ R/sc_slic_points.R | 8 +-- R/sc_slic_raster.R | 28 +++++--- R/supercells.R | 1 - man/sc_plot_iter_diagnostics.Rd | 27 -------- man/sc_slic.Rd | 10 +-- man/sc_slic_convergence.Rd | 75 ++++++++++++++++++++++ man/sc_slic_points.Rd | 4 -- man/sc_slic_raster.Rd | 4 -- tests/testthat/test-sc-slic-convergence.R | 22 +++++++ vignettes/articles/v2-changes-since-v1.Rmd | 12 ++-- vignettes/articles/v2-parameters.Rmd | 8 +-- 17 files changed, 216 insertions(+), 132 deletions(-) delete mode 100644 R/sc_plot_iter_diagnostics.R create mode 100644 R/sc_slic_convergence.R delete mode 100644 man/sc_plot_iter_diagnostics.Rd create mode 100644 man/sc_slic_convergence.Rd create mode 100644 tests/testthat/test-sc-slic-convergence.R diff --git a/NAMESPACE b/NAMESPACE index f53ed7a..ed37602 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,11 +1,12 @@ # Generated by roxygen2: do not edit by hand +S3method(plot,sc_slic_convergence) export(in_meters) export(sc_metrics_global) export(sc_metrics_pixels) export(sc_metrics_supercells) -export(sc_plot_iter_diagnostics) export(sc_slic) +export(sc_slic_convergence) export(sc_slic_points) export(sc_slic_raster) export(sc_tune_compactness) diff --git a/NEWS.md b/NEWS.md index 62fe7f2..8b9d75d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,7 @@ # supercells 1.9 * Added `outcomes` argument to `sc_slic()`, `sc_slic_points()`, and `sc_slic_raster()`; replaces `metadata` for controlling returned fields +* Iteration diagnostics API redesigned: `iter_diagnostics` and `sc_plot_iter_diagnostics()` replaced by `sc_slic_convergence()` with a `plot()` method * Added experimental `sc_merge_supercells()` for adjacency-constrained greedy merging * Added `sc_dist_vec_cpp()` (C++ distance wrapper) to support merge utilities * Documentation and vignettes updated (pkgdown refresh, new articles, and revised examples) diff --git a/R/helpers-sc_slic.R b/R/helpers-sc_slic.R index 4f221b5..668ecfc 100644 --- a/R/helpers-sc_slic.R +++ b/R/helpers-sc_slic.R @@ -2,7 +2,7 @@ # prepare and validate slic arguments .sc_slic_prep_args = function(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, - k, centers, outcomes, chunks, iter_diagnostics, verbose) { + k, centers, outcomes, chunks, verbose) { # Validate core arguments and types .sc_slic_validate_args(step, compactness, k, centers, chunks, dist_fun, avg_fun, iter, minarea) outcomes = .sc_slic_prep_outcomes(outcomes) @@ -34,11 +34,6 @@ minarea = .sc_slic_prep_minarea(minarea, step) # Compute chunk extents based on size/limits chunk_ext = .sc_chunk_extents(dim(x), limit = chunks, step = step) - # Disable iter diagnostics when chunking is active - if (iter_diagnostics && nrow(chunk_ext) > 1) { - warning("Iteration diagnostics are only available when chunks = FALSE (single chunk). Iteration diagnostics were disabled.", call. = FALSE) - iter_diagnostics = FALSE - } verbose_cpp = if (is.numeric(verbose) && length(verbose) == 1 && !is.na(verbose) && verbose >= 2) verbose else 0 compactness_input = compactness adaptive_compactness = is.character(compactness) && @@ -51,7 +46,7 @@ dist_fun_input = dist_fun, input_centers = input_centers, funs = funs, minarea = minarea, chunk_ext = chunk_ext, - iter_diagnostics = iter_diagnostics, outcomes = outcomes, + outcomes = outcomes, compactness = compactness, compactness_input = compactness_input, adaptive_compactness = adaptive_compactness, clean = clean, iter = iter, @@ -201,16 +196,8 @@ return(do.call(apply, c(list(chunk_ext, MARGIN = 1, FUN = fun), args))) } -# add iter diagnostics attribute when enabled -.sc_slic_add_iter_attr = function(chunks, iter_diagnostics) { - if (isTRUE(iter_diagnostics) && length(chunks) > 0) { - return(attr(chunks[[1]], "iter_diagnostics")) - } - NULL -} - # finalize slic output with ids, metadata, and attributes -.sc_slic_post = function(chunks, prep, iter_attr) { +.sc_slic_post = function(chunks, prep) { slic_sf = .sc_chunk_update_ids(chunks) @@ -222,9 +209,6 @@ cls = class(slic_sf) cls = c(setdiff(cls, "data.frame"), "supercells", "data.frame") class(slic_sf) = unique(cls) - if (!is.null(iter_attr)) { - attr(slic_sf, "iter_diagnostics") = iter_attr - } return(slic_sf) } @@ -243,8 +227,7 @@ iter = prep$iter, minarea = prep$minarea, input_centers = prep$input_centers, - verbose = prep$verbose_cpp, - iter_diagnostics = prep$iter_diagnostics + verbose = prep$verbose_cpp ) if (nrow(prep$chunk_ext) == 1) { list(chunks = list(do.call(single_runner, args))) diff --git a/R/sc_plot_iter_diagnostics.R b/R/sc_plot_iter_diagnostics.R deleted file mode 100644 index e1299fc..0000000 --- a/R/sc_plot_iter_diagnostics.R +++ /dev/null @@ -1,31 +0,0 @@ -#' Plot iteration diagnostics -#' -#' Plot mean distance across iterations for a supercells run -#' -#' @param x A supercells object with an \code{iter_diagnostics} attribute, -#' or a diagnostics list containing \code{mean_distance} -#' -#' @return Invisibly returns \code{TRUE} when a plot is created -#' -#' @seealso [`sc_slic()`], [`sc_slic_points()`] -#' @export -#' -#' @examples -#' library(supercells) -#' vol = terra::rast(system.file("raster/volcano.tif", package = "supercells")) -#' vol_sc = sc_slic_points(vol, step = 8, compactness = 1, iter_diagnostics = TRUE) -#' sc_plot_iter_diagnostics(vol_sc) -sc_plot_iter_diagnostics = function(x) { - - iter = attr(x, "iter_diagnostics") - - if (is.null(iter) || is.null(iter$mean_distance) || length(iter$mean_distance) == 0) { - stop("No iter_diagnostics found", call. = FALSE) - } - - y = iter$mean_distance - graphics::plot(seq_along(y), y, type = "b", - xlab = "Iteration", ylab = "Mean distance", - main = "SLIC iteration diagnostics") - return(invisible(TRUE)) -} diff --git a/R/sc_slic.R b/R/sc_slic.R index 71aca80..445019c 100644 --- a/R/sc_slic.R +++ b/R/sc_slic.R @@ -9,15 +9,13 @@ #' [`sc_slic_raster()`] and [`sc_slic_points()`]. #' Evaluation and diagnostic options: #' \itemize{ -#' \item Iteration diagnostics: set `iter_diagnostics = TRUE` to attach an -#' `iter_diagnostics` attribute (only available without chunking). Use -#' [`sc_plot_iter_diagnostics()`] to visualize the convergence over iterations. +#' \item Iteration convergence: use [`sc_slic_convergence()`] and plot its output. #' \item Pixel diagnostics: [sc_metrics_pixels()] for per-pixel spatial, value, #' and combined distances. #' \item Cluster diagnostics: [sc_metrics_supercells()] for per-supercell summaries. #' \item Global diagnostics: [sc_metrics_global()] for a single-row summary. #' } -#' @seealso [in_meters()], [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_plot_iter_diagnostics()`], +#' @seealso [in_meters()], [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_slic_convergence()`], #' [`sc_metrics_pixels()`], [`sc_metrics_supercells()`], [`sc_metrics_global()`] #' #' @param x An object of class SpatRaster (terra) or class stars (stars). @@ -45,8 +43,6 @@ #' automatic chunking based on size, or a numeric value for a fixed chunk size #' (in number of cells per side). #' @param verbose Verbosity level. -#' @param iter_diagnostics Logical. If `TRUE`, attaches iteration diagnostics as an -#' attribute (`iter_diagnostics`) on the output. Only available when chunks are not used. #' #' @return An sf object with the supercell polygons and summary statistics. #' Information on `step` and `compactness` are attached to the result as attributes. @@ -67,18 +63,16 @@ sc_slic = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, k = NULL, centers = NULL, outcomes = c("supercells", "coordinates", "values"), chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0) { + verbose = 0) { if (iter == 0) { stop("iter = 0 returns centers only; polygon output is not available. Use sc_slic_points(iter = 0) to get initial centers.", call. = FALSE) } prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, - k, centers, outcomes, chunks, iter_diagnostics, verbose) + k, centers, outcomes, chunks, verbose) segment = .sc_slic_segment(prep_args, .sc_run_full_polygons, .sc_run_chunk_polygons) - iter_attr = .sc_slic_add_iter_attr(segment$chunks, prep_args$iter_diagnostics) - - result = .sc_slic_post(segment$chunks, prep_args, iter_attr) + result = .sc_slic_post(segment$chunks, prep_args) return(result) } diff --git a/R/sc_slic_convergence.R b/R/sc_slic_convergence.R new file mode 100644 index 0000000..307d627 --- /dev/null +++ b/R/sc_slic_convergence.R @@ -0,0 +1,73 @@ +#' SLIC convergence diagnostics +#' +#' Runs SLIC and returns per-iteration mean combined distance. +#' The output can be plotted directly with [plot()]. +#' +#' @inheritParams sc_slic +#' +#' @return A data frame with class `sc_slic_convergence` and columns: +#' \describe{ +#' \item{iter}{Iteration number.} +#' \item{mean_distance}{Mean combined distance across assigned cells at each iteration.} +#' } +#' +#' @seealso [sc_slic()], [plot()] +#' @export +#' +#' @examples +#' library(supercells) +#' vol = terra::rast(system.file("raster/volcano.tif", package = "supercells")) +#' conv = sc_slic_convergence(vol, step = 8, compactness = 5, iter = 10) +#' plot(conv) +sc_slic_convergence = function(x, step = NULL, compactness, dist_fun = "euclidean", + avg_fun = "mean", clean = TRUE, minarea, iter = 10, + k = NULL, centers = NULL, verbose = 0) { + if (iter == 0) { + stop("iter must be > 0 for convergence diagnostics", call. = FALSE) + } + + prep = .sc_slic_prep_args( + x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, + k, centers, outcomes = "values", chunks = FALSE, verbose = verbose + ) + + res = .sc_run_full_raster( + x = prep$x, + step = prep$step, + compactness = prep$compactness, + dist_name = prep$funs$dist_name, + dist_fun = prep$funs$dist_fun, + adaptive_compactness = prep$adaptive_compactness, + avg_fun_fun = prep$funs$avg_fun_fun, + avg_fun_name = prep$funs$avg_fun_name, + clean = prep$clean, + iter = prep$iter, + minarea = prep$minarea, + input_centers = prep$input_centers, + iter_diagnostics = TRUE, + verbose = prep$verbose_cpp + ) + + iter_diag = res$iter_diagnostics + if (is.null(iter_diag) || is.null(iter_diag$mean_distance) || length(iter_diag$mean_distance) == 0) { + stop("No convergence diagnostics available", call. = FALSE) + } + + y = as.numeric(iter_diag$mean_distance) + out = data.frame(iter = seq_along(y), mean_distance = y) + class(out) = c("sc_slic_convergence", class(out)) + out +} + +#' @export +plot.sc_slic_convergence = function(x, ...) { + if (!all(c("iter", "mean_distance") %in% names(x))) { + stop("x must contain 'iter' and 'mean_distance' columns", call. = FALSE) + } + graphics::plot( + x$iter, x$mean_distance, type = "b", + xlab = "Iteration", ylab = "Mean distance", + main = "SLIC convergence", ... + ) + invisible(x) +} diff --git a/R/sc_slic_points.R b/R/sc_slic_points.R index 272f2b7..8300ab2 100644 --- a/R/sc_slic_points.R +++ b/R/sc_slic_points.R @@ -29,17 +29,15 @@ sc_slic_points = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, k = NULL, centers = NULL, outcomes = "values", chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0) { + verbose = 0) { if (iter == 0) { clean = FALSE } prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, - k, centers, outcomes, chunks, iter_diagnostics, verbose) + k, centers, outcomes, chunks, verbose) segment = .sc_slic_segment(prep_args, .sc_run_full_points, .sc_run_chunk_points) - iter_attr = .sc_slic_add_iter_attr(segment$chunks, prep_args$iter_diagnostics) - - results = .sc_slic_post(segment$chunks, prep_args, iter_attr) + results = .sc_slic_post(segment$chunks, prep_args) return(results) } diff --git a/R/sc_slic_raster.R b/R/sc_slic_raster.R index e567dbd..21db502 100644 --- a/R/sc_slic_raster.R +++ b/R/sc_slic_raster.R @@ -22,7 +22,7 @@ sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", avg_fun = "mean", clean = TRUE, minarea, iter = 10, k = NULL, centers = NULL, outcomes = "supercells", chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0) { + verbose = 0) { if (iter == 0) { stop("iter = 0 returns centers only; raster output is not available. Use sc_slic_points(iter = 0) to get initial centers.", call. = FALSE) @@ -32,7 +32,7 @@ sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", } # prep arguments prep_args = .sc_slic_prep_args(x, step, compactness, dist_fun, avg_fun, clean, minarea, iter, - k, centers, outcomes, chunks, iter_diagnostics, verbose) + k, centers, outcomes, chunks, verbose) # segment once (single) or per chunk (chunked), returning a list of chunk results if (nrow(prep_args$chunk_ext) > 1) { @@ -49,13 +49,23 @@ sc_slic_raster = function(x, step = NULL, compactness, dist_fun = "euclidean", message(sprintf("Processing chunk %d/%d", i, n_chunks)) } ext = prep_args$chunk_ext[i, ] - res = .sc_run_chunk_raster(ext, prep_args$x, prep_args$step, prep_args$compactness, - prep_args$funs$dist_name, prep_args$adaptive_compactness, - prep_args$funs$dist_fun, - prep_args$funs$avg_fun_fun, prep_args$funs$avg_fun_name, - prep_args$clean, prep_args$iter, prep_args$minarea, - prep_args$input_centers, prep_args$iter_diagnostics, - prep_args$verbose_cpp) + res = .sc_run_chunk_raster( + ext = ext, + x = prep_args$x, + step = prep_args$step, + compactness = prep_args$compactness, + dist_name = prep_args$funs$dist_name, + adaptive_compactness = prep_args$adaptive_compactness, + dist_fun = prep_args$funs$dist_fun, + avg_fun_fun = prep_args$funs$avg_fun_fun, + avg_fun_name = prep_args$funs$avg_fun_name, + clean = prep_args$clean, + iter = prep_args$iter, + minarea = prep_args$minarea, + input_centers = prep_args$input_centers, + iter_diagnostics = FALSE, + verbose = prep_args$verbose_cpp + ) r = res[["raster"]] if (max_id > 0) { r = r + max_id diff --git a/R/supercells.R b/R/supercells.R index 1d601ff..149237d 100644 --- a/R/supercells.R +++ b/R/supercells.R @@ -98,7 +98,6 @@ supercells = function(x, k, compactness, dist_fun = "euclidean", avg_fun = "mean iter = iter, outcomes = outcomes, chunks = chunks, - iter_diagnostics = FALSE, verbose = verbose ) if (!missing(step)) { diff --git a/man/sc_plot_iter_diagnostics.Rd b/man/sc_plot_iter_diagnostics.Rd deleted file mode 100644 index 2296de1..0000000 --- a/man/sc_plot_iter_diagnostics.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/sc_plot_iter_diagnostics.R -\name{sc_plot_iter_diagnostics} -\alias{sc_plot_iter_diagnostics} -\title{Plot iteration diagnostics} -\usage{ -sc_plot_iter_diagnostics(x) -} -\arguments{ -\item{x}{A supercells object with an \code{iter_diagnostics} attribute, -or a diagnostics list containing \code{mean_distance}} -} -\value{ -Invisibly returns \code{TRUE} when a plot is created -} -\description{ -Plot mean distance across iterations for a supercells run -} -\examples{ -library(supercells) -vol = terra::rast(system.file("raster/volcano.tif", package = "supercells")) -vol_sc = sc_slic_points(vol, step = 8, compactness = 1, iter_diagnostics = TRUE) -sc_plot_iter_diagnostics(vol_sc) -} -\seealso{ -\code{\link[=sc_slic]{sc_slic()}}, \code{\link[=sc_slic_points]{sc_slic_points()}} -} diff --git a/man/sc_slic.Rd b/man/sc_slic.Rd index 18b1efd..414e25b 100644 --- a/man/sc_slic.Rd +++ b/man/sc_slic.Rd @@ -17,7 +17,6 @@ sc_slic( centers = NULL, outcomes = c("supercells", "coordinates", "values"), chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0 ) } @@ -58,9 +57,6 @@ Use \code{outcomes = "values"} for value summaries only.} automatic chunking based on size, or a numeric value for a fixed chunk size (in number of cells per side).} -\item{iter_diagnostics}{Logical. If \code{TRUE}, attaches iteration diagnostics as an -attribute (\code{iter_diagnostics}) on the output. Only available when chunks are not used.} - \item{verbose}{Verbosity level.} } \value{ @@ -77,9 +73,7 @@ Use \code{\link[=sc_slic]{sc_slic()}} for polygon outputs. For raster or point c \code{\link[=sc_slic_raster]{sc_slic_raster()}} and \code{\link[=sc_slic_points]{sc_slic_points()}}. Evaluation and diagnostic options: \itemize{ -\item Iteration diagnostics: set \code{iter_diagnostics = TRUE} to attach an -\code{iter_diagnostics} attribute (only available without chunking). Use -\code{\link[=sc_plot_iter_diagnostics]{sc_plot_iter_diagnostics()}} to visualize the convergence over iterations. +\item Iteration convergence: use \code{\link[=sc_slic_convergence]{sc_slic_convergence()}} and plot its output. \item Pixel diagnostics: \code{\link[=sc_metrics_pixels]{sc_metrics_pixels()}} for per-pixel spatial, value, and combined distances. \item Cluster diagnostics: \code{\link[=sc_metrics_supercells]{sc_metrics_supercells()}} for per-supercell summaries. @@ -101,6 +95,6 @@ Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P., & Süsstrunk, S. (2012). Nowosad, J., Stepinski, T. (2022). Extended SLIC superpixels algorithm for applications to non-imagery geospatial rasters. International Journal of Applied Earth Observation and Geoinformation, https://doi.org/10.1016/j.jag.2022.102935 } \seealso{ -\code{\link[=in_meters]{in_meters()}}, \code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_plot_iter_diagnostics]{sc_plot_iter_diagnostics()}}, +\code{\link[=in_meters]{in_meters()}}, \code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_slic_convergence]{sc_slic_convergence()}}, \code{\link[=sc_metrics_pixels]{sc_metrics_pixels()}}, \code{\link[=sc_metrics_supercells]{sc_metrics_supercells()}}, \code{\link[=sc_metrics_global]{sc_metrics_global()}} } diff --git a/man/sc_slic_convergence.Rd b/man/sc_slic_convergence.Rd new file mode 100644 index 0000000..bae0e6c --- /dev/null +++ b/man/sc_slic_convergence.Rd @@ -0,0 +1,75 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/sc_slic_convergence.R +\name{sc_slic_convergence} +\alias{sc_slic_convergence} +\alias{plot.sc_slic_convergence} +\title{SLIC convergence diagnostics} +\usage{ +sc_slic_convergence( + x, + step = NULL, + compactness, + dist_fun = "euclidean", + avg_fun = "mean", + clean = TRUE, + minarea, + iter = 10, + k = NULL, + centers = NULL, + verbose = 0 +) + +\method{plot}{sc_slic_convergence}(x, ...) +} +\arguments{ +\item{x}{An object of class SpatRaster (terra) or class stars (stars).} + +\item{step}{Initial center spacing (alternative is \code{k}). +Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +map-distance steps in meters (automatically converted to cells using raster resolution).} + +\item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. +Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} + +\item{dist_fun}{A distance function name or a custom function. Supported names: +"euclidean", "jsd", "dtw", "dtw2d", or any method from \code{philentropy::getDistMethods()}. +A custom function must accept two numeric vectors and return a single numeric value.} + +\item{avg_fun}{An averaging function name or custom function used to summarize +values within each supercell. Supported names: "mean" and "median". A custom +function must accept a numeric vector and return a single numeric value.} + +\item{clean}{Should connectivity of the supercells be enforced?} + +\item{minarea}{Minimal size of a supercell (in cells).} + +\item{iter}{Number of iterations.} + +\item{k}{The number of supercells desired (alternative to \code{step}).} + +\item{centers}{Optional sf object of custom centers. Requires \code{step}.} + +\item{verbose}{Verbosity level.} + +\item{...}{Additional arguments passed to \code{\link[graphics:plot.default]{graphics::plot()}}.} +} +\value{ +A data frame with class \code{sc_slic_convergence} and columns: +\describe{ +\item{iter}{Iteration number.} +\item{mean_distance}{Mean combined distance across assigned cells at each iteration.} +} +} +\description{ +Runs SLIC and returns per-iteration mean combined distance. +The output can be plotted directly with \code{\link[=plot.sc_slic_convergence]{plot()}}. +} +\examples{ +library(supercells) +vol = terra::rast(system.file("raster/volcano.tif", package = "supercells")) +conv = sc_slic_convergence(vol, step = 8, compactness = 5, iter = 10) +plot(conv) +} +\seealso{ +\code{\link[=sc_slic]{sc_slic()}}, \code{\link[=plot.sc_slic_convergence]{plot()}} +} diff --git a/man/sc_slic_points.Rd b/man/sc_slic_points.Rd index 68c6cca..615635d 100644 --- a/man/sc_slic_points.Rd +++ b/man/sc_slic_points.Rd @@ -17,7 +17,6 @@ sc_slic_points( centers = NULL, outcomes = "values", chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0 ) } @@ -57,9 +56,6 @@ Allowed values are "supercells", "coordinates", and "values". Default is automatic chunking based on size, or a numeric value for a fixed chunk size (in number of cells per side).} -\item{iter_diagnostics}{Logical. If \code{TRUE}, attaches iteration diagnostics as an -attribute (\code{iter_diagnostics}) on the output. Only available when chunks are not used.} - \item{verbose}{Verbosity level.} } \value{ diff --git a/man/sc_slic_raster.Rd b/man/sc_slic_raster.Rd index 0446b2f..22bffcf 100644 --- a/man/sc_slic_raster.Rd +++ b/man/sc_slic_raster.Rd @@ -17,7 +17,6 @@ sc_slic_raster( centers = NULL, outcomes = "supercells", chunks = FALSE, - iter_diagnostics = FALSE, verbose = 0 ) } @@ -56,9 +55,6 @@ Only \code{"supercells"} is supported in \code{sc_slic_raster()}.} automatic chunking based on size, or a numeric value for a fixed chunk size (in number of cells per side).} -\item{iter_diagnostics}{Logical. If \code{TRUE}, attaches iteration diagnostics as an -attribute (\code{iter_diagnostics}) on the output. Only available when chunks are not used.} - \item{verbose}{Verbosity level.} } \value{ diff --git a/tests/testthat/test-sc-slic-convergence.R b/tests/testthat/test-sc-slic-convergence.R new file mode 100644 index 0000000..f85ba20 --- /dev/null +++ b/tests/testthat/test-sc-slic-convergence.R @@ -0,0 +1,22 @@ +test_that("sc_slic_convergence returns expected structure", { + conv = sc_slic_convergence(v1, step = 8, compactness = 1, iter = 5) + expect_s3_class(conv, "sc_slic_convergence") + expect_s3_class(conv, "data.frame") + expect_true(all(c("iter", "mean_distance") %in% names(conv))) + expect_equal(nrow(conv), 5) + expect_equal(conv$iter, seq_len(5)) + expect_true(all(is.finite(conv$mean_distance))) +}) + +test_that("sc_slic_convergence validates iter", { + expect_error( + sc_slic_convergence(v1, step = 8, compactness = 1, iter = 0), + "iter must be > 0", + fixed = TRUE + ) +}) + +test_that("plot.sc_slic_convergence works", { + conv = sc_slic_convergence(v1, step = 8, compactness = 1, iter = 3) + expect_silent(plot(conv)) +}) diff --git a/vignettes/articles/v2-changes-since-v1.Rmd b/vignettes/articles/v2-changes-since-v1.Rmd index a2c703d..22ddcf2 100644 --- a/vignettes/articles/v2-changes-since-v1.Rmd +++ b/vignettes/articles/v2-changes-since-v1.Rmd @@ -73,7 +73,7 @@ terra::plot(vol_ids) ``` While, `sc_slic()` is the main function, the other two functions are useful for specific tasks. -For example, `sc_slic_points()` is helpful for visualizing initial supercell centers or iteration diagnostics, while `sc_slic_raster()` is useful for large datasets where polygon outputs may be too memory-intensive. +For example, `sc_slic_points()` is helpful for visualizing initial supercell centers, while `sc_slic_raster()` is useful for large datasets where polygon outputs may be too memory-intensive. ## Compactness tuning and iteration diagnostics @@ -92,17 +92,17 @@ plot(sf::st_geometry(vol_sc_tuned), add = TRUE, lwd = 0.6, border = "red") This function also allow to calculate the compactness using second method called `"local"`. -`sc_slic_points(..., iter_diagnostics = TRUE)` attaches iteration diagnostics so you can visualize convergence in mean distance across iterations later on with `sc_plot_iter_diagnostics()`. +`sc_slic_convergence()` provides iteration diagnostics so you can visualize convergence in mean distance across iterations. ```{r} -# Iteration diagnostics plot (only available without chunking) -vol_pts_diag <- sc_slic_points( +# Iteration diagnostics plot +vol_conv <- sc_slic_convergence( vol, step = 8, compactness = 1, - iter_diagnostics = TRUE + iter = 10 ) -sc_plot_iter_diagnostics(vol_pts_diag) +plot(vol_conv) ``` ## Metrics for evaluating results diff --git a/vignettes/articles/v2-parameters.Rmd b/vignettes/articles/v2-parameters.Rmd index 7a88cec..9c8ca7d 100644 --- a/vignettes/articles/v2-parameters.Rmd +++ b/vignettes/articles/v2-parameters.Rmd @@ -236,12 +236,12 @@ If you need unpostprocessed supercells for speed or debugging, you can set `clea # Iterations -You can inspect how the algorithm converges over iterations by enabling `iter_diagnostics = TRUE`. -This attaches diagnostics to the output and allows plotting with `sc_plot_iter_diagnostics()`.^[It only works when chunking is disabled.] +You can inspect how the algorithm converges over iterations with `sc_slic_convergence()`. +It returns a data frame with per-iteration mean distance and has a dedicated `plot()` method. ```{r} -sc_diag <- sc_slic(vol, step = 8, compactness = 5, iter_diagnostics = TRUE) -sc_plot_iter_diagnostics(sc_diag) +sc_conv <- sc_slic_convergence(vol, step = 8, compactness = 5, iter = 10) +plot(sc_conv) ``` Use the diagnostics to decide whether fewer iterations are sufficient. From 177707066b9a8da5ba72e00e70010aeb43fb960b Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 16:24:42 +0100 Subject: [PATCH 09/11] improves docs --- R/sc_tune_compactness.R | 9 ++++++--- man/sc_tune_compactness.Rd | 9 ++++++--- vignettes/articles/v2-parameters.Rmd | 5 +++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/R/sc_tune_compactness.R b/R/sc_tune_compactness.R index 0246050..2ddd6aa 100644 --- a/R/sc_tune_compactness.R +++ b/R/sc_tune_compactness.R @@ -13,9 +13,12 @@ #' @param compactness Starting compactness used for the initial short run. #' @param metric Which compactness metric to return: `"global"` or `"local"`. #' Default: `"global"`. -#' @param value_scale Optional scale factor applied to the median value distance -#' before computing compactness. Use `"auto"` to divide by `sqrt(nlyr(raster))` -#' (useful for high-dimensional embeddings). Default: `"auto"`. +#' @param value_scale Scale factor for value distances during tuning. +#' Global metric: `compactness = (median(d_value) / value_scale) * step / median(d_spatial)`. +#' Local metric: `compactness = median(local_mean(d_value) / value_scale)`. +#' `"auto"` uses `sqrt(nlyr(raster))` (good for Euclidean-like distances); +#' for bounded/angular distances (e.g., cosine), `value_scale = 1` is often better. +#' Default: `"auto"`. #' @param dist_fun A distance function name or a custom function. Supported names: #' "euclidean", "jsd", "dtw", "dtw2d", or any method from `philentropy::getDistMethods()`. #' A custom function must accept two numeric vectors and return a single numeric value. diff --git a/man/sc_tune_compactness.Rd b/man/sc_tune_compactness.Rd index 757b3a9..abf2798 100644 --- a/man/sc_tune_compactness.Rd +++ b/man/sc_tune_compactness.Rd @@ -53,9 +53,12 @@ function must accept a numeric vector and return a single numeric value.} \item{sample_size}{Optional limit on the number of pixels used for the summary (passed to \code{terra::global()} as \code{maxcell}).} -\item{value_scale}{Optional scale factor applied to the median value distance -before computing compactness. Use \code{"auto"} to divide by \code{sqrt(nlyr(raster))} -(useful for high-dimensional embeddings). Default: \code{"auto"}.} +\item{value_scale}{Scale factor for value distances during tuning. +Global metric: \code{compactness = (median(d_value) / value_scale) * step / median(d_spatial)}. +Local metric: \code{compactness = median(local_mean(d_value) / value_scale)}. +\code{"auto"} uses \code{sqrt(nlyr(raster))} (good for Euclidean-like distances); +for bounded/angular distances (e.g., cosine), \code{value_scale = 1} is often better. +Default: \code{"auto"}.} } \value{ A one-row data frame with columns \code{step}, \code{metric}, \code{dist_fun}, and \code{compactness}. diff --git a/vignettes/articles/v2-parameters.Rmd b/vignettes/articles/v2-parameters.Rmd index 9c8ca7d..3b2a547 100644 --- a/vignettes/articles/v2-parameters.Rmd +++ b/vignettes/articles/v2-parameters.Rmd @@ -148,8 +148,9 @@ sc_tuned <- sc_slic(vol, step = 8, compactness = tune_global$compactness) If your raster has many layers, the value distances can be large. The `value_scale` argument controls the scaling of value distances before the compactness estimate. -The default `"auto"` divides by `sqrt(nlyr(x))`, which is often a good baseline for high-dimensional inputs. -If your values are already standardized or on a common scale, you can set `value_scale = 1`. +Global: `compactness = (median(value) / value_scale) * step / median(spatial)`. +Local: `compactness = median(local_mean_value / value_scale)`. +Use `"auto"` (`sqrt(nlyr(x))`) for Euclidean-like distances; for bounded/angular distances (e.g., cosine), `value_scale = 1` is often better. ## Automatic compactness From fee24161a7b9db3082fb84c5db857be290681cba Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 17:13:22 +0100 Subject: [PATCH 10/11] cleans tests --- codecov.yml | 4 + tests/testthat/test-sc-create.R | 75 ++++++----- tests/testthat/test-sc-metrics.R | 127 ++++++++++-------- tests/testthat/test-sc-slic-convergence.R | 14 +- tests/testthat/test-sc-tune-compactness.R | 58 +++++--- tests/testthat/test-supercells-chunks.R | 6 - .../testthat/test-supercells-custom-centers.R | 14 -- tests/testthat/test-supercells-iter0.R | 11 -- tests/testthat/test-supercells-legacy.R | 36 +++++ tests/testthat/test-supercells-options.R | 12 -- tests/testthat/test-supercells-v1.R | 34 ----- tests/testthat/test-supercells-v3.R | 21 --- 12 files changed, 193 insertions(+), 219 deletions(-) delete mode 100644 tests/testthat/test-supercells-chunks.R delete mode 100644 tests/testthat/test-supercells-custom-centers.R delete mode 100644 tests/testthat/test-supercells-iter0.R create mode 100644 tests/testthat/test-supercells-legacy.R delete mode 100644 tests/testthat/test-supercells-options.R delete mode 100644 tests/testthat/test-supercells-v1.R delete mode 100644 tests/testthat/test-supercells-v3.R diff --git a/codecov.yml b/codecov.yml index 04c5585..60e9d9b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,5 +1,9 @@ comment: false +ignore: + - "R/sc_merge_supercells.R" + - "**/R/sc_merge_supercells.R" + coverage: status: project: diff --git a/tests/testthat/test-sc-create.R b/tests/testthat/test-sc-create.R index 798cb3a..8330411 100644 --- a/tests/testthat/test-sc-create.R +++ b/tests/testthat/test-sc-create.R @@ -1,33 +1,27 @@ -test_that("sc_slic returns sf with attributes", { +test_that("sc_slic returns core output and attributes", { sc = sc_slic(v1, step = 8, compactness = 1) expect_s3_class(sc, "sf") + expect_true("supercells" %in% class(sc)) expect_true(all(c("supercells", "x", "y") %in% names(sc))) - expect_equal(attr(sc, "step"), attr(sc, "step")) + expect_false(is.null(attr(sc, "step"))) expect_equal(attr(sc, "compactness"), 1) - expect_true("supercells" %in% class(sc)) -}) -test_that("sc_slic stores adaptive compactness as 'auto'", { - sc = sc_slic(v1, step = 8, compactness = "auto") - expect_equal(attr(sc, "compactness"), "auto") + sc_auto = sc_slic(v1, step = 8, compactness = "auto") + expect_equal(attr(sc_auto, "compactness"), "auto") }) -test_that("sc_slic handles step and centers", { +test_that("sc_slic supports custom centers", { set.seed(2021-11-21) centers = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 50, type = "random")) - sc = sc_slic(v1, centers = centers, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) + sc = sc_slic(v1, centers = centers, step = 8, compactness = 1) expect_s3_class(sc, "sf") }) -test_that("sc_slic_raster returns raster with attributes", { +test_that("sc_slic_raster matches rasterized sc_slic output", { + sc = sc_slic(v1, step = 8, compactness = 1, outcomes = "supercells") sc_r = sc_slic_raster(v1, step = 8, compactness = 1) expect_s4_class(sc_r, "SpatRaster") -}) -test_that("sc_slic_raster matches rasterized sc_slic", { - sc = sc_slic(v1, step = 8, compactness = 1, outcomes = "supercells") - sc_r = sc_slic_raster(v1, step = 8, compactness = 1) ref_r = terra::rasterize(terra::vect(sc), v1, field = "supercells") ref_vals = terra::values(ref_r, mat = FALSE) out_vals = terra::values(sc_r, mat = FALSE) @@ -42,18 +36,18 @@ test_that("sc_slic_raster assigns unique ids across chunks", { "rounded up", fixed = TRUE ) + chunk_ext = .sc_chunk_extents(dim(v1), limit = ceiling(chunks / step) * step) ranges = lapply(seq_len(nrow(chunk_ext)), function(i) { ext = chunk_ext[i, ] chunk = sc_r[ext[1]:ext[2], ext[3]:ext[4], drop = FALSE] vals = terra::values(chunk, mat = FALSE) vals = vals[!is.na(vals)] - if (length(vals) == 0) { - return(NULL) - } + if (length(vals) == 0) return(NULL) c(min = min(vals), max = max(vals)) }) ranges = Filter(Negate(is.null), ranges) + if (length(ranges) > 1) { mins = vapply(ranges, function(x) x[["min"]], numeric(1)) maxs = vapply(ranges, function(x) x[["max"]], numeric(1)) @@ -61,53 +55,60 @@ test_that("sc_slic_raster assigns unique ids across chunks", { } }) -test_that("auto chunk size aligns to step", { - step = 8 +test_that("sc_slic_raster enforces raster-specific guardrails", { + expect_error( + sc_slic_raster(v1, step = 8, compactness = 1, iter = 0), + "iter = 0 returns centers only", + fixed = TRUE + ) + expect_error( + sc_slic_raster(v1, step = 8, compactness = 1, outcomes = "values"), + "supports only outcomes = 'supercells'", + fixed = TRUE + ) +}) + +test_that("sc_slic_raster works with chunks = TRUE", { old_opt = getOption("supercells.chunk_mem_gb") options(supercells.chunk_mem_gb = 0.001) on.exit(options(supercells.chunk_mem_gb = old_opt), add = TRUE) - wsize = .sc_chunk_optimize_size(dim(v1), getOption("supercells.chunk_mem_gb"), step = step) - expect_true(wsize %% step == 0) - expect_true(wsize >= step) + + sc_r = sc_slic_raster(v1, step = 8, compactness = 1, chunks = TRUE) + expect_s4_class(sc_r, "SpatRaster") + expect_equal(names(sc_r), "supercells") }) -test_that("sc_slic validates arguments", { +test_that("sc_slic validates key argument errors", { expect_error(sc_slic(v1, k = 10), "compactness", fixed = TRUE) expect_error(sc_slic(v1, k = 10, step = 5, compactness = 1), "either 'k' or 'step'", fixed = TRUE) expect_error(sc_slic(v1, centers = sf::st_sf(geom = sf::st_sfc()), compactness = 1), "step", fixed = TRUE) expect_error(sc_slic(v1, step = 8, compactness = 1, dist_fun = "not_a_dist"), "does not exist", fixed = TRUE) }) -test_that("sc_slic accepts units-based map step", { +test_that("sc_slic handles step units and unit-related errors", { step_map = in_meters(8 * terra::res(v1)[1]) sc = sc_slic(v1, step = step_map, compactness = 1) expect_s3_class(attr(sc, "step"), "units") expect_equal(as.numeric(units::drop_units(attr(sc, "step"))), as.numeric(units::drop_units(step_map))) -}) -test_that("sc_slic rejects units-based step for lonlat rasters", { - x = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 1, ymin = 0, ymax = 1, crs = "EPSG:4326") - terra::values(x) = seq_len(terra::ncell(x)) + x_lonlat = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 1, ymin = 0, ymax = 1, crs = "EPSG:4326") + terra::values(x_lonlat) = seq_len(terra::ncell(x_lonlat)) expect_error( - suppressWarnings(sc_slic(x, step = in_meters(1000), compactness = 1)), + suppressWarnings(sc_slic(x_lonlat, step = in_meters(1000), compactness = 1)), "projected CRS", fixed = TRUE ) -}) -test_that("sc_slic rejects non-meter units for step", { expect_error( sc_slic(v1, step = units::set_units(1, "km"), compactness = 1), "must use meters", fixed = TRUE ) -}) -test_that("sc_slic rejects units-based step for projected non-meter CRS", { - x = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 5000, ymin = 0, ymax = 5000, crs = "EPSG:2277") - terra::values(x) = seq_len(terra::ncell(x)) + x_non_meter = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 5000, ymin = 0, ymax = 5000, crs = "EPSG:2277") + terra::values(x_non_meter) = seq_len(terra::ncell(x_non_meter)) expect_error( - suppressWarnings(sc_slic(x, step = in_meters(100), compactness = 1)), + suppressWarnings(sc_slic(x_non_meter, step = in_meters(100), compactness = 1)), "meter units", fixed = TRUE ) diff --git a/tests/testthat/test-sc-metrics.R b/tests/testthat/test-sc-metrics.R index 968ffae..d31a7c8 100644 --- a/tests/testthat/test-sc-metrics.R +++ b/tests/testthat/test-sc-metrics.R @@ -1,68 +1,53 @@ -test_that("sc_metrics_pixels returns raster with layers", { - sc = sc_slic(v1, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) - pix = sc_metrics_pixels(v1, sc) +sc_full = sc_slic(v1, step = 8, compactness = 1, + outcomes = c("supercells", "coordinates", "values")) +sc_values = sc_slic(v1, step = 8, compactness = 1, outcomes = "values") +sc_auto = sc_slic(v1, step = 8, compactness = "auto", + outcomes = c("supercells", "coordinates", "values")) +manhattan = function(a, b) sum(abs(a - b)) +sc_custom = sc_slic(v1, step = 8, compactness = 1, dist_fun = manhattan, + outcomes = c("supercells", "coordinates", "values")) + +test_that("metrics outputs have expected structure", { + pix = sc_metrics_pixels(v1, sc_full) expect_s4_class(pix, "SpatRaster") expect_equal(terra::nlyr(pix), 4) expect_true(all(c("spatial_scaled", "value_scaled", "combined", "balance") %in% names(pix))) -}) - -test_that("sc_metrics functions work without metadata columns", { - sc = sc_slic(v1, step = 8, compactness = 1, outcomes = "values") - pix = sc_metrics_pixels(v1, sc) - expect_s4_class(pix, "SpatRaster") - cl = sc_metrics_supercells(v1, sc) - expect_s3_class(cl, "sf") - - gl = sc_metrics_global(v1, sc) - expect_s3_class(gl, "data.frame") -}) - -test_that("sc_metrics uses step and compactness from attributes", { - sc = sc_slic(v1, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) - gl = sc_metrics_global(v1, sc) - expect_equal(gl$step, attr(sc, "step")) - expect_equal(gl$compactness, attr(sc, "compactness")) -}) - -test_that("sc_metrics uses adaptive compactness from compactness attribute", { - sc = sc_slic(v1, step = 8, compactness = "auto", - outcomes = c("supercells", "coordinates", "values")) - gl = sc_metrics_global(v1, sc) - expect_equal(gl$compactness, "auto") -}) - -test_that("sc_metrics_supercells returns sf with metrics", { - sc = sc_slic(v1, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) - cl = sc_metrics_supercells(v1, sc) + cl = sc_metrics_supercells(v1, sc_full) expect_s3_class(cl, "sf") expect_true(all(c("supercells", "mean_value_dist_scaled", "mean_spatial_dist_scaled", "mean_combined_dist", "balance") %in% names(cl))) - expect_true(all(is.finite(cl[["balance"]]) | is.na(cl[["balance"]]))) -}) -test_that("sc_metrics_global returns single-row data.frame", { - sc = sc_slic(v1, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) - gl = sc_metrics_global(v1, sc) + gl = sc_metrics_global(v1, sc_full) expect_s3_class(gl, "data.frame") expect_equal(nrow(gl), 1) expect_true(all(c("step", "compactness", "n_supercells", - "mean_value_dist_scaled", "mean_spatial_dist_scaled", "mean_combined_dist", - "balance") %in% names(gl))) - expect_true(is.finite(gl[["balance"]]) | is.na(gl[["balance"]])) + "mean_value_dist_scaled", "mean_spatial_dist_scaled", + "mean_combined_dist", "balance") %in% names(gl))) }) -test_that("sc_metrics invalid dist_fun errors", { - sc = sc_slic(v1, step = 8, compactness = 1, - outcomes = c("supercells", "coordinates", "values")) - expect_error(sc_metrics_pixels(v1, sc, dist_fun = "not_a_dist"), "does not exist", fixed = TRUE) +test_that("metrics work without metadata columns", { + expect_s4_class(sc_metrics_pixels(v1, sc_values), "SpatRaster") + expect_s3_class(sc_metrics_supercells(v1, sc_values), "sf") + expect_s3_class(sc_metrics_global(v1, sc_values), "data.frame") +}) + +test_that("metrics use stored attributes and dist_fun defaults", { + gl = sc_metrics_global(v1, sc_full) + expect_equal(gl$step, attr(sc_full, "step")) + expect_equal(gl$compactness, attr(sc_full, "compactness")) + + gl_auto = sc_metrics_global(v1, sc_auto) + expect_equal(gl_auto$compactness, "auto") + + g_attr = sc_metrics_global(v1, sc_custom) + g_explicit = sc_metrics_global(v1, sc_custom, dist_fun = manhattan) + expect_equal(g_attr$mean_value_dist_scaled, g_explicit$mean_value_dist_scaled, tolerance = 1e-8) + expect_equal(g_attr$mean_spatial_dist_scaled, g_explicit$mean_spatial_dist_scaled, tolerance = 1e-8) + expect_equal(g_attr$mean_combined_dist, g_explicit$mean_combined_dist, tolerance = 1e-8) }) -test_that("sc_metrics spatial units follow step encoding", { +test_that("metrics follow step encoding in cells vs meters", { v1_map = terra::aggregate(v1, fact = 2, fun = mean, na.rm = TRUE) res_map = terra::res(v1_map)[1] step_cells = 8 @@ -84,13 +69,39 @@ test_that("sc_metrics spatial units follow step encoding", { tolerance = 1e-6) }) -test_that("sc_metrics defaults to dist_fun attribute when missing", { - manhattan = function(a, b) sum(abs(a - b)) - sc = sc_slic(v1, step = 8, compactness = 1, dist_fun = manhattan, +test_that("metrics reject invalid dist_fun", { + expect_error(sc_metrics_pixels(v1, sc_full, dist_fun = "not_a_dist"), "does not exist", fixed = TRUE) +}) + +test_that("metrics work after save/read when explicit args are supplied", { + sc = sc_slic(v1, step = 8, compactness = 1, outcomes = c("supercells", "coordinates", "values")) - g_attr = sc_metrics_global(v1, sc) - g_explicit = sc_metrics_global(v1, sc, dist_fun = manhattan) - expect_equal(g_attr$mean_value_dist_scaled, g_explicit$mean_value_dist_scaled, tolerance = 1e-8) - expect_equal(g_attr$mean_spatial_dist_scaled, g_explicit$mean_spatial_dist_scaled, tolerance = 1e-8) - expect_equal(g_attr$mean_combined_dist, g_explicit$mean_combined_dist, tolerance = 1e-8) + gpkg = tempfile(fileext = ".gpkg") + sf::st_write(sc, gpkg, quiet = TRUE) + sc_disk = sf::st_read(gpkg, quiet = TRUE) + unlink(gpkg) + + expect_error( + sc_metrics_global(v1, sc_disk), + "required when it is not stored in 'sc'", + fixed = TRUE + ) + + gl = sc_metrics_global(v1, sc_disk, step = 8, compactness = 1, dist_fun = "euclidean") + expect_s3_class(gl, "data.frame") + expect_equal(nrow(gl), 1) + + pix = sc_metrics_pixels( + v1, sc_disk, + step = 8, compactness = 1, dist_fun = "euclidean", + metrics = "combined" + ) + expect_s4_class(pix, "SpatRaster") + + cl = sc_metrics_supercells( + v1, sc_disk, + step = 8, compactness = 1, dist_fun = "euclidean", + metrics = "combined" + ) + expect_s3_class(cl, "sf") }) diff --git a/tests/testthat/test-sc-slic-convergence.R b/tests/testthat/test-sc-slic-convergence.R index f85ba20..26846ae 100644 --- a/tests/testthat/test-sc-slic-convergence.R +++ b/tests/testthat/test-sc-slic-convergence.R @@ -1,4 +1,4 @@ -test_that("sc_slic_convergence returns expected structure", { +test_that("sc_slic_convergence returns plottable convergence data", { conv = sc_slic_convergence(v1, step = 8, compactness = 1, iter = 5) expect_s3_class(conv, "sc_slic_convergence") expect_s3_class(conv, "data.frame") @@ -6,6 +6,13 @@ test_that("sc_slic_convergence returns expected structure", { expect_equal(nrow(conv), 5) expect_equal(conv$iter, seq_len(5)) expect_true(all(is.finite(conv$mean_distance))) + tmp = tempfile(fileext = ".pdf") + grDevices::pdf(tmp) + on.exit({ + grDevices::dev.off() + unlink(tmp) + }, add = TRUE) + expect_silent(plot(conv)) }) test_that("sc_slic_convergence validates iter", { @@ -15,8 +22,3 @@ test_that("sc_slic_convergence validates iter", { fixed = TRUE ) }) - -test_that("plot.sc_slic_convergence works", { - conv = sc_slic_convergence(v1, step = 8, compactness = 1, iter = 3) - expect_silent(plot(conv)) -}) diff --git a/tests/testthat/test-sc-tune-compactness.R b/tests/testthat/test-sc-tune-compactness.R index 207ce12..8ed7c6e 100644 --- a/tests/testthat/test-sc-tune-compactness.R +++ b/tests/testthat/test-sc-tune-compactness.R @@ -1,23 +1,14 @@ -test_that("sc_tune_compactness returns one-row data frame", { - tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500) - expect_s3_class(tune, "data.frame") - expect_equal(nrow(tune), 1) - expect_true(all(c("step", "metric", "dist_fun", "compactness") %in% names(tune))) - expect_equal(tune$step, 8) - expect_equal(tune$metric, "global") - expect_equal(tune$dist_fun, "euclidean") - expect_true(tune$compactness > 0) -}) - -test_that("sc_tune_compactness returns local compactness when requested", { - tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, metric = "local") - expect_s3_class(tune, "data.frame") - expect_equal(nrow(tune), 1) - expect_true(all(c("step", "metric", "dist_fun", "compactness") %in% names(tune))) - expect_equal(tune$step, 8) - expect_equal(tune$metric, "local") - expect_equal(tune$dist_fun, "euclidean") - expect_true(tune$compactness > 0) +test_that("sc_tune_compactness returns one-row output for both metrics", { + for (metric in c("global", "local")) { + tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, metric = metric) + expect_s3_class(tune, "data.frame") + expect_equal(nrow(tune), 1) + expect_true(all(c("step", "metric", "dist_fun", "compactness") %in% names(tune))) + expect_equal(tune$step, 8) + expect_equal(tune$metric, metric) + expect_equal(tune$dist_fun, "euclidean") + expect_true(tune$compactness > 0) + } }) test_that("sc_tune_compactness stores custom dist_fun label", { @@ -25,3 +16,30 @@ test_that("sc_tune_compactness stores custom dist_fun label", { tune = sc_tune_compactness(v1, step = 8, iter = 1, sample_size = 500, dist_fun = manhattan) expect_equal(tune$dist_fun, "custom") }) + +test_that("sc_tune_compactness value_scale rescales compactness and validates input", { + g1 = sc_tune_compactness( + v1, step = 8, iter = 1, metric = "global", + value_scale = 1, sample_size = terra::ncell(v1) + ) + g2 = sc_tune_compactness( + v1, step = 8, iter = 1, metric = "global", + value_scale = 2, sample_size = terra::ncell(v1) + ) + expect_equal(g1$compactness / g2$compactness, 2, tolerance = 1e-6) + + l1 = sc_tune_compactness(v1, step = 8, iter = 1, metric = "local", value_scale = 1) + l2 = sc_tune_compactness(v1, step = 8, iter = 1, metric = "local", value_scale = 2) + expect_equal(l1$compactness / l2$compactness, 2, tolerance = 1e-6) + + expect_error( + sc_tune_compactness(v1, step = 8, value_scale = 0), + "value_scale must be a single positive number or 'auto'", + fixed = TRUE + ) + expect_error( + sc_tune_compactness(v1, step = 8, value_scale = "bad"), + "value_scale must be a single positive number or 'auto'", + fixed = TRUE + ) +}) diff --git a/tests/testthat/test-supercells-chunks.R b/tests/testthat/test-supercells-chunks.R deleted file mode 100644 index be67b2b..0000000 --- a/tests/testthat/test-supercells-chunks.R +++ /dev/null @@ -1,6 +0,0 @@ -v3_a = supercells(v3, 100, compactness = 1, chunk = 150) - -test_that("supercells works for many chunks", { - expect_equal(ncol(v3_a), 7) - expect_equal(as.numeric(sf::st_bbox(v3_a)), unname(as.vector(terra::ext(v3)))[c(1, 3, 2, 4)]) -}) diff --git a/tests/testthat/test-supercells-custom-centers.R b/tests/testthat/test-supercells-custom-centers.R deleted file mode 100644 index b0413ac..0000000 --- a/tests/testthat/test-supercells-custom-centers.R +++ /dev/null @@ -1,14 +0,0 @@ -set.seed(2021-11-21) -y1 = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 100, type = "random")) -y2 = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 100, type = "regular")) -y3 = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 100, type = "hexagonal")) - -vol_slic1 = supercells(v1, k = y1, step = 10, compactness = 1, iter = 10, clean = TRUE) -vol_slic2 = supercells(v1, k = y2, step = 10, compactness = 1, iter = 10, clean = TRUE) -vol_slic3 = supercells(v1, k = y3, step = 10, compactness = 1, iter = 10, clean = TRUE) - -test_that("supercells works for custom centers", { - expect_equal(ncol(vol_slic1), 5) - expect_equal(ncol(vol_slic2), 5) - expect_equal(ncol(vol_slic3), 5) -}) diff --git a/tests/testthat/test-supercells-iter0.R b/tests/testthat/test-supercells-iter0.R deleted file mode 100644 index 399177a..0000000 --- a/tests/testthat/test-supercells-iter0.R +++ /dev/null @@ -1,11 +0,0 @@ -set.seed(2021-11-20) -y = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 100, type = "random")) - -vol_slic0a = supercells(v1, k = y, step = 10, compactness = 1, iter = 0) -vol_slic0b = supercells(v1, step = 10, compactness = 1, iter = 0) - -test_that("supercells works for 0 iter", { - expect_equal(ncol(vol_slic0a), 1 + 2 + terra::nlyr(v1) + 1) # supercells + x,y + layers + geometry - expect_equal(nrow(vol_slic0a), 98) - expect_equal(nrow(vol_slic0b), 54) -}) diff --git a/tests/testthat/test-supercells-legacy.R b/tests/testthat/test-supercells-legacy.R new file mode 100644 index 0000000..2eed1b0 --- /dev/null +++ b/tests/testthat/test-supercells-legacy.R @@ -0,0 +1,36 @@ +test_that("legacy supercells wrapper handles basic options", { + sc = supercells(v1, k = 100, compactness = 1) + expect_s3_class(sc, "sf") + expect_true(all(c("supercells", "x", "y") %in% names(sc))) + + sc_no_meta = supercells(v1, k = 30, compactness = 1, metadata = FALSE) + expect_false(any(c("supercells", "x", "y") %in% names(sc_no_meta))) + + expect_error( + supercells(v1, k = 10, compactness = 1, dist_fun = "not_a_dist"), + "does not exist", + fixed = TRUE + ) +}) + +test_that("legacy supercells wrapper supports iter = 0 and custom centers", { + set.seed(2021-11-20) + centers = sf::st_sf(geom = sf::st_sample(sf::st_as_sfc(sf::st_bbox(v1)), 100, type = "random")) + + sc_custom = supercells(v1, k = centers, step = 10, compactness = 1, iter = 0) + sc_default = supercells(v1, step = 10, compactness = 1, iter = 0) + expect_s3_class(sc_custom, "sf") + expect_s3_class(sc_default, "sf") + expect_true(nrow(sc_custom) > 0) + expect_true(nrow(sc_default) > 0) +}) + +test_that("legacy supercells wrapper supports transform and chunked calls", { + sc_lab = supercells(v3, 100, compactness = 1, transform = "to_LAB") + sc_chunk = supercells(v3, 100, compactness = 1, chunk = 150) + + expect_s3_class(sc_lab, "sf") + expect_s3_class(sc_chunk, "sf") + expect_equal(as.numeric(sf::st_bbox(sc_lab)), unname(as.vector(terra::ext(v3)))[c(1, 3, 2, 4)]) + expect_equal(as.numeric(sf::st_bbox(sc_chunk)), unname(as.vector(terra::ext(v3)))[c(1, 3, 2, 4)]) +}) diff --git a/tests/testthat/test-supercells-options.R b/tests/testthat/test-supercells-options.R deleted file mode 100644 index c959440..0000000 --- a/tests/testthat/test-supercells-options.R +++ /dev/null @@ -1,12 +0,0 @@ -test_that("metadata columns can be removed", { - v1_no_meta = supercells(v1, k = 30, compactness = 1, metadata = FALSE) - expect_false(any(c("supercells", "x", "y") %in% names(v1_no_meta))) -}) - -test_that("invalid dist_fun errors", { - expect_error( - supercells(v1, k = 10, compactness = 1, dist_fun = "not_a_dist"), - "does not exist", - fixed = TRUE - ) -}) diff --git a/tests/testthat/test-supercells-v1.R b/tests/testthat/test-supercells-v1.R deleted file mode 100644 index 673681f..0000000 --- a/tests/testthat/test-supercells-v1.R +++ /dev/null @@ -1,34 +0,0 @@ -v1_a = supercells(v1, 100, compactness = 1) -v1_b = supercells(v1, 100, compactness = 1, clean = FALSE) -v1_c = supercells(v1, step = 8, compactness = 1) -my_minarea = 3 -v1_d = supercells(v1, step = 8, compactness = 1, minarea = my_minarea) -v1_e = supercells(v1, step = 8, compactness = 0.1, avg_fun = "median", dist_fun = "jaccard") -v1_f = supercells(v1, 100, compactness = 1, avg_fun = mean) - -test_that("supercells works for 1 var", { - expect_equal(ncol(v1_a), 5) - expect_equal(ncol(v1_a), ncol(v1_b)) - expect_equal(nrow(v1_a), 90) - expect_equal(nrow(v1_b), 88) - expect_equal(nrow(v1_e), 88) - expect_equal(v1_a, v1_c) - expect_true(all(as.numeric(sf::st_area(v1_d)) >= xres(v1) * yres(v1) * my_minarea)) - expect_equal(v1_a, v1_f) -}) - -# test_that("supercells matches reference (no geometry)", { -# ref_path = testthat::test_path("testdata", "v1-supercells-v1.rds") -# testthat::skip_if_not(file.exists(ref_path), "Reference file missing; create with old package version.") - -# current = list( -# v1_a = sf::st_drop_geometry(v1_a), -# v1_b = sf::st_drop_geometry(v1_b), -# v1_c = sf::st_drop_geometry(v1_c), -# v1_d = sf::st_drop_geometry(v1_d), -# v1_e = sf::st_drop_geometry(v1_e), -# v1_f = sf::st_drop_geometry(v1_f) -# ) -# reference = readRDS(ref_path) -# expect_equal(current, reference, tolerance = 1e-6) -# }) diff --git a/tests/testthat/test-supercells-v3.R b/tests/testthat/test-supercells-v3.R deleted file mode 100644 index 18111c2..0000000 --- a/tests/testthat/test-supercells-v3.R +++ /dev/null @@ -1,21 +0,0 @@ -v3_a = supercells(v3, 100, compactness = 1) -v3_b = supercells(v3, 100, compactness = 1, transform = "to_LAB") - -test_that("supercells works for 3 var", { - expect_equal(ncol(v3_a), 7) - # expect_equal(nrow(v3_a), 80) - # expect_equal(nrow(v3_b), 86) - expect_equal(as.numeric(sf::st_bbox(v3_a)), unname(as.vector(terra::ext(v3)))[c(1, 3, 2, 4)]) -}) - -# test_that("supercells matches reference (no geometry)", { -# ref_path = testthat::test_path("testdata", "v3-supercells-v1.rds") -# testthat::skip_if_not(file.exists(ref_path), "Reference file missing; create with old package version.") - -# current = list( -# v3_a = sf::st_drop_geometry(v3_a), -# v3_b = sf::st_drop_geometry(v3_b) -# ) -# reference = readRDS(ref_path) -# expect_equal(current, reference, tolerance = 1e-6) -# }) From 4be777dd2e66b18865a1f274cd4607a843db3ba5 Mon Sep 17 00:00:00 2001 From: jn Date: Sun, 8 Feb 2026 18:31:36 +0100 Subject: [PATCH 11/11] introduces use_meters and use_adaptive --- NAMESPACE | 3 ++- NEWS.md | 2 ++ R/helpers-chunks.R | 8 ------- R/helpers-general.R | 17 +++++++++----- R/helpers-metrics.R | 24 +++++++++++--------- R/helpers-runners.R | 13 +---------- R/helpers-sc_slic.R | 18 +++++---------- R/sc_metrics_global.R | 15 +++++++++---- R/sc_metrics_pixels.R | 7 +++--- R/sc_metrics_supercells.R | 4 ++-- R/sc_slic.R | 9 ++++---- R/sc_tune_compactness.R | 4 ++-- R/supercells.R | 4 ++-- R/use_adaptive.R | 21 +++++++++++++++++ R/{in_meters.R => use_meters.R} | 6 ++--- man/sc_metrics_global.Rd | 10 +++++---- man/sc_metrics_pixels.Rd | 7 +++--- man/sc_metrics_supercells.Rd | 7 +++--- man/sc_slic.Rd | 9 ++++---- man/sc_slic_convergence.Rd | 4 ++-- man/sc_slic_points.Rd | 4 ++-- man/sc_slic_raster.Rd | 4 ++-- man/sc_tune_compactness.Rd | 4 ++-- man/supercells.Rd | 4 ++-- man/use_adaptive.Rd | 24 ++++++++++++++++++++ man/{in_meters.Rd => use_meters.Rd} | 12 +++++----- tests/testthat/test-sc-create.R | 26 ++++++++++++++-------- tests/testthat/test-sc-metrics.R | 10 +++++---- vignettes/articles/v2-changes-since-v1.Rmd | 6 ++--- vignettes/articles/v2-evaluation.Rmd | 6 ++--- vignettes/articles/v2-intro.Rmd | 2 +- vignettes/articles/v2-parameters.Rmd | 20 ++++++++--------- 32 files changed, 186 insertions(+), 128 deletions(-) create mode 100644 R/use_adaptive.R rename R/{in_meters.R => use_meters.R} (76%) create mode 100644 man/use_adaptive.Rd rename man/{in_meters.Rd => use_meters.Rd} (61%) diff --git a/NAMESPACE b/NAMESPACE index ed37602..b7d2867 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,7 +1,6 @@ # Generated by roxygen2: do not edit by hand S3method(plot,sc_slic_convergence) -export(in_meters) export(sc_metrics_global) export(sc_metrics_pixels) export(sc_metrics_supercells) @@ -11,4 +10,6 @@ export(sc_slic_points) export(sc_slic_raster) export(sc_tune_compactness) export(supercells) +export(use_adaptive) +export(use_meters) useDynLib(supercells, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index 8b9d75d..f628f9c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -2,6 +2,8 @@ * Added `outcomes` argument to `sc_slic()`, `sc_slic_points()`, and `sc_slic_raster()`; replaces `metadata` for controlling returned fields * Iteration diagnostics API redesigned: `iter_diagnostics` and `sc_plot_iter_diagnostics()` replaced by `sc_slic_convergence()` with a `plot()` method +* Added `use_meters()` for map-distance step values (replacing `in_meters()`) +* Added `use_adaptive()` for adaptive compactness mode (replacing `compactness = "auto"`) * Added experimental `sc_merge_supercells()` for adjacency-constrained greedy merging * Added `sc_dist_vec_cpp()` (C++ distance wrapper) to support merge utilities * Documentation and vignettes updated (pkgdown refresh, new articles, and revised examples) diff --git a/R/helpers-chunks.R b/R/helpers-chunks.R index 8fc1461..654060d 100644 --- a/R/helpers-chunks.R +++ b/R/helpers-chunks.R @@ -143,14 +143,6 @@ as.integer(ceiling(nrows / step) * ceiling(ncols / step)) } -# deterministic per-chunk id offsets based on expected supercell counts -.sc_chunk_offsets = function(chunk_ext, step) { - expected = .sc_chunk_expected_max_ids(chunk_ext, step) - offsets = cumsum(c(0L, expected[-length(expected)])) - storage.mode(offsets) = "double" - offsets -} - # choose a compact integer datatype based on expected max id .sc_chunk_id_datatype = function(max_id) { if (max_id <= 255) { diff --git a/R/helpers-general.R b/R/helpers-general.R index a54d126..aa8200b 100644 --- a/R/helpers-general.R +++ b/R/helpers-general.R @@ -1,8 +1,3 @@ -# check if raster is in memory -.sc_util_in_memory = function(x){ - terra::sources(x) == "" -} - # normalize input to SpatRaster .sc_util_prep_raster = function(x) { if (inherits(x, "SpatRaster")) { @@ -95,3 +90,15 @@ list(step = step_cells, step_meta = step_meta, spatial_scale = spatial_scale, step_scale = step_cells * spatial_scale) } + +# normalize compactness input for slic/metrics workflows +.sc_util_prep_compactness = function(compactness) { + if (is.numeric(compactness) && length(compactness) == 1 && !is.na(compactness) && compactness > 0) { + return(list(value = compactness, adaptive = FALSE, adaptive_method = NULL)) + } + + if (inherits(compactness, "sc_adaptive")) { + return(list(value = 0, adaptive = TRUE, adaptive_method = compactness$method)) + } + stop("The 'compactness' argument must be a single positive number or use_adaptive()", call. = FALSE) +} diff --git a/R/helpers-metrics.R b/R/helpers-metrics.R index 05187f7..2da4606 100644 --- a/R/helpers-metrics.R +++ b/R/helpers-metrics.R @@ -44,6 +44,10 @@ if (missing(compactness)) { compactness = attr(sc, "compactness") } + adaptive_method = attr(sc, "adaptive_method") + if (!is.null(adaptive_method) && is.null(compactness)) { + compactness = 0 + } if (missing(step)) { step = attr(sc, "step") } @@ -51,14 +55,14 @@ stop("Both 'compactness' and 'step' are required", call. = FALSE) } - compactness_input = compactness - adaptive_compactness = FALSE - if (is.character(compactness)) { - if (length(compactness) != 1 || is.na(compactness) || compactness != "auto") { - stop("The 'compactness' argument must be numeric or 'auto'", call. = FALSE) + if (!is.null(adaptive_method)) { + if (!is.character(adaptive_method) || length(adaptive_method) != 1 || is.na(adaptive_method) || + adaptive_method != "local_max") { + stop("The 'adaptive_method' attribute must be 'local_max' or NULL", call. = FALSE) } - adaptive_compactness = TRUE - compactness = 0 + compactness_prep = list(value = 0, adaptive = TRUE, adaptive_method = adaptive_method) + } else { + compactness_prep = .sc_util_prep_compactness(compactness) } step_prep = .sc_util_step_to_cells(raster, step) step = step_prep$step @@ -93,9 +97,9 @@ sc = sc_work, step = step, step_meta = step_prep$step_meta, - compactness = compactness, - compactness_input = compactness_input, - adaptive_compactness = adaptive_compactness, + compactness = compactness_prep$value, + adaptive_compactness = compactness_prep$adaptive, + adaptive_method = compactness_prep$adaptive_method, spatial_scale = spatial_scale, step_scale = step_scale ) diff --git a/R/helpers-runners.R b/R/helpers-runners.R index a2d46f1..c27b7e4 100644 --- a/R/helpers-runners.R +++ b/R/helpers-runners.R @@ -24,8 +24,7 @@ raster_ref = x list(centers = slic[[2]], centers_vals = slic[[3]], - iter_diagnostics = slic[[4]], names_x = names(x), - raster_ref = raster_ref) + names_x = names(x), raster_ref = raster_ref) } # run slic on the raster chunk defined by ext @@ -116,10 +115,6 @@ minarea, input_centers, iter_diagnostics = iter_diagnostics, verbose = verbose) points_sf = .sc_run_centers_points(res$centers, res$raster_ref, res$centers_vals, res$names_x) - # points_sf = stats::na.omit(points_sf) - if (iter_diagnostics && !is.null(res$iter_diagnostics)) { - attr(points_sf, "iter_diagnostics") = res$iter_diagnostics - } return(points_sf) } res = .sc_run_chunk_raster(ext, x, step, compactness, dist_name, @@ -132,9 +127,6 @@ points_sf = points_sf[points_sf[["supercells"]] %in% ids, , drop = FALSE] points_sf = points_sf[match(ids, points_sf[["supercells"]]), , drop = FALSE] points_sf = stats::na.omit(points_sf) - if (iter_diagnostics && !is.null(res$iter_diagnostics)) { - attr(points_sf, "iter_diagnostics") = res$iter_diagnostics - } return(points_sf) } @@ -162,9 +154,6 @@ } slic_sf = cbind(slic_sf, centers_df) slic_sf = suppressWarnings(sf::st_collection_extract(slic_sf, "POLYGON")) - if (iter_diagnostics && !is.null(res$iter_diagnostics)) { - attr(slic_sf, "iter_diagnostics") = res$iter_diagnostics - } return(slic_sf) } } diff --git a/R/helpers-sc_slic.R b/R/helpers-sc_slic.R index 668ecfc..4c02b87 100644 --- a/R/helpers-sc_slic.R +++ b/R/helpers-sc_slic.R @@ -35,20 +35,16 @@ # Compute chunk extents based on size/limits chunk_ext = .sc_chunk_extents(dim(x), limit = chunks, step = step) verbose_cpp = if (is.numeric(verbose) && length(verbose) == 1 && !is.na(verbose) && verbose >= 2) verbose else 0 - compactness_input = compactness - adaptive_compactness = is.character(compactness) && - length(compactness) == 1 && !is.na(compactness) && compactness == "auto" - if (adaptive_compactness) { - compactness = 0 - } + compactness_prep = .sc_util_prep_compactness(compactness) # Package prep results for downstream functions return(list(x = x, step = step, step_meta = step_meta, dist_fun_input = dist_fun, input_centers = input_centers, funs = funs, minarea = minarea, chunk_ext = chunk_ext, outcomes = outcomes, - compactness = compactness, compactness_input = compactness_input, - adaptive_compactness = adaptive_compactness, + compactness = compactness_prep$value, + adaptive_compactness = compactness_prep$adaptive, + adaptive_method = compactness_prep$adaptive_method, clean = clean, iter = iter, verbose = verbose, verbose_cpp = verbose_cpp)) } @@ -106,9 +102,6 @@ if (missing(compactness)) { stop("The 'compactness' argument is required", call. = FALSE) } - if (!is.numeric(compactness) && !(is.character(compactness) && length(compactness) == 1 && !is.na(compactness) && compactness == "auto")) { - stop("The 'compactness' argument must be numeric or 'auto'", call. = FALSE) - } if (!missing(minarea) && (!is.numeric(minarea) || length(minarea) != 1 || is.na(minarea) || minarea < 0)) { stop("The 'minarea' argument must be a non-negative numeric value", call. = FALSE) } @@ -204,7 +197,8 @@ slic_sf = .sc_slic_select_outcomes(slic_sf, prep$outcomes) attr(slic_sf, "step") = prep$step_meta - attr(slic_sf, "compactness") = prep$compactness_input + attr(slic_sf, "compactness") = prep$compactness + attr(slic_sf, "adaptive_method") = prep$adaptive_method attr(slic_sf, "dist_fun") = prep$dist_fun_input cls = class(slic_sf) cls = c(setdiff(cls, "data.frame"), "supercells", "data.frame") diff --git a/R/sc_metrics_global.R b/R/sc_metrics_global.R index abaf3e4..7ebaa8a 100644 --- a/R/sc_metrics_global.R +++ b/R/sc_metrics_global.R @@ -8,7 +8,7 @@ #' Use `outcomes = c("supercells", "coordinates", "values")` when calling #' `sc_slic()` or `supercells()` to preserve original centers and IDs. #' Metrics are averaged across supercells (each supercell has equal weight). -#' When using SLIC0 (set `compactness = "auto"` in [sc_slic()]), combined and balance metrics use per-supercell +#' When using SLIC0 (set `compactness = use_adaptive()` in [sc_slic()]), combined and balance metrics use per-supercell #' adaptive compactness (SLIC0), and scaled value distances are computed with the #' per-supercell max value distance. #' @@ -29,8 +29,9 @@ #' } #' \describe{ #' \item{step}{Step size used to generate supercells. Returned in meters when -#' the input used `step = in_meters(...)`, otherwise in cells.} +#' the input used `step = use_meters(...)`, otherwise in cells.} #' \item{compactness}{Compactness value used to generate supercells.} +#' \item{adaptive_method}{Adaptive compactness method; `NA` for fixed compactness.} #' \item{n_supercells}{Number of supercells with at least one non-missing pixel.} #' \item{mean_value_dist}{Mean per-supercell value distance from cells to their #' supercell centers, averaged across supercells. Returned as `mean_value_dist` @@ -38,7 +39,7 @@ #' \item{mean_spatial_dist}{Mean per-supercell spatial distance from cells to #' their supercell centers, averaged across supercells; units are grid cells #' (row/column index distance). If the input supercells were created with -#' `step = in_meters(...)`, distances are reported in meters. Returned as +#' `step = use_meters(...)`, distances are reported in meters. Returned as #' `mean_spatial_dist` (or `mean_spatial_dist_scaled` when `scale = TRUE`).} #' \item{mean_combined_dist}{Mean per-supercell combined distance, computed from #' value and spatial distances using `compactness` and `step`, averaged across @@ -94,8 +95,14 @@ sc_metrics_global = function(x, sc, ) names(out_metrics) = unname(name_map[metrics]) step_out = prep$step_meta + adaptive_method_out = prep$adaptive_method + if (is.null(adaptive_method_out)) { + adaptive_method_out = NA_character_ + } results = cbind( - data.frame(step = step_out, compactness = prep$compactness_input, n_supercells = out[["n_supercells"]]), + data.frame(step = step_out, compactness = prep$compactness, + adaptive_method = adaptive_method_out, + n_supercells = out[["n_supercells"]]), out_metrics ) return(results) diff --git a/R/sc_metrics_pixels.R b/R/sc_metrics_pixels.R index 591f91d..6f65f17 100644 --- a/R/sc_metrics_pixels.R +++ b/R/sc_metrics_pixels.R @@ -12,14 +12,15 @@ #' @param step A step value used for the supercells #' If missing, uses `attr(sc, "step")` when available #' @param compactness A compactness value used for the supercells -#' If missing, uses `attr(sc, "compactness")` when available +#' If missing, uses `attr(sc, "compactness")` when available. +#' Adaptive mode is read from `attr(sc, "adaptive_method")` when available. #' @param dist_fun A distance function name or function, as in [sc_slic()]. #' If missing or `NULL`, uses `attr(sc, "dist_fun")` when available. #' #' @details #' If `sc` lacks `supercells`, `x`, or `y` columns, they are derived from geometry #' and row order, which may differ from the original centers. -#' When using SLIC0 (set `compactness = "auto"` in [sc_slic()]), combined and balance metrics use per-supercell +#' When using SLIC0 (set `compactness = use_adaptive()` in [sc_slic()]), combined and balance metrics use per-supercell #' adaptive compactness (SLIC0), and scaled value distances are computed with the #' per-supercell max value distance. #' @@ -36,7 +37,7 @@ #' \describe{ #' \item{spatial}{Spatial distance from each pixel to its supercell center #' in grid-cell units (row/column index distance). If the input supercells were -#' created with `step = in_meters(...)`, distances are reported in meters.} +#' created with `step = use_meters(...)`, distances are reported in meters.} #' \item{value}{Value distance from each pixel to its supercell center in #' the raster value space.} #' \item{combined}{Combined distance using `compactness` and `step`.} diff --git a/R/sc_metrics_supercells.R b/R/sc_metrics_supercells.R index 871b84b..045f506 100644 --- a/R/sc_metrics_supercells.R +++ b/R/sc_metrics_supercells.R @@ -11,7 +11,7 @@ #' @details #' If `sc` lacks `supercells`, `x`, or `y` columns, they are derived from geometry #' and row order, which may differ from the original centers -#' When using SLIC0 (set `compactness = "auto"` in [sc_slic()]), combined and balance metrics use per-supercell +#' When using SLIC0 (set `compactness = use_adaptive()` in [sc_slic()]), combined and balance metrics use per-supercell #' adaptive compactness (SLIC0), and scaled value distances are computed with the #' per-supercell max value distance. #' @return An sf object with one row per supercell and columns: @@ -28,7 +28,7 @@ #' \item{supercells}{Supercell ID.} #' \item{spatial}{Mean spatial distance from cells to the supercell center #' in grid-cell units (row/column index distance). If the input supercells were -#' created with `step = in_meters(...)`, distances are reported in meters. +#' created with `step = use_meters(...)`, distances are reported in meters. #' Returned as `mean_spatial_dist` (or `mean_spatial_dist_scaled` when `scale = TRUE`).} #' \item{value}{Mean value distance from cells to the supercell center in #' value space. Returned as `mean_value_dist` (or `mean_value_dist_scaled` diff --git a/R/sc_slic.R b/R/sc_slic.R index 445019c..61d1536 100644 --- a/R/sc_slic.R +++ b/R/sc_slic.R @@ -15,15 +15,15 @@ #' \item Cluster diagnostics: [sc_metrics_supercells()] for per-supercell summaries. #' \item Global diagnostics: [sc_metrics_global()] for a single-row summary. #' } -#' @seealso [in_meters()], [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_slic_convergence()`], +#' @seealso [use_meters()], [use_adaptive()], [`sc_slic_raster()`], [`sc_slic_points()`], [`sc_slic_convergence()`], #' [`sc_metrics_pixels()`], [`sc_metrics_supercells()`], [`sc_metrics_global()`] #' #' @param x An object of class SpatRaster (terra) or class stars (stars). #' @param step Initial center spacing (alternative is `k`). -#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' Provide a plain numeric value for cell units, or use [use_meters()] for #' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param compactness A compactness value. Use [`sc_tune_compactness()`] to estimate it. -#' Set `compactness = "auto"` to enable adaptive compactness (SLIC0). +#' Use [use_adaptive()] to enable adaptive compactness (SLIC0). #' @param dist_fun A distance function name or a custom function. Supported names: #' "euclidean", "jsd", "dtw", "dtw2d", or any method from `philentropy::getDistMethods()`. #' A custom function must accept two numeric vectors and return a single numeric value. @@ -45,7 +45,8 @@ #' @param verbose Verbosity level. #' #' @return An sf object with the supercell polygons and summary statistics. -#' Information on `step` and `compactness` are attached to the result as attributes. +#' Information on `step`, `compactness`, and `adaptive_method` are attached to +#' the result as attributes (`adaptive_method` is `NULL` for fixed compactness). #' #' @references Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P., & Süsstrunk, S. (2012). SLIC Superpixels Compared to State-of-the-Art Superpixel Methods. IEEE Transactions on Pattern Analysis and Machine Intelligence, 34(11), 2274–2282. https://doi.org/10.1109/tpami.2012.120 #' @references Nowosad, J., Stepinski, T. (2022). Extended SLIC superpixels algorithm for applications to non-imagery geospatial rasters. International Journal of Applied Earth Observation and Geoinformation, https://doi.org/10.1016/j.jag.2022.102935 diff --git a/R/sc_tune_compactness.R b/R/sc_tune_compactness.R index 2ddd6aa..b787e24 100644 --- a/R/sc_tune_compactness.R +++ b/R/sc_tune_compactness.R @@ -8,7 +8,7 @@ #' #' @param raster A `SpatRaster`. #' @param step Initial center spacing (alternative is `k`). -#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' Provide a plain numeric value for cell units, or use [use_meters()] for #' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param compactness Starting compactness used for the initial short run. #' @param metric Which compactness metric to return: `"global"` or `"local"`. @@ -35,7 +35,7 @@ #' #' @return A one-row data frame with columns `step`, `metric`, `dist_fun`, and `compactness`. #' -#' @seealso [`sc_slic()`], [in_meters()] +#' @seealso [`sc_slic()`], [use_meters()], [use_adaptive()] #' #' @examples #' library(terra) diff --git a/R/supercells.R b/R/supercells.R index 149237d..378e003 100644 --- a/R/supercells.R +++ b/R/supercells.R @@ -8,7 +8,7 @@ #' You can use either `k` or `step`. #' It is also possible to provide a set of points (an `sf` object) as `k` together with the `step` value to create custom cluster centers. #' @param compactness A compactness value. Larger values cause supercells to be more compact/even (square). -#' Set `compactness = "auto"` to enable adaptive compactness (SLIC0). +#' Use [use_adaptive()] to enable adaptive compactness (SLIC0). #' A compactness value depends on the range of input cell values and selected distance measure. #' @param dist_fun A distance function. Currently implemented distance functions are `"euclidean"`, `"jsd"`, `"dtw"` (dynamic time warping), name of any distance function from the `philentropy` package (see [philentropy::getDistMethods()]; "log2" is used in this case), or any user defined function accepting two vectors and returning one value. Default: `"euclidean"` #' @param avg_fun An averaging function - how the values of the supercells' centers are calculated? The algorithm internally implements common functions `"mean"` and `"median"` (provided with quotation marks), but also accepts any fitting R function (e.g., `base::mean()` or `stats::median()`, provided as plain function name: `mean`). Default: `"mean"`. See details for more information. @@ -18,7 +18,7 @@ #' By default, when `clean = TRUE`, average area (A) is calculated based on the total number of cells divided by a number of supercells #' Next, the minimal size of a supercell equals to A/(2^2) (A is being right shifted) #' @param step Initial center spacing. You can use either `k` or `step`. -#' Provide a plain numeric value for cell units, or use [in_meters()] for +#' Provide a plain numeric value for cell units, or use [use_meters()] for #' map-distance steps in meters (automatically converted to cells using raster resolution). #' @param transform Transformation to be performed on the input. By default, no transformation is performed. Currently available transformation is "to_LAB": first, the conversion from RGB to the LAB color space is applied, then the supercells algorithm is run, and afterward, a reverse transformation is performed on the obtained results. (This argument is experimental and may be removed in the future). #' @param metadata Logical. Controls whether metadata columns diff --git a/R/use_adaptive.R b/R/use_adaptive.R new file mode 100644 index 0000000..c79f84a --- /dev/null +++ b/R/use_adaptive.R @@ -0,0 +1,21 @@ +#' Use adaptive compactness mode +#' +#' Creates a compactness mode object for adaptive compactness. +#' The `"local_max"` method corresponds to SLIC0-style local scaling, +#' where compactness is adapted using local maximum value distances. +#' +#' @param method Adaptive compactness method. Currently only `"local_max"` is supported +#' (SLIC0-style). +#' +#' @return An adaptive compactness mode object for `compactness` arguments. +#' +#' @examples +#' use_adaptive() +#' +#' @export +use_adaptive = function(method = "local_max") { + if (!is.character(method) || length(method) != 1 || is.na(method) || method != "local_max") { + stop("The 'method' argument must be 'local_max' (SLIC0-style)", call. = FALSE) + } + structure(list(method = method), class = "sc_adaptive") +} diff --git a/R/in_meters.R b/R/use_meters.R similarity index 76% rename from R/in_meters.R rename to R/use_meters.R index 6849596..fa0c909 100644 --- a/R/in_meters.R +++ b/R/use_meters.R @@ -1,17 +1,17 @@ #' Mark step values as meters #' #' Creates a units value in meters for use in `step` arguments. -#' Use plain numerics for cell units, and `in_meters()` for map-distance steps. +#' Use plain numerics for cell units, and `use_meters()` for map-distance steps. #' #' @param x A single positive numeric value. #' #' @return A [units::units] object in meters (`m`). #' #' @examples -#' in_meters(100) +#' use_meters(100) #' #' @export -in_meters = function(x) { +use_meters = function(x) { if (!is.numeric(x) || length(x) != 1 || is.na(x) || x <= 0) { stop("The 'x' argument must be a single positive number", call. = FALSE) } diff --git a/man/sc_metrics_global.Rd b/man/sc_metrics_global.Rd index c0378cd..07f2ac5 100644 --- a/man/sc_metrics_global.Rd +++ b/man/sc_metrics_global.Rd @@ -30,7 +30,8 @@ columns are named with the \verb{_scaled} suffix.} If missing, uses \code{attr(sc, "step")} when available} \item{compactness}{A compactness value used for the supercells -If missing, uses \code{attr(sc, "compactness")} when available} +If missing, uses \code{attr(sc, "compactness")} when available. +Adaptive mode is read from \code{attr(sc, "adaptive_method")} when available.} \item{dist_fun}{A distance function name or function, as in \code{\link[=sc_slic]{sc_slic()}}. If missing or \code{NULL}, uses \code{attr(sc, "dist_fun")} when available.} @@ -47,8 +48,9 @@ values indicate spatial dominance; positive values indicate value dominance.} } \describe{ \item{step}{Step size used to generate supercells. Returned in meters when -the input used \code{step = in_meters(...)}, otherwise in cells.} +the input used \code{step = use_meters(...)}, otherwise in cells.} \item{compactness}{Compactness value used to generate supercells.} +\item{adaptive_method}{Adaptive compactness method; \code{NA} for fixed compactness.} \item{n_supercells}{Number of supercells with at least one non-missing pixel.} \item{mean_value_dist}{Mean per-supercell value distance from cells to their supercell centers, averaged across supercells. Returned as \code{mean_value_dist} @@ -56,7 +58,7 @@ supercell centers, averaged across supercells. Returned as \code{mean_value_dist \item{mean_spatial_dist}{Mean per-supercell spatial distance from cells to their supercell centers, averaged across supercells; units are grid cells (row/column index distance). If the input supercells were created with -\code{step = in_meters(...)}, distances are reported in meters. Returned as +\code{step = use_meters(...)}, distances are reported in meters. Returned as \code{mean_spatial_dist} (or \code{mean_spatial_dist_scaled} when \code{scale = TRUE}).} \item{mean_combined_dist}{Mean per-supercell combined distance, computed from value and spatial distances using \code{compactness} and \code{step}, averaged across @@ -76,7 +78,7 @@ If they are missing, they are derived from geometry and row order. Use \code{outcomes = c("supercells", "coordinates", "values")} when calling \code{sc_slic()} or \code{supercells()} to preserve original centers and IDs. Metrics are averaged across supercells (each supercell has equal weight). -When using SLIC0 (set \code{compactness = "auto"} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell +When using SLIC0 (set \code{compactness = use_adaptive()} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell adaptive compactness (SLIC0), and scaled value distances are computed with the per-supercell max value distance. } diff --git a/man/sc_metrics_pixels.Rd b/man/sc_metrics_pixels.Rd index 34bd37b..36355bf 100644 --- a/man/sc_metrics_pixels.Rd +++ b/man/sc_metrics_pixels.Rd @@ -30,7 +30,8 @@ distances (\code{spatial_scaled}, \code{value_scaled}).} If missing, uses \code{attr(sc, "step")} when available} \item{compactness}{A compactness value used for the supercells -If missing, uses \code{attr(sc, "compactness")} when available} +If missing, uses \code{attr(sc, "compactness")} when available. +Adaptive mode is read from \code{attr(sc, "adaptive_method")} when available.} \item{dist_fun}{A distance function name or function, as in \code{\link[=sc_slic]{sc_slic()}}. If missing or \code{NULL}, uses \code{attr(sc, "dist_fun")} when available.} @@ -49,7 +50,7 @@ Metrics: \describe{ \item{spatial}{Spatial distance from each pixel to its supercell center in grid-cell units (row/column index distance). If the input supercells were -created with \code{step = in_meters(...)}, distances are reported in meters.} +created with \code{step = use_meters(...)}, distances are reported in meters.} \item{value}{Value distance from each pixel to its supercell center in the raster value space.} \item{combined}{Combined distance using \code{compactness} and \code{step}.} @@ -65,7 +66,7 @@ Computes per-pixel distance diagnostics from each pixel to its supercell center \details{ If \code{sc} lacks \code{supercells}, \code{x}, or \code{y} columns, they are derived from geometry and row order, which may differ from the original centers. -When using SLIC0 (set \code{compactness = "auto"} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell +When using SLIC0 (set \code{compactness = use_adaptive()} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell adaptive compactness (SLIC0), and scaled value distances are computed with the per-supercell max value distance. } diff --git a/man/sc_metrics_supercells.Rd b/man/sc_metrics_supercells.Rd index 0e67c6f..b8bfb13 100644 --- a/man/sc_metrics_supercells.Rd +++ b/man/sc_metrics_supercells.Rd @@ -30,7 +30,8 @@ columns are named with the \verb{_scaled} suffix.} If missing, uses \code{attr(sc, "step")} when available} \item{compactness}{A compactness value used for the supercells -If missing, uses \code{attr(sc, "compactness")} when available} +If missing, uses \code{attr(sc, "compactness")} when available. +Adaptive mode is read from \code{attr(sc, "adaptive_method")} when available.} \item{dist_fun}{A distance function name or function, as in \code{\link[=sc_slic]{sc_slic()}}. If missing or \code{NULL}, uses \code{attr(sc, "dist_fun")} when available.} @@ -50,7 +51,7 @@ Metrics: \item{supercells}{Supercell ID.} \item{spatial}{Mean spatial distance from cells to the supercell center in grid-cell units (row/column index distance). If the input supercells were -created with \code{step = in_meters(...)}, distances are reported in meters. +created with \code{step = use_meters(...)}, distances are reported in meters. Returned as \code{mean_spatial_dist} (or \code{mean_spatial_dist_scaled} when \code{scale = TRUE}).} \item{value}{Mean value distance from cells to the supercell center in value space. Returned as \code{mean_value_dist} (or \code{mean_value_dist_scaled} @@ -67,7 +68,7 @@ Computes per-supercell distance diagnostics \details{ If \code{sc} lacks \code{supercells}, \code{x}, or \code{y} columns, they are derived from geometry and row order, which may differ from the original centers -When using SLIC0 (set \code{compactness = "auto"} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell +When using SLIC0 (set \code{compactness = use_adaptive()} in \code{\link[=sc_slic]{sc_slic()}}), combined and balance metrics use per-supercell adaptive compactness (SLIC0), and scaled value distances are computed with the per-supercell max value distance. } diff --git a/man/sc_slic.Rd b/man/sc_slic.Rd index 414e25b..f8a7946 100644 --- a/man/sc_slic.Rd +++ b/man/sc_slic.Rd @@ -24,11 +24,11 @@ sc_slic( \item{x}{An object of class SpatRaster (terra) or class stars (stars).} \item{step}{Initial center spacing (alternative is \code{k}). -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. -Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} +Use \code{\link[=use_adaptive]{use_adaptive()}} to enable adaptive compactness (SLIC0).} \item{dist_fun}{A distance function name or a custom function. Supported names: "euclidean", "jsd", "dtw", "dtw2d", or any method from \code{philentropy::getDistMethods()}. @@ -61,7 +61,8 @@ automatic chunking based on size, or a numeric value for a fixed chunk size } \value{ An sf object with the supercell polygons and summary statistics. -Information on \code{step} and \code{compactness} are attached to the result as attributes. +Information on \code{step}, \code{compactness}, and \code{adaptive_method} are attached to +the result as attributes (\code{adaptive_method} is \code{NULL} for fixed compactness). } \description{ Creates supercells from single- or multi-band rasters using an extended SLIC algorithm. @@ -95,6 +96,6 @@ Achanta, R., Shaji, A., Smith, K., Lucchi, A., Fua, P., & Süsstrunk, S. (2012). Nowosad, J., Stepinski, T. (2022). Extended SLIC superpixels algorithm for applications to non-imagery geospatial rasters. International Journal of Applied Earth Observation and Geoinformation, https://doi.org/10.1016/j.jag.2022.102935 } \seealso{ -\code{\link[=in_meters]{in_meters()}}, \code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_slic_convergence]{sc_slic_convergence()}}, +\code{\link[=use_meters]{use_meters()}}, \code{\link[=use_adaptive]{use_adaptive()}}, \code{\link[=sc_slic_raster]{sc_slic_raster()}}, \code{\link[=sc_slic_points]{sc_slic_points()}}, \code{\link[=sc_slic_convergence]{sc_slic_convergence()}}, \code{\link[=sc_metrics_pixels]{sc_metrics_pixels()}}, \code{\link[=sc_metrics_supercells]{sc_metrics_supercells()}}, \code{\link[=sc_metrics_global]{sc_metrics_global()}} } diff --git a/man/sc_slic_convergence.Rd b/man/sc_slic_convergence.Rd index bae0e6c..68cc43d 100644 --- a/man/sc_slic_convergence.Rd +++ b/man/sc_slic_convergence.Rd @@ -25,11 +25,11 @@ sc_slic_convergence( \item{x}{An object of class SpatRaster (terra) or class stars (stars).} \item{step}{Initial center spacing (alternative is \code{k}). -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. -Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} +Use \code{\link[=use_adaptive]{use_adaptive()}} to enable adaptive compactness (SLIC0).} \item{dist_fun}{A distance function name or a custom function. Supported names: "euclidean", "jsd", "dtw", "dtw2d", or any method from \code{philentropy::getDistMethods()}. diff --git a/man/sc_slic_points.Rd b/man/sc_slic_points.Rd index 615635d..c8e0db6 100644 --- a/man/sc_slic_points.Rd +++ b/man/sc_slic_points.Rd @@ -24,11 +24,11 @@ sc_slic_points( \item{x}{An object of class SpatRaster (terra) or class stars (stars).} \item{step}{Initial center spacing (alternative is \code{k}). -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. -Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} +Use \code{\link[=use_adaptive]{use_adaptive()}} to enable adaptive compactness (SLIC0).} \item{dist_fun}{A distance function name or a custom function. Supported names: "euclidean", "jsd", "dtw", "dtw2d", or any method from \code{philentropy::getDistMethods()}. diff --git a/man/sc_slic_raster.Rd b/man/sc_slic_raster.Rd index 22bffcf..f0ed25c 100644 --- a/man/sc_slic_raster.Rd +++ b/man/sc_slic_raster.Rd @@ -24,11 +24,11 @@ sc_slic_raster( \item{x}{An object of class SpatRaster (terra) or class stars (stars).} \item{step}{Initial center spacing (alternative is \code{k}). -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{A compactness value. Use \code{\link[=sc_tune_compactness]{sc_tune_compactness()}} to estimate it. -Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0).} +Use \code{\link[=use_adaptive]{use_adaptive()}} to enable adaptive compactness (SLIC0).} \item{dist_fun}{A distance function name or a custom function. Supported names: "euclidean", "jsd", "dtw", "dtw2d", or any method from \code{philentropy::getDistMethods()}. diff --git a/man/sc_tune_compactness.Rd b/man/sc_tune_compactness.Rd index abf2798..ffe0d92 100644 --- a/man/sc_tune_compactness.Rd +++ b/man/sc_tune_compactness.Rd @@ -24,7 +24,7 @@ sc_tune_compactness( \item{raster}{A \code{SpatRaster}.} \item{step}{Initial center spacing (alternative is \code{k}). -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{compactness}{Starting compactness used for the initial short run.} @@ -78,5 +78,5 @@ tune$compactness } \seealso{ -\code{\link[=sc_slic]{sc_slic()}}, \code{\link[=in_meters]{in_meters()}} +\code{\link[=sc_slic]{sc_slic()}}, \code{\link[=use_meters]{use_meters()}}, \code{\link[=use_adaptive]{use_adaptive()}} } diff --git a/man/supercells.Rd b/man/supercells.Rd index a5f6b3b..c4b8ee4 100644 --- a/man/supercells.Rd +++ b/man/supercells.Rd @@ -28,7 +28,7 @@ You can use either \code{k} or \code{step}. It is also possible to provide a set of points (an \code{sf} object) as \code{k} together with the \code{step} value to create custom cluster centers.} \item{compactness}{A compactness value. Larger values cause supercells to be more compact/even (square). -Set \code{compactness = "auto"} to enable adaptive compactness (SLIC0). +Use \code{\link[=use_adaptive]{use_adaptive()}} to enable adaptive compactness (SLIC0). A compactness value depends on the range of input cell values and selected distance measure.} \item{dist_fun}{A distance function. Currently implemented distance functions are \code{"euclidean"}, \code{"jsd"}, \code{"dtw"} (dynamic time warping), name of any distance function from the \code{philentropy} package (see \code{\link[philentropy:getDistMethods]{philentropy::getDistMethods()}}; "log2" is used in this case), or any user defined function accepting two vectors and returning one value. Default: \code{"euclidean"}} @@ -42,7 +42,7 @@ A compactness value depends on the range of input cell values and selected dista \item{transform}{Transformation to be performed on the input. By default, no transformation is performed. Currently available transformation is "to_LAB": first, the conversion from RGB to the LAB color space is applied, then the supercells algorithm is run, and afterward, a reverse transformation is performed on the obtained results. (This argument is experimental and may be removed in the future).} \item{step}{Initial center spacing. You can use either \code{k} or \code{step}. -Provide a plain numeric value for cell units, or use \code{\link[=in_meters]{in_meters()}} for +Provide a plain numeric value for cell units, or use \code{\link[=use_meters]{use_meters()}} for map-distance steps in meters (automatically converted to cells using raster resolution).} \item{minarea}{Specifies the minimal size of a supercell (in cells). Only works when \code{clean = TRUE}. diff --git a/man/use_adaptive.Rd b/man/use_adaptive.Rd new file mode 100644 index 0000000..1398dfa --- /dev/null +++ b/man/use_adaptive.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/use_adaptive.R +\name{use_adaptive} +\alias{use_adaptive} +\title{Use adaptive compactness mode} +\usage{ +use_adaptive(method = "local_max") +} +\arguments{ +\item{method}{Adaptive compactness method. Currently only \code{"local_max"} is supported +(SLIC0-style).} +} +\value{ +An adaptive compactness mode object for \code{compactness} arguments. +} +\description{ +Creates a compactness mode object for adaptive compactness. +The \code{"local_max"} method corresponds to SLIC0-style local scaling, +where compactness is adapted using local maximum value distances. +} +\examples{ +use_adaptive() + +} diff --git a/man/in_meters.Rd b/man/use_meters.Rd similarity index 61% rename from man/in_meters.Rd rename to man/use_meters.Rd index 1305cff..8f61005 100644 --- a/man/in_meters.Rd +++ b/man/use_meters.Rd @@ -1,10 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/in_meters.R -\name{in_meters} -\alias{in_meters} +% Please edit documentation in R/use_meters.R +\name{use_meters} +\alias{use_meters} \title{Mark step values as meters} \usage{ -in_meters(x) +use_meters(x) } \arguments{ \item{x}{A single positive numeric value.} @@ -14,9 +14,9 @@ A \link[units:units]{units::units} object in meters (\code{m}). } \description{ Creates a units value in meters for use in \code{step} arguments. -Use plain numerics for cell units, and \code{in_meters()} for map-distance steps. +Use plain numerics for cell units, and \code{use_meters()} for map-distance steps. } \examples{ -in_meters(100) +use_meters(100) } diff --git a/tests/testthat/test-sc-create.R b/tests/testthat/test-sc-create.R index 8330411..93cfe96 100644 --- a/tests/testthat/test-sc-create.R +++ b/tests/testthat/test-sc-create.R @@ -5,9 +5,11 @@ test_that("sc_slic returns core output and attributes", { expect_true(all(c("supercells", "x", "y") %in% names(sc))) expect_false(is.null(attr(sc, "step"))) expect_equal(attr(sc, "compactness"), 1) + expect_null(attr(sc, "adaptive_method")) - sc_auto = sc_slic(v1, step = 8, compactness = "auto") - expect_equal(attr(sc_auto, "compactness"), "auto") + sc_auto = sc_slic(v1, step = 8, compactness = use_adaptive()) + expect_equal(attr(sc_auto, "compactness"), 0) + expect_equal(attr(sc_auto, "adaptive_method"), "local_max") }) test_that("sc_slic supports custom centers", { @@ -86,7 +88,7 @@ test_that("sc_slic validates key argument errors", { }) test_that("sc_slic handles step units and unit-related errors", { - step_map = in_meters(8 * terra::res(v1)[1]) + step_map = use_meters(8 * terra::res(v1)[1]) sc = sc_slic(v1, step = step_map, compactness = 1) expect_s3_class(attr(sc, "step"), "units") expect_equal(as.numeric(units::drop_units(attr(sc, "step"))), as.numeric(units::drop_units(step_map))) @@ -94,7 +96,7 @@ test_that("sc_slic handles step units and unit-related errors", { x_lonlat = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 1, ymin = 0, ymax = 1, crs = "EPSG:4326") terra::values(x_lonlat) = seq_len(terra::ncell(x_lonlat)) expect_error( - suppressWarnings(sc_slic(x_lonlat, step = in_meters(1000), compactness = 1)), + suppressWarnings(sc_slic(x_lonlat, step = use_meters(1000), compactness = 1)), "projected CRS", fixed = TRUE ) @@ -108,16 +110,22 @@ test_that("sc_slic handles step units and unit-related errors", { x_non_meter = terra::rast(nrows = 50, ncols = 50, xmin = 0, xmax = 5000, ymin = 0, ymax = 5000, crs = "EPSG:2277") terra::values(x_non_meter) = seq_len(terra::ncell(x_non_meter)) expect_error( - suppressWarnings(sc_slic(x_non_meter, step = in_meters(100), compactness = 1)), + suppressWarnings(sc_slic(x_non_meter, step = use_meters(100), compactness = 1)), "meter units", fixed = TRUE ) }) -test_that("in_meters validates inputs", { - x = in_meters(100) +test_that("use_meters validates inputs", { + x = use_meters(100) expect_s3_class(x, "units") expect_equal(as.character(units::deparse_unit(x)), "m") - expect_error(in_meters(-1), "single positive number", fixed = TRUE) - expect_error(in_meters(c(1, 2)), "single positive number", fixed = TRUE) + expect_error(use_meters(-1), "single positive number", fixed = TRUE) + expect_error(use_meters(c(1, 2)), "single positive number", fixed = TRUE) +}) + +test_that("use_adaptive validates input", { + x = use_adaptive() + expect_s3_class(x, "sc_adaptive") + expect_error(use_adaptive("other"), "must be 'local_max'", fixed = TRUE) }) diff --git a/tests/testthat/test-sc-metrics.R b/tests/testthat/test-sc-metrics.R index d31a7c8..c05d37f 100644 --- a/tests/testthat/test-sc-metrics.R +++ b/tests/testthat/test-sc-metrics.R @@ -1,7 +1,7 @@ sc_full = sc_slic(v1, step = 8, compactness = 1, outcomes = c("supercells", "coordinates", "values")) sc_values = sc_slic(v1, step = 8, compactness = 1, outcomes = "values") -sc_auto = sc_slic(v1, step = 8, compactness = "auto", +sc_auto = sc_slic(v1, step = 8, compactness = use_adaptive(), outcomes = c("supercells", "coordinates", "values")) manhattan = function(a, b) sum(abs(a - b)) sc_custom = sc_slic(v1, step = 8, compactness = 1, dist_fun = manhattan, @@ -21,7 +21,7 @@ test_that("metrics outputs have expected structure", { gl = sc_metrics_global(v1, sc_full) expect_s3_class(gl, "data.frame") expect_equal(nrow(gl), 1) - expect_true(all(c("step", "compactness", "n_supercells", + expect_true(all(c("step", "compactness", "adaptive_method", "n_supercells", "mean_value_dist_scaled", "mean_spatial_dist_scaled", "mean_combined_dist", "balance") %in% names(gl))) }) @@ -36,9 +36,11 @@ test_that("metrics use stored attributes and dist_fun defaults", { gl = sc_metrics_global(v1, sc_full) expect_equal(gl$step, attr(sc_full, "step")) expect_equal(gl$compactness, attr(sc_full, "compactness")) + expect_true(is.na(gl$adaptive_method)) gl_auto = sc_metrics_global(v1, sc_auto) - expect_equal(gl_auto$compactness, "auto") + expect_equal(gl_auto$compactness, 0) + expect_equal(gl_auto$adaptive_method, "local_max") g_attr = sc_metrics_global(v1, sc_custom) g_explicit = sc_metrics_global(v1, sc_custom, dist_fun = manhattan) @@ -51,7 +53,7 @@ test_that("metrics follow step encoding in cells vs meters", { v1_map = terra::aggregate(v1, fact = 2, fun = mean, na.rm = TRUE) res_map = terra::res(v1_map)[1] step_cells = 8 - step_map = in_meters(step_cells * res_map) + step_map = use_meters(step_cells * res_map) sc_cells = sc_slic(v1_map, step = step_cells, compactness = 1, outcomes = c("supercells", "coordinates", "values")) diff --git a/vignettes/articles/v2-changes-since-v1.Rmd b/vignettes/articles/v2-changes-since-v1.Rmd index 22ddcf2..02ea380 100644 --- a/vignettes/articles/v2-changes-since-v1.Rmd +++ b/vignettes/articles/v2-changes-since-v1.Rmd @@ -140,10 +140,10 @@ global_metrics ## SLIC0 adaptive compactness -`compactness = "auto"` enables adaptive compactness (SLIC0). This lets the method adjust compactness across supercells rather than using a single fixed value. +`compactness = use_adaptive()` enables adaptive compactness (SLIC0). This lets the method adjust compactness across supercells rather than using a single fixed value. ```{r} -vol_sc_slic0 <- sc_slic(vol, step = 8, compactness = "auto") +vol_sc_slic0 <- sc_slic(vol, step = 8, compactness = use_adaptive()) # Plot results on top of the volcano raster terra::plot(vol) @@ -152,7 +152,7 @@ plot(sf::st_geometry(vol_sc_slic0), add = TRUE, lwd = 0.6, border = "violet") ## Other changes -- New utilities: Added `in_meters()` helper for `step` in `sc_slic()`/`sc_slic_points()`/`sc_slic_raster()` to support map-distance step sizes. +- New utilities: Added `use_meters()` helper for `step` and `use_adaptive()` for adaptive compactness in `sc_slic()`/`sc_slic_points()`/`sc_slic_raster()`. - Behavior: Since version 1.0, the way coordinates are summarized internally has changed, and results in versions after 1.0 may differ slightly from those prior to 1.0. - Performance: Improved speed and memory efficiency. - New utilities: Added experimental `sc_merge_supercells()` for adjacency-constrained greedy merging. diff --git a/vignettes/articles/v2-evaluation.Rmd b/vignettes/articles/v2-evaluation.Rmd index 0820f59..b2d0bca 100644 --- a/vignettes/articles/v2-evaluation.Rmd +++ b/vignettes/articles/v2-evaluation.Rmd @@ -26,7 +26,7 @@ D = \sqrt{\left(\frac{d_s}{\text{step}}\right)^2 + \left(\frac{d_v}{c}\right)^2} $$ where $d_s$ is the spatial distance in grid-cell units, $d_v$ is the value-space distance defined by `dist_fun`, and $c$ is `compactness`. - + All examples use the `volcano` raster for simplicity, but the same workflow applies to multi-layer rasters. @@ -47,7 +47,7 @@ Pixel metrics are provided with the `sc_metrics_pixels()` function, which accept The pixel metrics include four layers^[Use the `metrics` argument to request a subset of metrics], each with a specific interpretation and a simple definition: -1. `spatial`: $d_s$, the distance from each pixel to its supercell center in grid-cell units (unless the input supercells were created with `step = in_meters(...)`, in which case distances are reported in map units). +1. `spatial`: $d_s$, the distance from each pixel to its supercell center in grid-cell units (unless the input supercells were created with `step = use_meters(...)`, in which case distances are reported in map units). Lower spatial values indicate cells that are closer to the center and more compact supercells, while higher values indicate cells that are farther from the center and may indicate irregular shapes or outliers. 2. `value`: $d_v$, the distance from each pixel to its supercell center in the value space defined by your `dist_fun`. Lower value distances indicate more homogeneous supercells, while higher values indicate more heterogeneous supercells or outliers. @@ -126,7 +126,7 @@ metrics_lower <- sc_metrics_global(vol, vol_sc_low) rbind(higher_compactness = metrics_higher, lower_compactness = metrics_lower) ``` -When using `compactness = "auto"`, the value scaling is per-supercell. +When using `compactness = use_adaptive()`, the value scaling is per-supercell. This improves local adaptation, but it also means metrics are not directly comparable to fixed-compactness outputs. In this case, you can still compare the spatial metrics, but the value metrics will reflect the local scaling rather than the original value distances. diff --git a/vignettes/articles/v2-intro.Rmd b/vignettes/articles/v2-intro.Rmd index a119ab6..8f2c09b 100644 --- a/vignettes/articles/v2-intro.Rmd +++ b/vignettes/articles/v2-intro.Rmd @@ -42,7 +42,7 @@ It can be thought of as the expected spatial scale of the output. Lower values prioritize value similarity and may lead to irregular shapes, while higher values prioritize shape regularity -- supercells may look more like squares -- but may be less homogeneous in terms of values. For compactness selection, you may use `sc_tune_compactness()` to estimate a good value from a short pilot run. -Alternatively, the `"auto"` option for `compactness` enables SLIC0-style adaptive compactness when you want the algorithm to adjust locally -- this does not require setting a specific value, but also takes away direct control. +Alternatively, `compactness = use_adaptive()` enables SLIC0-style adaptive compactness when you want the algorithm to adjust locally -- this does not require setting a specific value, but also takes away direct control. To assess quality of the resulting supercells, use `sc_metrics_pixels()` for pixel-level distances, `sc_metrics_supercells()` for per-supercell summaries, and `sc_metrics_global()` for a general overview. These metrics help compare different parameter settings or input preprocessing choices. diff --git a/vignettes/articles/v2-parameters.Rmd b/vignettes/articles/v2-parameters.Rmd index 3b2a547..dc8492a 100644 --- a/vignettes/articles/v2-parameters.Rmd +++ b/vignettes/articles/v2-parameters.Rmd @@ -42,13 +42,13 @@ D = \sqrt{\left(\frac{d_s}{\text{step}}\right)^2 + \left(\frac{d_v}{c}\right)^2} $$ where $d_s$ is the spatial distance in grid-cell units, $d_v$ is the value-space distance (from `dist_fun`), and $c$ is the `compactness` value. -When `step` is provided as `in_meters(...)`, it is converted to cells before segmentation; distances are still computed in grid-cell units. +When `step` is provided as `use_meters(...)`, it is converted to cells before segmentation; distances are still computed in grid-cell units. Larger `compactness` down-weights the value term, making shapes more regular, while smaller values emphasize value similarity. # Choosing step or k You can control the number and size of supercells using either `step` or `k`. -`step` defines the spacing of initial centers in pixel units (or map units when given as `in_meters(...)`). +`step` defines the spacing of initial centers in pixel units (or map units when given as `use_meters(...)`). Smaller `step` values produce more and smaller supercells, while larger values produce fewer and larger supercells. For example, `step = 8` creates centers approximately every 8 pixels, which leads to supercells that are roughly 8 by 8 pixels in size, depending on the compactness. @@ -56,12 +56,12 @@ For example, `step = 8` creates centers approximately every 8 pixels, which lead sc_step <- sc_slic(vol, step = 8, compactness = 5) ``` -By default, `step` is in pixel units, but instead we can also specify it in map units with `in_meters()`. +By default, `step` is in pixel units, but instead we can also specify it in map units with `use_meters()`. This allows us to think about the spatial scales of expected supercells in terms of the actual map units. -For example, if your raster has a resolution of 10 meters, then `step = in_meters(200)` would create centers approximately every 200 meters, which corresponds to every 20 pixels. +For example, if your raster has a resolution of 10 meters, then `step = use_meters(200)` would create centers approximately every 200 meters, which corresponds to every 20 pixels. ```{r} -sc_step_map <- sc_slic(vol, step = in_meters(200), compactness = 5) +sc_step_map <- sc_slic(vol, step = use_meters(200), compactness = 5) ``` `k` specifies the desired number of supercells and the algorithm chooses an approximate step size. @@ -154,22 +154,22 @@ Use `"auto"` (`sqrt(nlyr(x))`) for Euclidean-like distances; for bounded/angular ## Automatic compactness -For heterogeneous rasters, `compactness = "auto"` enables SLIC0-style adaptive compactness. +For heterogeneous rasters, `compactness = use_adaptive()` enables SLIC0-style adaptive compactness. This adjusts the value scale per supercell and often improves local adaptation. At the same time, it reduces direct control, so use it when a single global compactness is hard to choose. Importantly, it still uses your chosen `dist_fun` for value distances. ```{r} -sc_auto <- sc_slic(vol, step = 8, compactness = "auto") +sc_auto <- sc_slic(vol, step = 8, compactness = use_adaptive()) ``` ```{r} -terra::plot(vol, main = "compactness = auto") +terra::plot(vol, main = "compactness = adaptive") plot(sc_auto[0], add = TRUE, border = "red", lwd = 0.5) ``` -When you compare results, keep in mind that metrics from `"auto"` are not directly comparable to fixed-compactness runs. -You can still compare spatial metrics, but value metrics reflect local scaling when `compactness = "auto"`. +When you compare results, keep in mind that metrics from adaptive compactness are not directly comparable to fixed-compactness runs. +You can still compare spatial metrics, but value metrics reflect local scaling when `compactness = use_adaptive()`. ## Manual tuning