tests/testthat/test-neurospace.R

test_that("NeuroSpace constructor creates valid 3D space", {
  sp <- make_space(c(64, 64, 32), spacing = c(3, 3, 4))
  expect_equal(dim(sp), c(64L, 64L, 32L))
  expect_equal(spacing(sp), c(3, 3, 4))
  expect_equal(origin(sp), c(0, 0, 0))
  expect_equal(ndim(sp), 3L)
})

test_that("NeuroSpace constructor creates valid 2D space", {
  sp <- NeuroSpace(c(128L, 128L), spacing = c(1.5, 1.5))
  expect_equal(dim(sp), c(128L, 128L))
  expect_equal(ndim(sp), 2L)
  expect_equal(spacing(sp), c(1.5, 1.5))
})

test_that("NeuroSpace constructor creates valid 4D space", {
  sp <- NeuroSpace(c(64L, 64L, 32L, 100L), spacing = c(3, 3, 4))
  expect_equal(dim(sp), c(64L, 64L, 32L, 100L))
  expect_equal(ndim(sp), 4L)
})

test_that("NeuroSpace from explicit affine trans", {
  aff <- diag(c(2, 2, 2, 1))
  aff[1:3, 4] <- c(-90, -126, -72)
  sp <- NeuroSpace(c(91, 109, 91), trans = aff)
  expect_equal(trans(sp), aff)
  # Actual voxel sizes are encoded in the affine, not the spacing slot
  expect_equal(voxel_sizes(trans(sp)), c(2, 2, 2))
})

test_that("NeuroSpace validates positive dimensions", {
  expect_error(NeuroSpace(c(0, 10, 10)))
  expect_error(NeuroSpace(c(-5, 10, 10)))
})

test_that("NeuroSpace validates positive spacing", {
  expect_error(NeuroSpace(c(10, 10, 10), spacing = c(-1, 1, 1)))
  expect_error(NeuroSpace(c(10, 10, 10), spacing = c(0, 1, 1)))
})

# ---- Coordinate transforms ----

test_that("index_to_grid and grid_to_index are inverses", {
  sp <- make_space(c(10, 10, 10))
  idx <- c(1, 50, 100, 500, 1000)
  grids <- index_to_grid(sp, idx)
  idx2 <- grid_to_index(sp, grids)
  expect_equal(idx, as.numeric(idx2))
})

test_that("grid_to_coord and coord_to_grid are inverses", {
  sp <- make_space(c(64, 64, 32), spacing = c(3, 3, 4), origin = c(-90, -126, -72))
  grid <- matrix(c(10, 20, 15), nrow = 1)
  coord <- grid_to_coord(sp, grid)
  grid2 <- coord_to_grid(sp, coord)
  expect_equal(as.numeric(grid), as.numeric(grid2), tolerance = 1e-6)
})

test_that("index_to_coord and coord_to_index are inverses", {
  sp <- make_space(c(20, 20, 20), spacing = c(2, 2, 2), origin = c(-20, -20, -20))
  idx <- c(1, 100, 500, 4000, 8000)
  coords <- index_to_coord(sp, idx)
  idx2 <- coord_to_index(sp, coords)
  expect_equal(idx, as.numeric(idx2))
})

test_that("coord transforms work with batch inputs", {
  sp <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  grids <- matrix(c(1,1,1, 5,5,5, 10,10,10), ncol = 3, byrow = TRUE)
  coords <- grid_to_coord(sp, grids)
  expect_equal(nrow(coords), 3)
  grids2 <- coord_to_grid(sp, coords)
  expect_equal(as.numeric(grids), as.numeric(grids2), tolerance = 1e-6)
})

# ---- Affine operations ----

test_that("trans() returns the transformation matrix", {
  sp <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  tx <- trans(sp)
  expect_true(is.matrix(tx))
  expect_equal(dim(tx), c(4, 4))
  # Diagonal should reflect spacing
  expect_equal(tx[1,1], 2)
  expect_equal(tx[2,2], 2)
  expect_equal(tx[3,3], 2)
})

test_that("inverse_trans() is the inverse of trans()", {
  sp <- make_space(c(10, 10, 10), spacing = c(2, 3, 4), origin = c(-10, -15, -20))
  tx <- trans(sp)
  itx <- inverse_trans(sp)
  product <- tx %*% itx
  expect_equal(product, diag(4), tolerance = 1e-6)
})

test_that("oblique affine round-trips through coord transforms", {
  aff <- matrix(c(1.5, 0.1, 0, -80,
                   0.1, 2.0, 0, -120,
                   0, 0, 2.5, -60,
                   0, 0, 0, 1), 4, 4, byrow = TRUE)
  sp <- NeuroSpace(c(64, 64, 32), trans = aff)

  idx <- c(1, 100, 1000, 50000)
  coords <- index_to_coord(sp, idx)
  idx2 <- coord_to_index(sp, coords)
  expect_equal(idx, as.numeric(idx2), tolerance = 0.5)
})

# ---- Spatial queries ----

