Skip to content

Commit c4c26f7

Browse files
committed
Remove MASS Null from ATGP and test it
1 parent 87ac812 commit c4c26f7

File tree

2 files changed

+72
-4
lines changed

2 files changed

+72
-4
lines changed

pkg/unmixR/DESCRIPTION

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
Package: unmixR
22
Type: Package
33
Title: Hyperspectral Unmixing Methods
4-
Version: 2.1.0
5-
Date: 20025-03-21
4+
Version: 2.2.0
5+
Date: 20025-07-11
66
Authors@R: c(
77
person("Anton", "Belov", role = "aut"),
88
person("Conor", "McManus", role = "aut"),

pkg/unmixR/R/atgp.R

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
#'
66
#' @param p Number of endmembers.
77
#'
8-
#' @references Based on Python implementation in [pysptools](https://github.com/ctherien/pysptools/blob/fbcd3ecaa7ab27f0158b28b4327537c3e75db160/pysptools/eea/eea.py#L52)
8+
#' @references Based on Python implementation in [pysptools](https://github.com/ctherien/pysptools/blob/fbcd3ecaa7ab27f0158b28b4327537c3e75db160/pysptools/eea/eea.py#L52).
9+
#' Also matches [matlabHyperspectralToolbox](https://github.com/isaacgerg/matlabHyperspectralToolbox/blob/master/hyperspectralToolbox/hyperAtgp.m)
910
#'
1011
#' @return A list which contains:
1112
#' \itemize{
@@ -33,7 +34,13 @@ atgp <- function(data, p) {
3334
projection_vectors <- NULL
3435
while (length(indices) < p) {
3536
# Get the next projection vector
36-
P <- MASS::Null(t(data[indices,,drop=FALSE]))
37+
# Same as `P <- MASS::Null(t(data[indices,,drop=FALSE]))`
38+
# But MASS::Null is very slow, it is faster to use orthogonal complement:
39+
# $P = I - U(UU^T)^{-1}U^T$
40+
# P is symmetric matrix, so we use Ut directly to reduce transpositions
41+
Ut <- data[indices, ,drop=FALSE]
42+
P <- diag(ncol(data)) - t(Ut) %*% solve(tcrossprod(Ut), Ut)
43+
projections <- data %*% P
3744

3845
# Save it for debugging
3946
if (.options("debuglevel") >= 1L) {
@@ -61,6 +68,54 @@ atgp <- function(data, p) {
6168
.test(atgp) <- function() {
6269
context("ATGP")
6370

71+
# Reference implementation from matlabHyperspectralToolbox
72+
.ref_hyperAtgp <- function(M, q, Maug = NULL) {
73+
# Inputs:
74+
# M - Matrix of hyperspectral data (p x N)
75+
# q - Number of endmembers
76+
# Maug - Optional initial endmembers (p x k)
77+
# Outputs:
78+
# List containing:
79+
# U - Extracted endmember matrix (p x q)
80+
# indices - Indices in M of selected endmembers
81+
82+
p <- nrow(M)
83+
N <- ncol(M)
84+
85+
U <- matrix(numeric(0), nrow = p)
86+
indices <- integer(0)
87+
88+
# Step 1: Find the pixel with the largest norm
89+
norms <- colSums(M * M) # squared L2 norm for each column
90+
idx <- which.max(norms)
91+
indices <- c(indices, idx)
92+
U <- M[, idx, drop = FALSE]
93+
94+
start <- 1
95+
96+
# Step 2: Use initial endmembers if provided
97+
if (!is.null(Maug)) {
98+
U <- Maug
99+
# start <- ncol(Maug) + 1 # Not used in original loop
100+
}
101+
102+
# Step 3: Iteratively find new endmembers
103+
for (n in start:(q - 1)) {
104+
# Orthogonal projection matrix
105+
P <- diag(p) - U %*% solve(t(U) %*% U) %*% t(U)
106+
107+
# Compute projection magnitudes
108+
projected <- P %*% M
109+
norms <- colSums(projected * projected)
110+
111+
idx <- which.max(abs(norms))
112+
indices <- c(indices, idx)
113+
U <- cbind(U, M[, idx])
114+
}
115+
116+
return(list(U = U, indices = indices))
117+
}
118+
64119
test_that("ATGP produces error for invalid values of p", {
65120
expect_error(atgp(.testdata$x, p = "---"))
66121
expect_error(atgp(.testdata$x, p = 0))
@@ -75,4 +130,17 @@ atgp <- function(data, p) {
75130
test_that("ATGP works in higher dimensions", {
76131
expect_equal(atgp(.testdata$x, p = 2)$indices, .correct[1:2])
77132
})
133+
134+
test_that("ATGP works same as MATLAB implementation", {
135+
# Compare with the reference implementation
136+
137+
set.seed(123)
138+
# Generate a random matrix with 5 rows and 10 columns
139+
X <- matrix(rnorm(100*20), nrow = 100)
140+
141+
res <- atgp(X, p = 3)$indices
142+
ref <- .ref_hyperAtgp(t(X), q = 3)$indices
143+
expect_equal(res, ref)
144+
})
145+
78146
}

0 commit comments

Comments
 (0)