[GRASS-user] Pansharpening using PCA in R
Nikos Alexandris
nikos.alexandris at felis.uni-freiburg.de
Mon Apr 5 17:13:24 EDT 2010
Georg Kaspar wrote:
> Hi,
> I'm trying to apply pansharpening to a set of 4 quickbird channels by
> exchanging the first principal component with the panchromatic channel.
> Since an inverse PCA is not possible using GRASS (alone)
Hi Georg!
Theoretically, it takes to multiply the transpose eigenvector matrix
with a back-transformation coefficient. If you use all PC's then you
would get the original data back (right?). If, instead, you do something
with your data before the back-transformation, then you'll get a "new"
spectral space.
Not sure though ;-)
> I used the
> following workflow:
>
> R:
> 1. import data into R using spgrass6
> 2. eliminate NAs
* Dunno if it helps here, but some time ago I used the complete.cases()
function which will return a vector with the "TRUE" value for all rows
that are complete (=no NA's). I remember that it was very handy.
> 3. calculate covariance matrix using cov
> 4. calculate eigenvalues and eigenvectors using eigen
In R, you can perform PCA using "princomp()" (the so-called
"eigenvector" solution) or using "prcomp()" (the SVD solution) which is
meant to give higher numerical accuracy.
Then you can export the PC's back to grass.
> GRASS:
>
> 1. calculate pc-layers by multiplying initial layers with eigenvector *
> sqrt(eigenvalue)
> 2. since the pc-layers seem to be inverted, create an inverted version
> of the panchromatic channel rescaled to the range of first pc.
If you don't mind could you explain more on this ("inverted" ? ).
> 3. set region to resolution of panchromatic channel
> 4. calculate pansharpened channels by inverting first step and replacing
> 1st pc with pan-channel
> Problem is, that the results do not seem to be pansharpened at all...
> Would be nice if someone could have a look at the attached workflow to
> see if there are any obvious mistakes...
> Best regards & thanks,
> Georg
---
> plain text document attachment (1_pca_r.txt)
> library(spgrass6)
> G = gmeta6()
> # import data from grass
> qbird = readRAST6(c('szene1_mul.1', 'szene1_mul.2', 'szene1_mul.3', 'szene1_mul.4'))
> # work around NAs
> qbird.nas = which(is.na(qbird at data$szene1_mul.1) & is.na(qbird at data$szene1_mul.2) & is.na(qbird at data$szene1_mul.3) & is.na(qbird at data$szene1_mul.4))
> qbird.values = which(!is.na(qbird at data$szene1_mul.1) & !is.na(qbird at data$szene1_mul.2) & !is.na(qbird at data$szene1_mul.3) & !is.na(qbird at data$szene1_mul.4))
> qbird.nonas = qbird at data[qbird.values, ]
--%<---
Here you can use complete.cases(), e.g.:
# make a list first!
qbird.bands <- c(
'szene1_mul.1',
'szene1_mul.2',
'szene1_mul.3',
'szene1_mul.4')
# import the data
qbird.original <- readRAST6 ( qbird.bands, NODATA=-999999 )
# use complete.cases()
qbird.na_mask <- complete.cases ( qbird )
# get values & check structure
qbird <- qbird.original at data[ qbird.na_mask, ]
str( qbird )
# you may want to add new columns to the sp object for later use:
qbird.original at data$pc1 <- NA
qbird.original at data$pc2 <- NA
qbird.original at data$pc3 <- NA
qbird.original at data$pc4 <- NA
> # calculate covariance matrix
> qbird.cov = cov(qbird.nonas)
> # calculate eigenvalues and eigenvectors of covariance matrix
> eigen(qbird.cov)
> $values
> [1] 20456.53596 6349.30655 132.17551 29.54617
>
> $vectors
> [,1] [,2] [,3] [,4]
> [1,] 0.14080443 -0.6333085 0.74920435 -0.13336911
> [2,] 0.11099650 -0.6563785 -0.47295220 0.57720293
> [3,] 0.07533621 -0.3617352 -0.46332379 -0.80548320
> [4,] -0.98090690 -0.1929644 0.01844232 -0.01569314
> plain text document attachment (2_pca_grass.txt)
--%<---
# Perform PCA based on the "Eigenvector solution" using the covariance
# princomp()
qbird.eig.cov <- princomp ( qbird )
# check results / keep only 5 digits
print ( qbird$loadings, digits=5, cutoff=0.001 )
summary ( qbird.eig.cov )
# fill-in the PC's
qbird.original at data$pc1[qbird.na_mask] <- qbird.eig.cov
$scores[,"Comp.1"]
#...and so on for pc2, pc3 and pc4
# write grass raster maps
writeRAST6 ( qbird.original, zcol=5 , vname="qbird.eig.cov.pc.1",
overwrite = TRUE )
> # set region to match extend and resolution of multispectral data
> g.region rast=szene1_mul.1
You should have done this prior to importing grass-maps in R. I think
_this_ mistake pushed me close to my pain-limit not long ago :D
> # calculate pc channels based on pca results in R
> r.mapcalc "szene1_pca.1 = 20.13874 * szene1_mul.1 + 15.87542 * szene1_mul.2 + 10.77506 * szene1_mul.3 - 140.29553 * szene1_mul.4"
> r.mapcalc "szene1_pca.2 = -50.46363 * szene1_mul.1 - 52.30191 * szene1_mul.2 - 28.82398 * szene1_mul.3 - 15.37589 * szene1_mul.4"
> r.mapcalc "szene1_pca.3 = 8.6134232 * szene1_mul.1 - 5.4374184 * szene1_mul.2 - 5.3267228 * szene1_mul.3 + 0.2120269 * szene1_mul.4"
> r.mapcalc "szene1_pca.4 = -0.72494637 * szene1_mul.1 + 3.13746694 * szene1_mul.2 - 4.37831615 * szene1_mul.3 - 0.08530223 * szene1_mul.4"
PC's already there if you create with princomp() or prcomp() and export
them in grass (as in the example above).
Below I can't really tell the Truth :-)
> # set region to match resolution of panchromatic data
> g.region res=0.6
just curious:
what does "r.info -r PanchromaticRaster" say?
why not use "g.region rast=PanchromaticRaster -pa" ?
> # since 1st pc seems to be inverted when compared to panchromatic channel, an inverted copy of the panchromatic channel is created
> r.mapcalc "szene1_pan_inv=szene1_pan*-1+2047"
Can you use "*-" in r.mapcalc?
Nikos
> # data range of panchromatic channel is set to match the 1st pc
> r.info -r szene1_pca.1
> min=-219802.73799
> max=-4871.05689
> r.rescale in=szene1_pan_inv out=szene1_pan_inv_re to=-219802.73799,-4871.05689
> # calculate pansharpened channels
> r.mapcalc "szene1_mul_pan.1=20.13874 * szene1_pan_inv_re - 90.57980 * szene1_pca.2 + 107.15596 * szene1_pca.3 - 19.07530 * szene1_pca.4"
> r.mapcalc "szene1_mul_pan.2=8.844483 * szene1_pan_inv_re - 52.301905 * szene1_pca.2 - 37.686031 * szene1_pca.3 + 45.992993 * szene1_pca.4"
> r.mapcalc "szene1_mul_pan.3=0.8661224 * szene1_pan_inv_re - 4.1587827 * szene1_pca.2 - 5.3267228 * szene1_pca.3 - 9.2604477 * szene1_pca.4"
> r.mapcalc "szene1_mul_pan.4=-5.33185610 * szene1_pan_inv_re - 1.04888494 * szene1_pca.2 + 0.10024578 * szene1_pca.3 - 0.08530223 * szene1_pca.4"
More information about the grass-user
mailing list