Open smckenzie1986 opened 6 days ago
I think I have found a solution that fixes this issue! Here is my proposed solution:
histMatch2<-function (x, ref, xmask = NULL, refmask = NULL, nSamples = 1e+05,
intersectOnly = TRUE, paired = TRUE, forceInteger = FALSE,
returnFunctions = FALSE, ...)
{
nSamples <- min(ncell(ref), nSamples, ncell(ref))
x <- RStoolbox:::.toTerra(x)
ref <- RStoolbox:::.toTerra(ref)
ext <- if (paired | intersectOnly)
intersect(ext(x), ext(ref))
if (paired & is.null(ext)) {
paired <- FALSE
warning("Rasters do not overlap. Precise sampling disabled.",
call. = FALSE)
}
if (nlyr(x) != nlyr(ref))
stop("x and ref must have the same number of layers.")
if (!is.null(xmask)) {
RStoolbox:::.vMessage("Apply xmask")
xfull <- x
x <- mask(x, xmask)
}
if (!is.null(refmask)) {
.vMessage("Apply refmask")
ref <- c(ref, refmask)
}
RStoolbox:::.vMessage("Extract samples")
ref.sample <- as.matrix(spatSample(ref, size = nSamples,
na.rm = TRUE, ext = ext, xy = paired))
if (!is.null(refmask))
ref.sample <- ref.sample[, -ncol(ref.sample), drop = FALSE]
if (paired) {
x.sample <- extract(x, ref.sample[, c("x", "y")])
if (is.vector(x.sample))
x.sample <- as.matrix(x.sample)
valid <- complete.cases(x.sample)
ref.sample <- ref.sample[valid, -c(1:2), drop = FALSE]
x.sample <- x.sample[valid, , drop = FALSE]
}
else {
x.sample <- as.matrix(spatSample(x, size = nSamples,
na.rm = T, ext = ext))
}
RStoolbox:::.vMessage("Calculate empirical cumulative histograms")
layerFun <- lapply(1:ncol(x.sample), function(i) {
source.ecdf <- ecdf(x.sample[, i])
ecdf.ref <- ecdf(ref.sample[, i])
kn <- knots(ecdf.ref)
y <- ecdf.ref(kn)
minmax <- c(min(values(ref)[i]), max(values(ref)[i]))
limits <- if (is.na(minmax[1]) || is.na(minmax[2])) {
range(ref.sample)
}
else {
minmax
}
inverse.ref.ecdf <- approxfun(y, kn, method = "linear",
yleft = limits[1], yright = limits[2], ties = "ordered")
histMatchFun <- if (grepl("INT", terra::datatype(ref)[i]) |
forceInteger)
function(values, na.rm = FALSE) {
round(inverse.ref.ecdf(source.ecdf(values)))
}
else {
function(values, na.rm = FALSE) {
inverse.ref.ecdf(source.ecdf(values))
}
}
histMatchFun
})
appfun<-function(x, f){
result<-app(x, f)
return(result)
}
tmp<-lapply(1:nlyr(x), function(i, x, f) appfun(x[[i]], f[[i]]), f=layerFun, x=x)
out<-do.call(c, tmp)
if (returnFunctions) {
names(layerFun) <- names(x)
return(layerFun)
}
RStoolbox:::.vMessage("Apply histogram match functions")
if (!is.null(xmask))
out <- merge(xfull, out, ..., overwrite = TRUE)
names(out) <- names(x)
out
}
And you can check the results with the example rasters from above with this code:
HM2<-histMatch2(r1, r2, intersectOnly = FALSE, paired=FALSE)
par(mfrow=c(4,3))
plotRGB(r1[[c(1:3)]], scale=15000, main="Original")
plotRGB(r2[[c(1:3)]], scale=15000, main="Reference")
plotRGB(HM2[[c(1:3)]], scale=15000, main="Revised HistMatched")
hist(r1[[1]], breaks=100, xlim=c(0, 10000), main="Original Band 1")
hist(r2[[1]], breaks=100, xlim=c(0, 10000), main="Reference Band 1")
hist(HM2[[1]], breaks=100, xlim=c(0, 10000), main="Revised HistMatched Band 1")
hist(r1[[2]], breaks=100, xlim=c(0, 10000), main="Original Band 2")
hist(r2[[2]], breaks=100, xlim=c(0, 10000), main="Reference Band 2")
hist(HM2[[2]], breaks=100, xlim=c(0, 10000), main="Revised HistMatched Band 2")
hist(r1[[3]], breaks=100, xlim=c(0, 10000), main="Original Band 3")
hist(r2[[3]], breaks=100, xlim=c(0, 10000), main="Reference Band 3")
hist(HM2[[3]], breaks=100, xlim=c(0, 10000), main="Revised HistMatched Band 3")
Hi Benjamin, I have been doing some processing to Sentinel 2 imagery, and I think I have discovered a bug with
histMatch()
when I attempt to do color balancing between tiles withhistMatch()
I have noticed that many bands show much lower maximum values than what is in the corresponding band of the reference raster. Digging into this, I think the issue arises whentotalFun()
gets passed toterra:::app()
.terra::app()
doesn't cycle through the multiple inverse reference empirical cumulative density functions that are created fromlayerFun
You can observe the behavior with this reproducible example:Here is my session info: