Skip to content
Merged

1.9 #65

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Imports:
sf,
terra (>= 1.4-21),
philentropy (>= 0.6.0),
units
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3
LinkingTo:
Expand Down
5 changes: 4 additions & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Generated by roxygen2: do not edit by hand

S3method(plot,sc_slic_convergence)
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)
export(supercells)
export(use_adaptive)
export(use_meters)
useDynLib(supercells, .registration = TRUE)
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# 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 `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)
Expand Down
10 changes: 1 addition & 9 deletions R/helpers-chunks.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
53 changes: 48 additions & 5 deletions R/helpers-general.R
Original file line number Diff line number Diff line change
@@ -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")) {
Expand Down Expand Up @@ -59,3 +54,51 @@
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)
}

# 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)
}
67 changes: 46 additions & 21 deletions R/helpers-metrics.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# .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_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")) {

Expand All @@ -15,13 +40,12 @@
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
}
adaptive_method = attr(sc, "adaptive_method")
if (!is.null(adaptive_method) && is.null(compactness)) {
compactness = 0
}
if (missing(step)) {
Expand All @@ -30,10 +54,18 @@
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"

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)
}
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

# prepare data, including handling missing metadata
sc_work = sc
Expand All @@ -58,23 +90,16 @@
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,
compactness = compactness,
adaptive_compactness = adaptive_compactness,
step_unit = step_unit,
step_meta = step_prep$step_meta,
compactness = compactness_prep$value,
adaptive_compactness = compactness_prep$adaptive,
adaptive_method = compactness_prep$adaptive_method,
spatial_scale = spatial_scale,
step_scale = step_scale
)
Expand Down
13 changes: 1 addition & 12 deletions R/helpers-runners.R
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}

Expand Down Expand Up @@ -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)
}
}
Expand Down
Loading