GitHub

CRAN status CRAN downloads License: MIT

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-neighbour 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.

Key features

  • ssim_bandwidth() — bias–variance trade-off plots to help choose an appropriate bandwidth (number of neighbours 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.
  • Multiple value transforms ("normal_score", "percentile", "minmax", "none") for comparing variables on different scales.
  • Built-in example data: Toronto Census Tract deprivation indices (Toronto_SSIM) and British Columbia Fire Weather Index rasters (fwi_0816_bc, fwi_0818_bc, fwi_1101_bc).

Installation

Install the released version from CRAN:

install.packages("SSIMmap")

Or the development version from GitHub:

# install.packages("remotes")
remotes::install_github("Hailyee-Ha/SSIMmap", build_vignettes = TRUE)

Quick example

library(SSIMmap)
library(sf)
library(terra)
library(ggplot2)
library(patchwork)
library(tidyterra)

Example 1 — Polygon maps: comparing two deprivation indices in Toronto

The package ships with Toronto_SSIM, an sf object covering Toronto Census Tracts. It includes two area-based deprivation indices (the Canadian Index of Multiple Deprivation, CIMD, and the Pampalon index, PP) and a 2016 census variable (Commute).

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

Step 1: Choose a bandwidth

ssim_bandwidth() provides a bias–variance trade-off curve for selecting k, the number of nearest neighbours used in the adaptive kernel.

S <- ssim_bandwidth(
  shape         = Toronto_SSIM,
  map1          = "CIMD",
  map2          = "PP",
  max_bandwidth = 100,
  transform     = "percentile",
  option        = "midpoint"
)
S$bandwidth
#> [1] 49
S$plot

Step 2: Compute global SSIM with a permutation test

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
)
#> 
#> 
#> |Statistic |      SSIM|       SIM|       SIV|       SIP|
#> |:---------|---------:|---------:|---------:|---------:|
#> |Mean      | 0.8250472| 0.9921019| 0.9912113| 0.8392269|
#> |Min       | 0.5103267| 0.9505378| 0.9470631| 0.5118904|
#> |Max       | 0.9519651| 1.0000000| 0.9999999| 0.9520484|
#> |SD        | 0.0931275| 0.0107547| 0.0109567| 0.0953972|
#> 
#> Global permutation p-values (two-sided):
#> 
#> 
#> |Component | Global_Mean| P_value|
#> |:---------|-----------:|-------:|
#> |SSIM      |      0.8250|  0.0010|
#> |SIM       |      0.9921|  0.3397|
#> |SIV       |      0.9912|  0.0090|
#> |SIP       |      0.8392|  0.0010|
S_global$p_global
#>   Component Global_Mean     P_value
#> 1      SSIM   0.8250472 0.000999001
#> 2       SIM   0.9921019 0.339660340
#> 3       SIV   0.9912113 0.008991009
#> 4       SIP   0.8392269 0.000999001

Step 3: Compute local SSIM and visualize it

S_local <- ssim_polygon(
  shape     = Toronto_SSIM,
  map1      = "CIMD",
  map2      = "PP",
  global    = FALSE,
  bandwidth = S$bandwidth,
  transform = "percentile",
  do_test   = TRUE, R = 1000, fdr = TRUE, alpha = 0.05,
  seed      = 1
)
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 components (SIM, SIV, SIP) or the FDR-significant cells (sig) using your favourite mapping package (ggplot2, tmap, etc.).

Example 2 — Raster maps: comparing daily Fire Weather Index in British Columbia

The package also includes three daily Canadian Fire Weather Index (FWI) maps for British Columbia at a 2 km resolution. Two represent the peak summer fire season (16 and 18 August 2023) and one represents late fall (1 November 2023). We expect the two summer maps to be much more similar to each other than either one is to the fall map.

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_1101_bc"); fwi_1101_bc <- terra::unwrap(fwi_1101_bc)
par(mfrow = c(1, 3))
plot(fwi_0816_bc, main = "FWI – 16 Aug 2023")
plot(fwi_0818_bc, main = "FWI – 18 Aug 2023")
plot(fwi_1101_bc, main = "FWI – 1 Nov 2023")

Global SSIM

A quick global comparison (no permutation test, fast):

ssim_raster(fwi_0816_bc, fwi_0818_bc)   # summer vs summer
#> $summary
#>   metric      mean         min       max         sd
#> 1   SSIM 0.5380046 -0.98968521 0.9999790 0.58855978
#> 2    SIM 0.5507247 -0.99427575 1.0000000 0.60271292
#> 3    SIV 0.9822690  0.40999905 1.0000000 0.03216969
#> 4    SIP 0.9924066  0.03063862 0.9999998 0.02029212
#> 
#> $settings
#> $settings$global
#> [1] TRUE
#> 
#> $settings$w
#> [1] 1
#> 
#> $settings$transform
#> [1] "normal_score"
#> 
#> $settings$do_test
#> [1] FALSE
#> 
#> $settings$local_test
#> [1] FALSE
ssim_raster(fwi_0816_bc, fwi_1101_bc)   # summer vs fall
#> $summary
#>   metric      mean        min       max         sd
#> 1   SSIM 0.2909122 -0.9914894 0.9998444 0.61660743
#> 2    SIM 0.3063775 -0.9964454 1.0000000 0.65287253
#> 3    SIV 0.9561909  0.2553303 1.0000000 0.06145141
#> 4    SIP 0.9845342 -0.1710329 0.9999987 0.04173728
#> 
#> $settings
#> $settings$global
#> [1] TRUE
#> 
#> $settings$w
#> [1] 1
#> 
#> $settings$transform
#> [1] "normal_score"
#> 
#> $settings$do_test
#> [1] FALSE
#> 
#> $settings$local_test
#> [1] FALSE

As expected, the summer–summer comparison shows much higher similarity than the summer–fall comparison.

Local SSIM and components

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
)
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 (p_SSIM, p_SIM, p_SIV, p_SIP) and FDR-adjusted q-values (q_SSIM, …).

Learn more

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

Citation

If you use SSIMmap in your research, please cite both the package and the original SSIM paper:

citation("SSIMmap")
  • Wang, Z., Bovik, A.C., Sheikh, H.R. and Simoncelli, E.P. (2004). Image quality assessment: from error visibility to structural similarity. IEEE Transactions on Image Processing, 13(4), 600–612. https://doi.org/10.1109/TIP.2003.819861
  • Brunsdon, C., Fotheringham, A.S. and Charlton, M. (2002). Geographically weighted summary statistics — a framework for localised exploratory data analysis. Computers, Environment and Urban Systems, 26(6), 501–524. https://doi.org/10.1016/S0198-9715(01)00009-6

Contributing & bug reports

Bug reports, feature requests, and pull requests are very welcome — please open an issue on GitHub.

License

MIT © Hui Jeong (Hailyee) Ha and Jed Long.

Read the original on github.com ↗