Skip to content

Commit 36ff229

Browse files
committed
Update NFINDR initializations
1 parent c4c26f7 commit 36ff229

File tree

5 files changed

+453
-55
lines changed

5 files changed

+453
-55
lines changed

pkg/unmixR/DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: unmixR
22
Type: Package
33
Title: Hyperspectral Unmixing Methods
4-
Version: 2.2.0
4+
Version: 2.3.0
55
Date: 20025-07-11
66
Authors@R: c(
77
person("Anton", "Belov", role = "aut"),
@@ -64,6 +64,7 @@ Collate:
6464
'nfindr_volume.R'
6565
'options.R'
6666
'predict.R'
67+
'projections.R'
6768
'simplex.R'
6869
'unittests.R'
6970
'vca.R'

pkg/unmixR/NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ export(bary)
1212
export(dimensionalityReduction)
1313
export(endmembers)
1414
export(estSNR)
15+
export(extreme_coordinates)
1516
export(get.implementations)
1617
export(ice)
1718
export(nfindr)
1819
export(nfindrBrute)
1920
export(nnls)
21+
export(random_projections)
2022
export(simplex_volume)
2123
export(unmixR.options)
2224
export(unmixR.unittest)

pkg/unmixR/R/nfindr.R

Lines changed: 170 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,17 @@
1818
#' \itemize{
1919
#' \item vector of `p` integers - manually selected initial points, can be output of
2020
#' previous another endmember extraction method, e.g. VCA
21+
#' \item list of numeric vectors - multiple initializations, each element should be
22+
#' a vector of `p` integers representing indices
23+
#' \item function - a callable function that takes (data, p) as arguments and returns
24+
#' either numeric indices or a list with 'indices' element (e.g. output of other methods)
2125
#' \item random - randomly selected points
2226
#' \item projections - selecting the two extreme points of the
2327
#' projections of the data onto random vectors
24-
#' \item coordinates - selecting the two extreme points of the
25-
#' projections of the data onto the coordinate axes
28+
#' \item coordinates_sequence - selecting extreme points along coordinate axes
29+
#' in a deterministic sequential manner
30+
#' \item coordinates_random - selecting extreme points along coordinate axes
31+
#' with randomization
2632
#' }
2733
#' Default: "projections" is used.
2834
#'
@@ -136,18 +142,24 @@ nfindr <- function (x, ...) {
136142
paste0("Trivial case: ", estimator, " - endmembers in the inner-most loop"),
137143
{
138144
unmixR.options(debuglevel = 0L)
139-
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
145+
suppressWarnings({
146+
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
147+
})
140148
expect_equal(sort(result$indices), best_indices)
141149
expect_equal(names(result), c("indices"))
142150

143151
unmixR.options(debuglevel = 1L)
144-
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
152+
suppressWarnings({
153+
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
154+
})
145155
expect_equal(result$iterations_count, 2)
146156
expect_equal(result$replacements_count, 3)
147157
expect_equal(names(result), c("indices", "iterations_count", "replacements_count"))
148158

149159
unmixR.options(debuglevel = 2L)
150-
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
160+
suppressWarnings({
161+
result <- nfindr(data, p, indices, iter = "endmembers", estimator = estimator)
162+
})
151163
expect_equal(
152164
result$replacements[[1]],
153165
rbind(
@@ -206,18 +218,24 @@ nfindr <- function (x, ...) {
206218
{
207219

208220
unmixR.options(debuglevel = 0L)
209-
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
221+
suppressWarnings({
222+
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
223+
})
210224
expect_equal(sort(result$indices), best_indices)
211225
expect_equal(names(result), c("indices"))
212226

213227
unmixR.options(debuglevel = 1L)
214-
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
228+
suppressWarnings({
229+
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
230+
})
215231
expect_equal(result$iterations_count, 4)
216232
expect_equal(result$replacements_count, 3)
217233
expect_equal(names(result), c("indices", "iterations_count", "replacements_count"))
218234

219235
unmixR.options(debuglevel = 2L)
220-
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
236+
suppressWarnings({
237+
result <- nfindr(data, p, indices, iter = "both", estimator = estimator)
238+
})
221239
expect_equal(
222240
result$replacements[[1]],
223241
rbind(
@@ -257,10 +275,21 @@ nfindr <- function (x, ...) {
257275
paste("Non-trivial case:", estimator, iterator),
258276
# The iteration steps and the final solution must be the same as we use
259277
# straightforward volume calculation
260-
expect_equal(
261-
nfindr(data, p, indices, iter=iterator, estimator = estimator),
262-
nfindr(data, p, indices, iter=iterator, estimator = "volume")
263-
)
278+
{
279+
if ((estimator == "height") && (iterator %in% c("endmembers", "both"))) {
280+
expect_warning(
281+
result <- nfindr(data, p, indices, iter=iterator, estimator = estimator),
282+
"This combination of iterator and volume change estimator is not optimized"
283+
)
284+
} else {
285+
result <- nfindr(data, p, indices, iter=iterator, estimator = estimator)
286+
}
287+
288+
expect_equal(
289+
result,
290+
nfindr(data, p, indices, iter=iterator, estimator = "volume")
291+
)
292+
}
264293
)
265294
}
266295
}
@@ -278,6 +307,135 @@ nfindr <- function (x, ...) {
278307
})
279308
}
280309

310+
## Test init ----
311+
unmixR.options(debuglevel = 2L)
312+
313+
# Test string-based initialization methods
314+
test_that("String-based initialization methods", {
315+
set.seed(123)
316+
317+
# Test projections initialization
318+
result_proj <- nfindr(data, p, init="projections")
319+
expect_equal(length(result_proj$indices), p)
320+
expect_true(all(result_proj$indices %in% 1:nrow(data)))
321+
322+
# Test coordinates_sequence initialization
323+
result_coords_seq <- nfindr(data, p, init="coordinates_sequence")
324+
expect_equal(
325+
result_coords_seq$replacements[[1]][1,],
326+
extreme_coordinates(data, p, random=FALSE)$indices
327+
)
328+
329+
# Test coordinates_random initialization
330+
set.seed(1234)
331+
result_coords_rand <- nfindr(data, p, init="coordinates_random")
332+
expect_equal(
333+
result_coords_rand$replacements[[1]][1,],
334+
{set.seed(1234); extreme_coordinates(data, p, random=TRUE)$indices}
335+
)
336+
337+
# Test random initialization
338+
set.seed(1234)
339+
result_random <- nfindr(data, p, init="random")
340+
expect_equal(
341+
result_random$replacements[[1]][1,],
342+
{set.seed(1234); sample(nrow(data), p)}
343+
)
344+
})
345+
346+
# Test numeric vector initialization
347+
test_that("Numeric vector initialization", {
348+
manual_indices <- c(1, 5, 10, 15)
349+
result <- nfindr(data, p, init=manual_indices)
350+
expect_equal(result$replacements[[1]][1,], manual_indices)
351+
})
352+
353+
# Test list initialization
354+
test_that("List initialization", {
355+
init_list <- list(
356+
c(1, 5, 10, 15),
357+
c(2, 8, 12, 18),
358+
c(3, 7, 11, 16)
359+
)
360+
expect_warning(
361+
result <- nfindr(data, p, init=init_list),
362+
"n_init.*is ignored"
363+
)
364+
expect_equal(length(result$replacements), length(init_list))
365+
expect_true(
366+
all(
367+
sapply(1:length(init_list), function(i) result$replacements[[i]][1,] == init_list[[i]])
368+
)
369+
)
370+
})
371+
372+
# Test function initialization
373+
test_that("Function initialization", {
374+
# Test function that returns numeric indices
375+
custom_init_numeric <- function(data, p) {
376+
return(sample(1:nrow(data), p))
377+
}
378+
379+
set.seed(456)
380+
result_numeric <- nfindr(data, p, init=custom_init_numeric)
381+
expect_equal(length(result_numeric$indices), p)
382+
expect_true(all(result_numeric$indices %in% 1:nrow(data)))
383+
384+
# Test function that returns list with indices element
385+
custom_init_list <- function(data, p) {
386+
indices <- sample(1:nrow(data), p)
387+
return(list(indices = indices))
388+
}
389+
390+
set.seed(789)
391+
result_list <- nfindr(data, p, init=custom_init_list)
392+
expect_equal(length(result_list$indices), p)
393+
expect_true(all(result_list$indices %in% 1:nrow(data)))
394+
})
395+
396+
# Test error conditions for initialization
397+
test_that("Initialization error conditions", {
398+
# Test invalid list elements
399+
invalid_list <- list(c(1, 2), c(3, 4, 5)) # Different lengths
400+
expect_error(nfindr(data, p, init=invalid_list, n_init=2))
401+
402+
# Test function that returns invalid output
403+
invalid_function <- function(data, p) {
404+
return("invalid")
405+
}
406+
expect_error(nfindr(data, p, init=invalid_function))
407+
408+
# Test invalid string
409+
expect_error(nfindr(data, p, init="invalid_method"))
410+
})
411+
412+
# Test that n_init warnings are properly issued
413+
test_that("n_init warnings", {
414+
# Test with numeric vector
415+
expect_warning(
416+
nfindr(data, p, init=c(1, 5, 10, 15), n_init=3),
417+
"n_init.*is ignored"
418+
)
419+
420+
# Test with list
421+
expect_warning(
422+
nfindr(data, p, init=list(c(1, 5, 10, 15)), n_init=3),
423+
"n_init.*is ignored"
424+
)
425+
})
426+
427+
# Test reproducibility with seed
428+
test_that("Reproducibility with seed", {
429+
set.seed(999)
430+
result1 <- nfindr(data, p, init="random")
431+
432+
set.seed(999)
433+
result2 <- nfindr(data, p, init="random")
434+
435+
expect_equal(result1$indices, result2$indices)
436+
})
437+
438+
281439
## Test the formula interface ----
282440
# -> nfindr.formula has its own test
283441

pkg/unmixR/R/nfindr.default.R

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,11 @@
1-
#' Find initial endmember candidates by selecting extreme points along coordinate axes.
2-
#' @param data matrix with coordinates in rows
3-
#' @param p number of endmembers to select
4-
#' @noRd
5-
.init_extreme_coordinates <- function(data, p) {
6-
indices <- c()
7-
i <- 1
8-
while((length(indices)<p) && (i<=ncol(data))) {
9-
new_indices <- c(which.max(data[,i]), which.min(data[,i]))
10-
new_indices <- new_indices[!(new_indices %in% indices)]
11-
indices <- c(indices, new_indices)
12-
i <- i+1
13-
}
14-
15-
return(list("indices"=indices[1:p]))
16-
}
17-
18-
#' Find initial endmember candidates by projecting the data onto random vectors
19-
#' and selecting the two extreme points.
20-
#' @param data matrix with coordinates in rows
21-
#' @param p number of endmembers to select
22-
#' @noRd
23-
.init_random_projections <- function(data, p) {
24-
indices <- c()
25-
m <- ncol(data)
26-
while(length(indices)<p) {
27-
w <- stats::rnorm(m, sd = 1)
28-
projections <- as.vector(data %*% w)
29-
new_indices <- c(which.max(projections), which.min(projections))
30-
new_indices <- new_indices[!(new_indices %in% indices)]
31-
indices <- c(indices, new_indices)
32-
}
33-
34-
return(list("indices"=indices[1:p]))
35-
}
36-
37-
381
#' @name nfindr
392
#' @rdname nfindr
403
#' @include nfindr.R
414
#' @export
425
nfindr.default <- function(
436
x,
447
p,
45-
init= c("projections", "random", "coordinates"),
8+
init = c("projections", "random", "coordinates_sequence", "coordinates_random"),
469
iter = c("points", "endmembers", "both"),
4710
estimator = c("Cramer", "volume", "height", "cofactor", "LDU"),
4811
iter_max = 10,
@@ -80,20 +43,43 @@ nfindr.default <- function(
8043
## Parse init --------
8144
# Convert init into list where each element of the list is a set of initial indices
8245
if (is.numeric(init) && (length(init) == p)) {
46+
# Single numeric vector of indices
8347
init <- list(init)
8448
if ( (.options("debuglevel") > 0L) && (n_init != 1L) ) {
8549
warning("`n_init` is ignored since specific initial endmember indices were provided.")
8650
}
51+
} else if (is.list(init)) {
52+
# List of numeric vectors (multiple initializations)
53+
if ( (.options("debuglevel") > 0L) && (n_init != length(init)) ) {
54+
warning("`n_init` is ignored since specific initial endmember indices were provided.")
55+
}
56+
} else if (is.function(init)) {
57+
# Call the function n_init times
58+
init <- lapply(1:n_init, function(i) {
59+
result <- init(x, p)
60+
# Handle both direct indices and list with indices element
61+
if (is.list(result) && ("indices" %in% names(result)) ) {
62+
result$indices
63+
} else {
64+
result
65+
}
66+
})
8767
} else if (init == "random") {
8868
init <- lapply(1:n_init, function(i) sample(m, p))
8969
} else if (init == "projections") {
90-
init <- lapply(1:n_init, function(i) .init_random_projections(x, p)$indices)
91-
} else if (init == "coordinates") {
92-
init <- lapply(1:n_init, function(i) .init_extreme_coordinates(x, p)$indices)
70+
init <- lapply(1:n_init, function(i) random_projections(x, p)$indices)
71+
} else if (init == "coordinates_sequence") {
72+
init <- lapply(1:n_init, function(i) extreme_coordinates(x, p, random = FALSE)$indices)
73+
} else if (init == "coordinates_random") {
74+
init <- lapply(1:n_init, function(i) extreme_coordinates(x, p, random = TRUE)$indices)
9375
} else {
94-
stop("Unexpected `init` value.")
76+
stop("Unexpected `init` value. Must be a string, numeric vector, list of numeric vectors, or function.")
9577
}
9678

79+
stopifnot(
80+
is.list(init) && all(sapply(init, is.numeric)) && all(sapply(init, length) == p)
81+
)
82+
9783
## Check number of outer-most iterations --------
9884
# for "both" type iteration increase the number of iteration
9985
# to approximately similar amount that "points" estimator would have

0 commit comments

Comments
 (0)