tests/testthat/test-segment_trees.R

context("segment_trees")

las <- clip_rectangle(mixedconifer, 481250, 3812980, 481300, 3813030)
ctg <- mixedconifer_ctg
opt_progress(ctg) = FALSE
opt_chunk_size(ctg) = 100
opt_chunk_buffer(ctg) = 20
opt_chunk_alignment(ctg) <- c(0, 20)

chm   <- rasterize_canopy(las, 0.5, pitfree())
ker   <- matrix(1,3,3)
chm   <- terra::focal(chm, w = ker, fun = mean)
chm   <- stars::st_as_stars(chm)
ttops <- locate_trees(chm, lmf(3, 2))
n     <- nrow(ttops)

ttops_shifted500 <- ttops
sf::st_geometry(ttops_shifted500) <- sf::st_geometry(ttops_shifted500) + sf::st_sfc(sf::st_point(c(0, 500, 0)))
sf::st_crs(ttops_shifted500) <- sf::st_crs(chm)

test_that("segment_tree propagates tree IDs and preserves storage mode", {

  ttops1 <- ttops
  ttops1$treeID <- ttops1$treeID*2L
  las1 <- segment_trees(las, dalponte2016(chm, ttops1))

  ttops2 <- ttops
  ttops2$treeID <- ttops2$treeID + runif(n, -0.1, 0.1)
  las2 <- segment_trees(las, dalponte2016(chm, ttops2))

  expect_equal(sort(unique(las1$treeID)), ttops1$treeID)
  expect_equal(sort(unique(las2$treeID)), ttops2$treeID)
  expect_equal(storage.mode(las1$treeID), storage.mode(ttops1$treeID))
  expect_equal(storage.mode(las2$treeID), storage.mode(ttops2$treeID))
})

test_that("segment_trees can store in a user defined column", {

  las <- segment_trees(las, li2012(speed_up = 5), attribute = "plop")

  expect_true("plop" %in% names(las))
})

test_that("segment_trees supports different unicity strategies", {

  las <- segment_trees(las, li2012(speed_up = 5), uniqueness = "incremental", attribute = "ID1")
  las <- segment_trees(las, li2012(speed_up = 5), uniqueness = "gpstime", attribute = "ID2")
  las <- segment_trees(las, li2012(speed_up = 5), uniqueness = "bitmerge", attribute = "ID3")

  expect_equal(length(na.omit(unique(las$ID1))), 48L)
  expect_equal(length(na.omit(unique(las$ID2))), 48L)
  expect_equal(length(na.omit(unique(las$ID3))), 48L)

  u <- las@data[, if (!anyNA(.BY)) .I[which(Z == max(Z))], by = "ID1"]
  s <- las@data[u$V1]

  expectedID2 <- s$gpstime
  expectedID3 <- lidR:::bitmerge(s$X*100, s$Y*100)

  id42 = s$ID1 == 42

  expect_equal(s$ID2[!id42], expectedID2[!id42])
  expect_equal(s$ID3[!id42], expectedID3[!id42])

  expect_equal(s$ID2[id42][1], min(s$gpstime[id42]))
  expect_equal(s$ID3[id42][1], expectedID3[id42][which.min(s$X[id42])])
})

test_that("segment_trees supports a LAScatalog", {

  # Skipping this test temporarily to be able to have a build on MacOS. Then I will be
  # able to test that on somebody' computer.
  skip_on_cran()

  opt_output_files(ctg) <- "{tempdir()}/{ID}"
  new_ctg <- segment_trees(ctg, li2012(speed_up = 5), uniqueness = 'gpstime')

  las <- readLAS(new_ctg)
  las <- segment_trees(las, li2012(speed_up = 5), uniqueness = 'gpstime', attribute = "treeID2")

  expect_equal(length(na.omit(unique(las$treeID))), 274)  # 272 on Fedora and MacOS. Impossible to reproduce.
  expect_equal(length(na.omit(unique(las$treeID2))), 274) # 272 on Fedora and MacOS. Impossible to reproduce.

  skip("Need investigation")

  expect_equal(las$treeID, las$treeID2)
})

test_that("Dalponte algorithm works", {

  las <- segment_trees(las, dalponte2016(chm, ttops))

  expected_table <- c(46, 80, 143, 98, 175, 15, 175, 196, 222, 132, 91, 124, 77, 147, 100, 52, 35, 81, 102, 81,
                      49, 95, 170, 89, 72, 60, 123, 91, 98, 125, 101, 69, 159, 118, 134, 128, 136, 99, 47, 58)
  expected_table <- table(las$treeID)

  expect_true("treeID" %in% names(las))
  expect_equal(sort(unique(las$treeID)), 1:n)
  expect_equivalent(expected_table, expected_table)
})

test_that("Silva algorithm works", {

  las <- segment_trees(las, silva2016(chm, ttops))

  expected_table <- c(96, 64, 71, 131, 134, 126, 141, 110, 163, 93, 134, 104, 145, 92, 68, 81, 183, 93, 100, 65,
                      98, 71, 96, 44, 35, 80, 155, 95, 146, 92, 144, 196, 301, 177, 14, 180, 111, 186, 40, 89)
  expected_table <- table(las$treeID)

  expect_true("treeID" %in% names(las))
  expect_equal(sort(unique(las$treeID)), 1:n)
  expect_equivalent(expected_table, expected_table)
})

test_that("Watershed algorithm works", {

  skip_if_not_installed("EBImage")

  las <- segment_trees(las, watershed(chm))

  expected_table <- c(336, 271, 208, 254, 198, 289, 140, 35, 195, 258, 287, 156, 167, 174, 233,
                      150, 251, 225, 121, 44, 103, 180, 109, 87, 198, 92, 43, 78, 15)
  expected_table <- table(las$treeID)

  expect_true("treeID" %in% names(las))
  expect_equal(sort(unique(las$treeID)), 1:29)
  expect_equivalent(expected_table, expected_table)
})