test_that("spacing() returns correct values", {
  sp <- make_space(c(10, 10, 10), spacing = c(1.5, 2.0, 2.5))
  expect_equal(spacing(sp), c(1.5, 2.0, 2.5))
})

test_that("origin() returns correct values", {
  sp <- make_space(c(10, 10, 10), origin = c(-90, -126, -72))
  expect_equal(origin(sp), c(-90, -126, -72))
})

test_that("bounds() returns min/max world coordinates", {
  sp <- make_space(c(10, 10, 10), spacing = c(1, 1, 1))
  b <- bounds(sp)
  expect_equal(nrow(b), 3)
  expect_equal(ncol(b), 2)
  for (i in 1:3) {
    expect_true(b[i, 1] <= b[i, 2])
  }
})

test_that("axes() returns an AxisSet", {
  sp <- make_space(c(10, 10, 10))
  ax <- axes(sp)
  expect_s4_class(ax, "AxisSet3D")
})

# ---- Dimension manipulation ----

test_that("add_dim extends space dimensionality", {
  sp <- make_space(c(10, 10, 10))
  sp4 <- add_dim(sp, 20)
  expect_equal(ndim(sp4), 4L)
  expect_equal(dim(sp4), c(10L, 10L, 10L, 20L))
})

test_that("drop_dim reduces space dimensionality", {
  sp4 <- NeuroSpace(c(10L, 10L, 10L, 20L))
  sp3 <- drop_dim(sp4)
  expect_equal(ndim(sp3), 3L)
  expect_equal(dim(sp3), c(10L, 10L, 10L))
})

test_that("drop_dim with explicit dimnum works", {
  sp4 <- NeuroSpace(c(10L, 10L, 10L, 20L))
  sp3 <- drop_dim(sp4, 4)
  expect_equal(ndim(sp3), 3L)
  expect_equal(dim(sp3), c(10L, 10L, 10L))
})

test_that("drop_dim preserves spatial origin for 4D->3D", {
  sp3_orig <- make_space(c(10, 10, 10), spacing = c(2, 2, 2), origin = c(-10, -20, -30))
  sp4 <- add_dim(sp3_orig, 20)
  sp3 <- drop_dim(sp4)
  expect_equal(origin(sp3), c(-10, -20, -30))
  expect_equal(spacing(sp3), c(2, 2, 2))
})

test_that("add_dim then drop_dim round-trips for >3D", {
  sp <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  sp4 <- add_dim(sp, 50)
  sp3 <- drop_dim(sp4)
  expect_equal(dim(sp3), dim(sp))
  expect_equal(spacing(sp3), spacing(sp))
})

# ---- Equality ----

test_that("identical NeuroSpaces compare equal", {
  sp1 <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  sp2 <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  expect_equal(trans(sp1), trans(sp2))
  expect_equal(dim(sp1), dim(sp2))
})

test_that("different NeuroSpaces differ", {
  sp1 <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  sp2 <- make_space(c(10, 10, 10), spacing = c(3, 3, 3))
  expect_false(identical(trans(sp1), trans(sp2)))
})

# ---- Edge cases ----

test_that("single-voxel space has correct dimensions", {
  sp <- make_space(c(1, 1, 1))
  expect_equal(prod(dim(sp)), 1)
  expect_equal(ndim(sp), 3L)
  # grid_to_coord for grid (1,1,1): (1-1)*spacing + origin = (0,0,0)
  coord <- grid_to_coord(sp, matrix(c(1, 1, 1), nrow = 1))
  expect_equal(as.numeric(coord), c(0, 0, 0))
})

test_that("non-isotropic spacing is handled correctly", {
  sp <- make_space(c(20, 20, 10), spacing = c(1, 1, 3))
  expect_equal(spacing(sp), c(1, 1, 3))
  # grid_to_coord does (grid - 1) * spacing + origin
  # grid (10,10,5) -> (9, 9, 4) * (1, 1, 3) = (9, 9, 12)
  coord <- grid_to_coord(sp, matrix(c(10, 10, 5), nrow = 1))
  expect_equal(as.numeric(coord), c(9, 9, 12), tolerance = 1e-6)
})

test_that("centroid computes the mean world coordinate", {
  sp <- make_space(c(10, 10, 10), spacing = c(1, 1, 1))
  ctr <- centroid(sp)
  expect_equal(length(ctr), 3)
  # For uniform spacing from origin 0, centroid should be near the center
  expect_true(all(ctr > 0))
})

# ---- grid_to_grid ----

test_that("grid_to_grid permutes grid coordinates", {
  sp <- make_space(c(10, 10, 10), spacing = c(2, 2, 2))
  g <- matrix(c(1,2,3, 5,5,5), ncol = 3, byrow = TRUE)
  g2 <- grid_to_grid(sp, g)
  expect_equal(nrow(g2), 2)
  expect_equal(ncol(g2), 3)
})

Try the neuroim2 package in your browser

Any scripts or data that you put into this service are public.

neuroim2 documentation built on April 16, 2026, 5:07 p.m.