
The goal of SSIMmap is to extend the classical Structural Similarity Index Measure (SSIM) — originally developed for image quality assessment — to maps. The package provides a unified framework for quantifying spatial similarity between two maps of the same area, for both irregular polygon (lattice) data and raster images.
For polygon maps, the geographical SSIM method incorporates geographically weighted summary statistics with an adaptive k-nearest-neighbor Gaussian kernel. For raster images, SSIM is computed using a moving window. In addition to point estimates, SSIMmap also supports permutation tests for both global and local significance, with optional Benjamini–Hochberg FDR correction.
ssim_bandwidth() — bias–variance
trade-off plots to help choose an appropriate bandwidth (number of
neighbors k) for polygon maps.ssim_polygon() — global and local SSIM
(and components SIM, SIV, SIP) for polygon maps, with permutation
testing and FDR correction.ssim_raster() — global and local SSIM
for raster images, with permutation testing and per-cell
p-/q-values."normal_score", "percentile",
"minmax", "none") for comparing variables on
different scales.Toronto_SSIM) and British Columbia
Fire Weather Index rasters (fwi_0816_bc,
fwi_0818_bc, fwi_1111_bc, plus the legacy
fwi_1101_bc).Install the released version from CRAN:
install.packages("SSIMmap")The current defaults and two-sided raster examples below require SSIMmap 0.4.2 or later.
The development version is available from GitHub:
# install.packages("remotes")
remotes::install_github("Hailyee-Ha/SSIMmap", build_vignettes = TRUE)library(SSIMmap)
library(sf)
library(terra)
library(ggplot2)
library(patchwork)
library(tidyterra)The package ships with Toronto_SSIM, an sf
object containing 279 Aggregate Dissemination Areas (ADAs) for Toronto
from the 2021 Census geography. It includes a Canadian Index of
Multiple Deprivation residential instability score
(CIMD) and the social dimension of the
Pampalon index (PP), plus a derived
commuting measure (Commute). Its numeric data are unchanged
from version 0.4.0. Commute is the unweighted mean of all
stored DA percentages for all intersecting DAs, including boundary-only
intersections and stored zeros. The nine zero-population DAs have stored
zero percentages, which are included in the means. For
positive-population DAs, the percentage is 100 times the Census count
for commuting to a different CSD and CD within the
province or territory of residence (characteristic 2601), divided by
total DA Census population (characteristic 1), rounded half-up to two
decimals. It is not a household or worker share, a pooled ADA population
rate, or the share of all commuting outside the CSD. Sixteen source
numerator cells are officially suppressed but stored as zero in the
preserved source; their values cannot be independently verified from the
public release. See the installed
extdata/Toronto_SSIM_provenance.txt note.
data("Toronto_SSIM")
p_cimd <- ggplot(Toronto_SSIM) +
geom_sf(aes(fill = CIMD), color = NA) +
scale_fill_viridis_c(option = "magma") +
labs(title = "CIMD") + theme_void()
p_pp <- ggplot(Toronto_SSIM) +
geom_sf(aes(fill = PP), color = NA) +
scale_fill_viridis_c(option = "magma") +
labs(title = "Pampalon") + theme_void()
p_cimd + p_pp
ssim_bandwidth() provides a bias–variance trade-off
curve for selecting k, the number of nearest neighbors used in
the adaptive kernel.
S <- ssim_bandwidth(
shape = Toronto_SSIM,
map1 = "CIMD",
map2 = "PP",
max_bandwidth = 200,
transform = "percentile",
option = "midpoint"
)
S$bandwidth
#> [1] 87
S$plot
S_global <- ssim_polygon(
shape = Toronto_SSIM,
map1 = "CIMD",
map2 = "PP",
global = TRUE,
bandwidth = S$bandwidth,
transform = "percentile",
do_test = TRUE, R = 1000, fdr = TRUE, alpha = 0.05,
seed = 1
)
S_global$p_globalS_local <- ssim_polygon(
shape = Toronto_SSIM,
map1 = "CIMD",
map2 = "PP",
global = FALSE,
bandwidth = S$bandwidth,
transform = "percentile",
do_test = FALSE
)
ggplot(S_local) +
geom_sf(aes(fill = SSIM), color = NA) +
scale_fill_distiller(palette = "PRGn", limits = c(-1, 1)) +
labs(title = "Local SSIM: CIMD vs. Pampalon") +
theme_void()
The S_local object is an ordinary sf data
frame, so you can also map its components (SIM,
SIV, SIP). From version 0.4.3, repeat the
local call with do_test = TRUE, R = 1000, and
a fixed seed to obtain two-sided local tests for all four
statistics. Columns use _p, _q, and
_sig suffixes, for example SIM_p,
SIM_q, and SIM_sig. BH correction is applied
across polygons separately for each statistic. Both polygon and raster
significance use q <= alpha, or
p <= alpha without FDR adjustment. The existing
p_value, q_value, and sig columns
remain aliases for the SSIM results. With fdr = FALSE,
polygon _q columns equal their unadjusted p-values.
The manuscript examples use three daily Canadian Fire Weather
Index (FWI) maps for British Columbia at 2 km resolution: 16
and 18 August 2023 and 11 November 2023. Version 0.4.1
adds the actual November 11 observation as fwi_1111_bc. The
earlier fwi_1101_bc remains the unchanged November 1
observation for compatibility. These are distinct data, not alternative
names for one raster.
The examples explicitly use transform = "none" to
compare original FWI values. From version 0.4.2, "none" is
the default in ssim_raster(), ssim_polygon(),
and ssim_bandwidth(). Use
transform = "percentile" explicitly for the Toronto
comparisons shown above.
From version 0.4.2, both global and local raster permutation tests are two-sided, using distance from the permutation mean, as in the polygon tests. Raster map A remains fixed while map B is shuffled over common valid cells. Observed SSIM components are unchanged for an explicitly selected transformation; p-values, BH-adjusted q-values, and significance flags can change. Local tests replay the same draws in two passes to avoid storing all null rasters.
data("fwi_0816_bc"); fwi_0816_bc <- terra::unwrap(fwi_0816_bc)
data("fwi_0818_bc"); fwi_0818_bc <- terra::unwrap(fwi_0818_bc)
data("fwi_1111_bc"); fwi_1111_bc <- terra::unwrap(fwi_1111_bc)
par(mfrow = c(1, 3))
plot(fwi_0816_bc, main = "FWI – 16 Aug 2023", range = c(0, 110))
plot(fwi_0818_bc, main = "FWI – 18 Aug 2023", range = c(0, 110))
plot(fwi_1111_bc, main = "FWI – 11 Nov 2023", range = c(0, 110))
All three input-map panels use the same 0–110 FWI color scale.
A quick global comparison (no permutation test, fast):
ssim_raster(fwi_0816_bc, fwi_0818_bc, w = 1, transform = "none") # summer vs summer
#> $summary
#> metric mean min max sd
#> 1 SSIM 0.7686929 -0.1976864 0.9999991 0.29695831
#> 2 SIM 0.7888708 0.0034073 1.0000000 0.29035795
#> 3 SIV 0.9603730 0.1852894 1.0000000 0.08892729
#> 4 SIP 0.9915390 -0.2499358 1.0000000 0.02908319
#>
#> $settings
#> $settings$global
#> [1] TRUE
#>
#> $settings$w
#> [1] 1
#>
#> $settings$transform
#> [1] "none"
#>
#> $settings$do_test
#> [1] FALSE
#>
#> $settings$local_test
#> [1] FALSE
ssim_raster(fwi_0816_bc, fwi_1111_bc, w = 1, transform = "none") # summer vs fall
#> $summary
#> metric mean min max sd
#> 1 SSIM 0.2259786 -0.2139426534 1 0.33614608
#> 2 SIM 0.2339652 0.0001376689 1 0.33838033
#> 3 SIV 0.7107652 0.0308876725 1 0.28760524
#> 4 SIP 0.9940730 -0.2700576644 1 0.03425971
#>
#> $settings
#> $settings$global
#> [1] TRUE
#>
#> $settings$w
#> [1] 1
#>
#> $settings$transform
#> [1] "none"
#>
#> $settings$do_test
#> [1] FALSE
#>
#> $settings$local_test
#> [1] FALSEAs expected, the summer–summer comparison shows much higher similarity than the summer–fall comparison.
For per-cell SSIM surfaces, set global = FALSE. Below we
use tidyterra to faceted-plot the four SSIM components:
p1_l <- ssim_raster(
fwi_0816_bc, fwi_0818_bc,
global = FALSE, w = 1, transform = "none"
)
ggplot() +
geom_spatraster(data = p1_l[[c("SSIM", "SIM", "SIV", "SIP")]]) +
facet_wrap(~lyr, ncol = 2) +
scale_fill_viridis_c(limits = c(-1, 1), na.value = "transparent") +
theme_minimal()Setting do_test = TRUE and
local_test = TRUE additionally returns per-cell permutation
p-values (SSIM_p, SIM_p, SIV_p,
SIP_p) and FDR-adjusted q-values (SSIM_q,
…).
The installed package includes the analytical inputs, fixed seeds, scripts and reference results for both tables and Figures 3–7. No external download or computer-specific path is required. Run in a new writable output directory:
source(system.file("reproduction", "reproduce_manuscript.R",
package = "SSIMmap"))
reproduce_manuscript("ssim-observed", full = FALSE)
reproduce_manuscript("ssim-full", full = TRUE)The first call verifies observed values and bandwidths. The second
also runs all 1,000-permutation tests, including the local significance
maps. It is a long-running analysis, rather than a package example run
during installation. See the installed
reproduction/README.txt for settings, separate-job
execution, verification tolerances and the scope of historical Toronto
source reconstruction. Rendering instructions are included under
system.file("figures", package = "SSIMmap").
The November 11 FWI was verified against the historical coverage
provided by Natural Resources Canada, Canadian Forest Service, CWFIS. The original grid,
missing cells and all values match the official coverage. Exact
retrieval parameters and attribution are in
extdata/fwi_1111_bc_provenance.txt.
A more detailed walkthrough — including bandwidth selection,
side-by-side patchwork plots, and tidyterra
styling for publication-ready figures — is available in the package
vignette:
vignette("Introduction_to_SSIMmap", package = "SSIMmap")Or, after installing from GitHub with
build_vignettes = TRUE, browse all vignettes:
browseVignettes("SSIMmap")If you use SSIMmap in your research, please cite both the package and the original SSIM paper:
citation("SSIMmap")Bug reports, feature requests, and pull requests are very welcome — please open an issue on GitHub.
MIT © Hui Jeong (Hailyee) Ha and Jed Long.
The November 11 FWI data contain information licensed under the Open Government Licence – Canada. Source: Canadian Forest Service. 2022. Canadian Wildland Fire Information System (CWFIS), Natural Resources Canada, Canadian Forest Service, Northern Forestry Centre, Edmonton, Alberta. See the installed provenance note for source metadata.