test_that("Dalponte algorithm works with sfc", {

  ttops <- sf::st_geometry(ttops)
  las <- segment_trees(las, dalponte2016(chm, ttops))

  expected_table <- c(46, 80, 143, 98, 175, 15, 175, 196, 222, 132, 91, 124, 77, 147, 100, 52, 35, 81, 102, 81,
                      49, 95, 170, 89, 72, 60, 123, 91, 98, 125, 101, 69, 159, 118, 134, 128, 136, 99, 47, 58)
  expected_table <- table(las$treeID)

  expect_true("treeID" %in% names(las))
  expect_equal(sort(unique(las$treeID)), 1:n)
  expect_equivalent(expected_table, expected_table)
})

test_that("Silva algorithm works with sfc", {

  ttops <- sf::st_geometry(ttops)
  las <- segment_trees(las, silva2016(chm, ttops))

  expected_table <- c(96, 64, 71, 131, 134, 126, 141, 110, 163, 93, 134, 104, 145, 92, 68, 81, 183, 93, 100, 65,
                      98, 71, 96, 44, 35, 80, 155, 95, 146, 92, 144, 196, 301, 177, 14, 180, 111, 186, 40, 89)
  expected_table <- table(las$treeID)

  expect_true("treeID" %in% names(las))
  expect_equal(sort(unique(las$treeID)), 1:n)
  expect_equivalent(expected_table, expected_table)
})

test_that("Dalponte algorithm catches invalid IDs in input", {

  ttops$treeID[2:3] <- 1L
  expect_error(segment_trees(las, dalponte2016(chm, ttops3), "Duplicated tree IDs found"))
})

test_that("Silva algorithm catches invalid IDs in input", {

  ttops$treeID[2:3] <- 1L
  expect_error(segment_trees(las, silva2016(chm, ttops3), "Duplicated tree IDs found"))
})

test_that("Dalponte algorithm returns NAs if no seed in chm", {

  expect_warning(las <- segment_trees(las, dalponte2016(chm, ttops_shifted500)), "No tree can be used as seed")
  expect_true("treeID" %in% names(las))
  expect_true(all(is.na(las$treeID)))
  expect_equal(storage.mode(las$treeID), storage.mode(ttops_shifted500$treeID))
})

test_that("Silva algorithm returns NAs if no seed in chm", {

  expect_warning(las <- segment_trees(las, silva2016(chm, ttops_shifted500)), "No tree can be used as seed")
  expect_true("treeID" %in% names(las))
  expect_true(all(is.na(las$treeID)))
  expect_equal(storage.mode(las$treeID), storage.mode(ttops_shifted500$treeID))
})

test_that("Dalponte algorithm does not fail with no seed at all", {

  ttops <- ttops[0,]
  expect_message(segment_trees(las, dalponte2016(chm, ttops)), "No tree found")
})

test_that("Silva algorithm does not fail with no seed at all", {

  ttops <- ttops[0,]
  expect_message(segment_trees(las, silva2016(chm, ttops)), "No tree found")
})

test_that("Dalponte algorithm works standalone", {

  expect_error(trees <- dalponte2016(chm, ttops)(), NA)
  expect_true(is(trees, "stars"))
  expect_equal(sort(unique(as.numeric(trees[[1]]))), ttops$treeID)
  expect_warning(trees <- dalponte2016(chm, ttops_shifted500)(), "No tree can be used as seed")
  expect_true(is(trees, "stars"))
  expect_equal(sort(unique(as.numeric(trees[[1]]))), numeric(0))
})

test_that("Silva algorithm works standalone", {

  expect_error(trees <- silva2016(chm, ttops)(), NA)
  expect_true(is(trees, "stars"))
  expect_equal(sort(unique(as.numeric(trees[[1]]))), ttops$treeID)
  expect_warning(trees <- silva2016(chm, ttops_shifted500)(), "No tree can be used as seed")
  expect_true(is(trees, "stars"))
  expect_equal(sort(unique(as.numeric(trees[[1]]))), numeric(0))
})

test_that("Watershed algorithm works standalone", {

  skip_if_not_installed("EBImage")

  expect_error(trees <- watershed(chm)(), NA)
  expect_true(is(trees, "stars"))
  expect_equal(sort(unique(as.numeric(trees[[1]]))), 1:29)
})

test_that("Dalponte algorithm works standalone and is backward compatible", {
  skip_if_not_installed("raster")
  ttops <- locate_trees(chm, lmf(3, 2))
  chm   <- as(chm, "Raster")
  trees <- dalponte2016(chm, ttops)()

  expect_true(is(trees, "RasterLayer"))
})

test_that("Silva algorithm works standalone and is backward compatible", {
  skip_if_not_installed("raster")
  ttops <- locate_trees(chm, lmf(3, 2))
  chm   <- as(chm, "Raster")
  trees <- silva2016(chm, ttops)()

  expect_true(is(trees, "RasterLayer"))
})

test_that("Watershed algorithm works standalone and is backward compatible", {

  skip_if_not_installed("EBImage")
  skip_if_not_installed("raster")

  chm   <- as(chm, "Raster")
  trees <- watershed(chm)()

  expect_true(is(trees, "RasterLayer"))
})

test_that("Li algorithm method works", {

  las <- segment_trees(las, li2012(speed_up = 5))

  expect_equal(sort(unique(las$treeID)), 1:48L)
  expect_true(is.integer(las$treeID))

  expect_warning(segment_trees(las, li2012(hmin =  100)), "No tree segmented")
})
Jean-Romain/lidR documentation built on April 6, 2024, 9:41 p.m.