Nothing
test_that("item_transform_rotate rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_rotate(img, angle = 45),
"requires a dataset item"
)
})
test_that("item_transform_rotate rejects numeric input", {
expect_error(
item_transform_rotate(42, angle = 0),
"requires a dataset item"
)
})
test_that("item_transform_rotate 0 degrees preserves image and boxes", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(boxes, image_size = c(100, 200))
result <- item_transform_rotate(item, angle = 0)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal_to_r(result$x, as_array(item$x), tolerance = 1e-5)
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_equal_to_r(result$y$boxes[, 1:4], boxes)
expect_equal_to_r(result$y$boxes[, 5], 0)
})
test_that("item_transform_rotate expands canvas for non-axis-aligned angles", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(300L, 500L))
result <- item_transform_rotate(item, angle = 30)
# Read expected dimensions from actual output
expect_equal(result$x$shape[1], 3)
expect_equal(result$x$shape[2], result$y$image_height) # H
expect_equal(result$x$shape[3], result$y$image_width) # W
})
test_that("item_transform_rotate 90 degrees swaps dimensions", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(200L, 400L))
result <- item_transform_rotate(item, angle = 90)
# After 90° rotation with expand=TRUE, H and W swap
expect_tensor_shape(result$x, c(3, 400L, 200L))
expect_equal(result$y$image_height, 400L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_rotate 180 degrees preserves dimensions", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(200L, 400L))
result <- item_transform_rotate(item, angle = 180)
expect_tensor_shape(result$x, c(3, 200, 400))
expect_equal(result$y$image_height, 200L)
expect_equal(result$y$image_width, 400L)
})
test_that("item_transform_rotate negative angles work", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_rotate(item, angle = -45)
expect_equal(result$x$shape[1], 3)
expect_equal(result$x$shape[2], result$y$image_height)
expect_equal(result$x$shape[3], result$y$image_width)
})
test_that("item_transform_rotate boxes follow the rotated image without expansion", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(300L, 500L))
result <- item_transform_rotate(item, angle = 30)
# Compute geometry manually
orig_box <- c(10, 20, 50, 60)
angle <- 30
cx <- (orig_box[1] + orig_box[3]) / 2 # 30
cy <- (orig_box[2] + orig_box[4]) / 2 # 40
hw <- (orig_box[3] - orig_box[1]) / 2 # 20
hh <- (orig_box[4] - orig_box[2]) / 2 # 20
angle_rad <- angle * pi / 180
new_spatial <- tail(result$x$shape, 2)
new_H <- new_spatial[1]
new_W <- new_spatial[2]
# The box centre is rotated around the original image centre and shifted onto
# the expanded canvas, while its physical half-extents stay unchanged.
cx_new <- new_W / 2 + (cx - 500 / 2) * cos(angle_rad) + (cy - 300 / 2) * sin(angle_rad)
cy_new <- new_H / 2 - (cx - 500 / 2) * sin(angle_rad) + (cy - 300 / 2) * cos(angle_rad)
expect_equal_to_r(result$y$boxes[1, 1], cx_new - hw, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 3], cx_new + hw, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 2], cy_new - hh, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 4], cy_new + hh, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 5], angle, tolerance = 1e-4)
})
test_that("item_transform_rotate does not expand boxes when expand = FALSE", {
# A 2x2 square at the image centre. Rotating must keep the box the same size
# (half-extents 1x1), only the angle column is added.
item <- make_detection_item(matrix(c(99, 49, 101, 51), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_rotate(item, angle = 45, expand = FALSE)
expect_equal_to_r(result$y$boxes[1, 1:4], c(99, 49, 101, 51), tolerance = 1e-5)
expect_equal_to_r(result$y$boxes[1, 5], 45, tolerance = 1e-5)
})
test_that("item_transform_rotate rotates the box centre around the image centre", {
# Off-centre box: rotating the image must move the box centre accordingly.
item <- make_detection_item(matrix(c(100, 40, 140, 80), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_rotate(item, angle = 90, expand = FALSE)
cx <- 120
cy <- 60
hw <- 20
hh <- 20
# 90-degree rotation around image centre (cx=100, cy=50):
# (dx, dy) = (20, 10) -> (dx', dy') = (dy, -dx) = (10, -20)
expect_equal_to_r(result$y$boxes[1, 1], 100 + 10 - hw, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 3], 100 + 10 + hw, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 2], 50 - 20 - hh, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 4], 50 - 20 + hh, tolerance = 1e-4)
expect_equal_to_r(result$y$boxes[1, 5], 90, tolerance = 1e-4)
})
test_that("item_transform_rotate converts boxes to xyxyr format", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_rotate(item, angle = 0)
expect_equal(result$y$boxes$shape[2], 5)
expect_equal_to_r(result$y$boxes[1, 5], 0)
})
test_that("item_transform_rotate applies angle to boxes", {
item <- make_detection_item(
matrix(c(100, 100, 102, 102), ncol = 4),
image_size = c(200L, 200L)
)
result <- item_transform_rotate(item, angle = 45)
expect_equal_to_r(result$y$boxes[1, 5], 45, tolerance = 1e-5)
})
test_that("item_transform_rotate preserves labels", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 5, 5, 15, 25), ncol = 4, byrow = TRUE),
labels = labels
)
original_labels <- item$y$labels$clone()
result <- item_transform_rotate(item, angle = 0)
expect_true(result$y$labels$eq(original_labels)$all()$item())
})
test_that("item_transform_rotate handles empty boxes", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_rotate(item, angle = 45)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(0, 5))
expect_equal(result$y$boxes$dtype, torch_float())
})
test_that("item_transform_rotate handles multiple boxes", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(400, 300))
result <- item_transform_rotate(item, angle = 0)
expect_tensor_shape(result$y$boxes, c(3, 5))
expect_equal_to_r(result$y$boxes[, 1:4], boxes)
expect_equal_to_r(result$y$boxes[, 5], rep(0,3))
})
test_that("item_transform_rotate does not mutate input", {
boxes <- torch_tensor(matrix(c(10, 20, 50, 60), ncol = 4))
item <- make_detection_item(boxes)
original_img <- item$x$clone()
original_boxes <- item$y$boxes$clone()
original_class <- class(item)
result <- item_transform_rotate(item, angle = 30)
expect_true(item$x$eq(original_img)$all()$item())
expect_equal_to_r(item$y$boxes, as.array(original_boxes$cpu()))
expect_equal(class(item), original_class)
})
test_that("item_transform_rotate returns image_with_rotated_box class", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_rotate(item, angle = 45)
expect_s3_class(result, "image_with_rotated_box")
expect_false(inherits(result, "image_with_bounding_box"))
})
test_that("item_transform_rotate image dtype is preserved", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_rotate(item, angle = 0)
expect_equal(result$x$dtype, item$x$dtype)
})
test_that("item_transform_rotate can be composed", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
labels <- sample.int(2^16, 3)
item <- make_detection_item(boxes, labels = labels, image_size = c(410, 300))
result <- item |>
item_transform_rotate(angle = 90) |>
item_transform_rotate(angle = 90)
# Two 90-degree rotations are a 180-degree rotation around the image centre:
# box centre (cx, cy) moves to (W - cx, H - cy), physical size is unchanged.
expected <- t(apply(boxes, 1, function(b) {
cx <- (b[1] + b[3]) / 2
cy <- (b[2] + b[4]) / 2
hw <- (b[3] - b[1]) / 2
hh <- (b[4] - b[2]) / 2
c(300 - cx - hw, 410 - cy - hh, 300 - cx + hw, 410 - cy + hh, 180)
}))
expect_tensor_shape(result$x, c(3, 410, 300))
expect_equal(result$y$image_height, 410)
expect_equal(result$y$image_width, 300)
expect_tensor_shape(result$y$boxes, c(3, 5))
expect_equal_to_r(result$y$boxes[, 1:4], expected[, 1:4], tolerance = 1e-5)
expect_equal_to_r(result$y$boxes[, 5], expected[, 5])
expect_equal(result$y$labels, labels)
})
test_that("item_transform_rotate keeps a fractional angle for integer boxes", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
item$y$boxes <- item$y$boxes$to(dtype = torch_long())
result <- item_transform_rotate(item, angle = 12.7, expand = FALSE)
expect_equal_to_r(result$y$boxes[1, 5], 12.7, tolerance = 1e-5)
})
test_that("item_transform_rotate 0 degrees preserves masks for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_rotate(item, angle = 0)
expect_s3_class(result, "image_with_segmentation_mask")
expect_true(result$y$masks$equal(original_masks))
})
test_that("item_transform_rotate rotates masks for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_rotate(item, angle = 90)
expect_tensor_shape(result$y$masks, c(2, 200, 100))
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(original_masks$transpose(-1, -2)$flip(-2)))
})
test_that("item_transform_rotate expands image and masks together for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
result <- item_transform_rotate(item, angle = 30)
expect_equal(result$y$masks$shape[2], result$x$shape[2])
expect_equal(result$y$masks$shape[3], result$x$shape[3])
expect_equal(result$y$image_height, result$x$shape[2])
expect_equal(result$y$image_width, result$x$shape[3])
})
test_that("item_transform_rotate preserves labels for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_labels <- item$y$labels$clone()
result <- item_transform_rotate(item, angle = 30)
expect_true(result$y$labels$eq(original_labels)$all()$item())
})
test_that("item_transform_rotate works on a segmentation dataset", {
ds <- toy_item_dataset(function(index) {
make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
})
ds <- item_transform_rotate(ds, angle = 90)
item <- ds$.getitem(1)
expect_s3_class(item, "image_with_segmentation_mask")
expect_tensor_shape(item$x, c(3, 200, 100))
expect_tensor_shape(item$y$masks, c(2, 200, 100))
})
test_that("item_transform_hflip rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_hflip(img),
"requires a dataset item"
)
})
test_that("item_transform_hflip rejects numeric input", {
expect_error(
item_transform_hflip(42),
"requires a dataset item"
)
})
test_that("item_transform_hflip preserves image shape and flips x-coordinates", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_hflip(item)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal_to_r(result$y$boxes[1, 1], 200 - 50)
expect_equal_to_r(result$y$boxes[1, 3], 200 - 10)
expect_equal_to_r(result$y$boxes[1, 2], 20)
expect_equal_to_r(result$y$boxes[1, 4], 60)
})
test_that("item_transform_hflip preserves labels and metadata", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 5, 5, 15, 25), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(100L, 200L)
)
result <- item_transform_hflip(item)
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_hflip handles empty boxes", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_hflip(item)
expect_tensor_shape(result$y$boxes, c(0, 4))
expect_tensor_dtype(result$y$boxes, torch_float())
})
test_that("item_transform_hflip handles multiple boxes", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(500L, 600L))
result <- item_transform_hflip(item)
expect_tensor_shape(result$y$boxes, c(3, 4))
expect_equal_to_r(result$y$boxes[1, 1], 600 - 50)
expect_equal_to_r(result$y$boxes[1, 3], 600 - 10)
expect_equal_to_r(result$y$boxes[2, 1], 600 - 150)
expect_equal_to_r(result$y$boxes[2, 3], 600 - 100)
expect_equal_to_r(result$y$boxes[3, 1], 600 - 300)
expect_equal_to_r(result$y$boxes[3, 3], 600 - 0)
expect_equal_to_r(result$y$boxes[1, 2], 20)
expect_equal_to_r(result$y$boxes[1, 4], 60)
expect_equal_to_r(result$y$boxes[2, 2], 200)
expect_equal_to_r(result$y$boxes[2, 4], 250)
expect_equal_to_r(result$y$boxes[3, 2], 0)
expect_equal_to_r(result$y$boxes[3, 4], 400)
})
test_that("item_transform_hflip does not mutate input for detection", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(torch_tensor(boxes))
original_img <- as_array(item$x)
original_class <- class(item)
result <- item_transform_hflip(item)
expect_equal_to_r(item$x, original_img)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
})
test_that("item_transform_hflip preserves class", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_hflip(item)
expect_s3_class(result, "image_with_bounding_box")
})
test_that("item_transform_hflip actually flips image pixels", {
w <- 200L
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, w))
original_img <- item$x$clone()
result <- item_transform_hflip(item)
expect_tensor_shape(result$x, c(3, 100L, w))
expect_true(torch_equal(result$x, transform_hflip(original_img)))
})
test_that("item_transform_hflip image dtype is preserved for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_hflip(item)
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_hflip preserves image shape for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_hflip(item)
expect_tensor_shape(result$x, c(3, 100, 200))
})
test_that("item_transform_hflip flips masks for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_hflip(item)
expect_tensor_shape(result$y$masks, original_masks$shape)
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(original_masks$flip(-1)))
})
test_that("item_transform_hflip preserves labels for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_hflip(item)
expect_equal_to_r(result$y$labels, original_labels)
})
test_that("item_transform_hflip preserves image_height and image_width for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_hflip(item)
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_hflip preserves class for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_hflip(item)
expect_s3_class(result, "image_with_segmentation_mask")
})
test_that("item_transform_hflip image dtype is preserved for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_hflip(item)
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_hflip can be composed", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
labels <- sample.int(2^16, 3)
item <- make_detection_item(boxes, labels = labels, image_size = c(410, 300))
result <- item |>
item_transform_hflip() |>
item_transform_hflip()
expect_tensor_shape(result$x, c(3, 410, 300))
expect_equal(result$y$image_height, 410)
expect_equal(result$y$image_width, 300)
expect_tensor_shape(result$y$boxes, c(3, 4))
expect_equal_to_r(result$y$boxes[, 1:4], boxes, tolerance = 1e-5)
expect_equal(result$y$labels, labels)
})
test_that("item_transform_hflip handles rotated boxes", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(boxes, image_size = c(100L, 200L))
rotated <- item_transform_rotate(item, angle = 30)
result <- item_transform_hflip(rotated)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal_to_r(result$y$boxes[1, 5], -30, tolerance = 1e-5)
})
test_that("item_transform_hflip composed with rotate is symmetric", {
boxes <- matrix(c(10, 20, 50, 60, 100, 200, 150, 250), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(300L, 400L))
rotated <- item_transform_rotate(item, angle = 30)
result <- rotated |>
item_transform_hflip() |>
item_transform_hflip()
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(2, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal_to_r(result$y$boxes, as_array(rotated$y$boxes), tolerance = 1e-5)
})
# item_transform_vflip
test_that("item_transform_vflip rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_vflip(img),
"requires a dataset item"
)
})
test_that("item_transform_vflip rejects numeric input", {
expect_error(
item_transform_vflip(42),
"requires a dataset item"
)
})
test_that("item_transform_vflip preserves image shape and flips y-coordinates", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_vflip(item)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal_to_r(result$y$boxes[1, 1], 10)
expect_equal_to_r(result$y$boxes[1, 3], 50)
expect_equal_to_r(result$y$boxes[1, 2], 100 - 60)
expect_equal_to_r(result$y$boxes[1, 4], 100 - 20)
})
test_that("item_transform_vflip preserves labels and metadata", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 5, 5, 15, 25), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(100L, 200L)
)
result <- item_transform_vflip(item)
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_vflip handles empty boxes", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_vflip(item)
expect_tensor_shape(result$y$boxes, c(0, 4))
expect_tensor_dtype(result$y$boxes, torch_float())
})
test_that("item_transform_vflip handles multiple boxes", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(500L, 600L))
result <- item_transform_vflip(item)
expect_tensor_shape(result$y$boxes, c(3, 4))
expect_equal_to_r(result$y$boxes[1, 1], 10)
expect_equal_to_r(result$y$boxes[1, 3], 50)
expect_equal_to_r(result$y$boxes[2, 1], 100)
expect_equal_to_r(result$y$boxes[2, 3], 150)
expect_equal_to_r(result$y$boxes[3, 1], 0)
expect_equal_to_r(result$y$boxes[3, 3], 300)
expect_equal_to_r(result$y$boxes[1, 2], 500 - 60)
expect_equal_to_r(result$y$boxes[1, 4], 500 - 20)
expect_equal_to_r(result$y$boxes[2, 2], 500 - 250)
expect_equal_to_r(result$y$boxes[2, 4], 500 - 200)
expect_equal_to_r(result$y$boxes[3, 2], 500 - 400)
expect_equal_to_r(result$y$boxes[3, 4], 500 - 0)
})
test_that("item_transform_vflip does not mutate input for detection", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(torch_tensor(boxes))
original_img <- as_array(item$x)
original_class <- class(item)
result <- item_transform_vflip(item)
expect_equal_to_r(item$x, original_img)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
})
test_that("item_transform_vflip preserves class", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_vflip(item)
expect_s3_class(result, "image_with_bounding_box")
})
test_that("item_transform_vflip actually flips image pixels", {
h <- 100L
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(h, 200L))
original_img <- item$x$clone()
result <- item_transform_vflip(item)
expect_tensor_shape(result$x, c(3, h, 200L))
expect_true(torch_equal(result$x, transform_vflip(original_img)))
})
test_that("item_transform_vflip image dtype is preserved for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_vflip(item)
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_vflip preserves image shape for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_vflip(item)
expect_tensor_shape(result$x, c(3, 100, 200))
})
test_that("item_transform_vflip flips masks for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_vflip(item)
expect_tensor_shape(result$y$masks, original_masks$shape)
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(original_masks$flip(-2)))
})
test_that("item_transform_vflip preserves labels for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_vflip(item)
expect_equal_to_r(result$y$labels, original_labels)
})
test_that("item_transform_vflip preserves image_height and image_width for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_vflip(item)
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_vflip preserves class for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_vflip(item)
expect_s3_class(result, "image_with_segmentation_mask")
})
test_that("item_transform_vflip image dtype is preserved for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_vflip(item)
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_vflip can be composed", {
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
labels <- sample.int(2^16, 3)
item <- make_detection_item(boxes, labels = labels, image_size = c(410, 300))
result <- item |>
item_transform_vflip() |>
item_transform_vflip()
expect_tensor_shape(result$x, c(3, 410, 300))
expect_equal(result$y$image_height, 410)
expect_equal(result$y$image_width, 300)
expect_tensor_shape(result$y$boxes, c(3, 4))
expect_equal_to_r(result$y$boxes[, 1:4], boxes, tolerance = 1e-5)
expect_equal(result$y$labels, labels)
})
test_that("item_transform_vflip handles rotated boxes", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(boxes, image_size = c(100L, 200L))
rotated <- item_transform_rotate(item, angle = 30)
result <- item_transform_vflip(rotated)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal_to_r(result$y$boxes[1, 5], -30, tolerance = 1e-5)
})
test_that("item_transform_vflip composed with rotate is symmetric", {
boxes <- matrix(c(10, 20, 50, 60, 100, 200, 150, 250), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(300L, 400L))
rotated <- item_transform_rotate(item, angle = 30)
result <- rotated |>
item_transform_vflip() |>
item_transform_vflip()
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(2, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal_to_r(result$y$boxes, as_array(rotated$y$boxes), tolerance = 1e-5)
})
# --- item_transform_center_crop tests ---
test_that("item_transform_center_crop rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_center_crop(img, size = 50),
"requires a dataset item"
)
expect_error(
item_transform_center_crop(42, size = 50),
"requires a dataset item"
)
})
test_that("item_transform_center_crop crops detection items and adjusts targets", {
boxes <- matrix(c(120, 70, 180, 130), ncol = 4)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
original_img <- item$x$clone()
original_img_r <- as_array(item$x)
original_class <- class(item)
original_dtype <- item$x$dtype
result <- item_transform_center_crop(item, size = c(100L, 200L))
expect_s3_class(result, "image_with_bounding_box")
expect_tensor_dtype(result$x, original_dtype)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expect_true(torch_equal(result$x, transform_center_crop(original_img, size = c(100L, 200L))))
expect_equal_to_r(result$y$boxes[1, 1], 120 - 99) # x1
expect_equal_to_r(result$y$boxes[1, 3], 180 - 99) # x2
expect_equal_to_r(result$y$boxes[1, 2], 70 - 49) # y1
expect_equal_to_r(result$y$boxes[1, 4], 130 - 49) # y2
# input is not mutated
expect_equal_to_r(item$x, original_img_r)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
# labels and metadata are preserved
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(120, 70, 180, 130, 210, 80, 280, 120), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(200L, 400L)
)
result <- item_transform_center_crop(item, size = c(100L, 200L))
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
# multiple boxes are adjusted and clamped to the crop
boxes <- matrix(c(
120, 70, 180, 130,
150, 60, 280, 140,
110, 80, 140, 120
), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
result <- item_transform_center_crop(item, size = c(100L, 200L))
expected_boxes <- boxes
expected_boxes[, 1] <- pmax(0, boxes[, 1] - 99)
expected_boxes[, 3] <- pmin(200, boxes[, 3] - 99)
expected_boxes[, 2] <- pmax(0, boxes[, 2] - 49)
expected_boxes[, 4] <- pmin(100, boxes[, 4] - 49)
expect_tensor_shape(result$y$boxes, c(3, 4))
expect_equal_to_r(result$y$boxes[, 1], expected_boxes[, 1])
expect_equal_to_r(result$y$boxes[, 3], expected_boxes[, 3])
expect_equal_to_r(result$y$boxes[, 2], expected_boxes[, 2])
expect_equal_to_r(result$y$boxes[, 4], expected_boxes[, 4])
# empty boxes are preserved
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_center_crop(item, size = c(100L, 200L))
expect_tensor_shape(result$y$boxes, c(0, 4))
expect_tensor_dtype(result$y$boxes, torch_float())
})
test_that("item_transform_center_crop square crop via single int", {
item <- make_detection_item(matrix(c(120, 70, 180, 130), ncol = 4), image_size = c(200L, 400L))
result <- item_transform_center_crop(item, size = 100)
expect_tensor_shape(result$x, c(3, 100, 100))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 100L)
})
test_that("item_transform_center_crop crops segmentation items", {
item <- make_segmentation_item(image_size = c(200L, 400L), num_masks = 2L)
original_img <- item$x$clone()
original_masks <- item$y$masks$clone()
original_dtype <- item$x$dtype
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_center_crop(item, size = c(100L, 200L))
expect_s3_class(result, "image_with_segmentation_mask")
expect_tensor_dtype(result$x, original_dtype)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expect_equal_to_r(result$y$labels, original_labels)
expected_masks <- transform_center_crop(original_masks, size = c(100L, 200L))
expect_tensor_shape(result$y$masks, c(2, 100, 200))
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(expected_masks))
})
test_that("item_transform_center_crop pads when crop is larger than image", {
item <- make_detection_item(matrix(c(5, 5, 15, 15), ncol = 4), image_size = c(20L, 30L))
result <- item_transform_center_crop(item, size = c(30L, 40L))
expect_tensor_shape(result$x, c(3, 30, 40))
expect_equal(result$y$image_height, 30L)
expect_equal(result$y$image_width, 40L)
})
test_that("item_transform_center_crop can be composed", {
boxes <- matrix(c(120, 70, 180, 130, 210, 80, 280, 120), ncol = 4, byrow = TRUE)
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(boxes, labels = labels, image_size = c(200L, 400L))
result <- item |>
item_transform_hflip() |>
item_transform_center_crop(size = c(100L, 200L))
# after hflip, boxes become (W - x2, y1, W - x1, y2) with W = 400;
# center crop offsets are 99 (width) and 49 (height)
expect_s3_class(result, "image_with_bounding_box")
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal_to_r(result$y$boxes[1, ], c(220 - 99, 70 - 49, 280 - 99, 130 - 49))
expect_equal_to_r(result$y$boxes[2, ], c(120 - 99, 80 - 49, 190 - 99, 120 - 49))
})
test_that("item_transform_center_crop handles rotated boxes", {
boxes <- matrix(c(120, 70, 180, 130), ncol = 4)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
rotated <- item_transform_rotate(item, angle = 30)
result <- item_transform_center_crop(rotated, size = c(100L, 200L))
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
})
# item_transform_affine
test_that("item_transform_affine rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_affine(img, angle = 30),
"requires a dataset item"
)
})
test_that("item_transform_affine rejects numeric input", {
expect_error(
item_transform_affine(42),
"requires a dataset item"
)
})
test_that("item_transform_affine identity preserves image and boxes", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
original_img <- item$x$clone()
result <- item_transform_affine(item)
expect_s3_class(result, "image_with_rotated_box")
expect_true(torch_equal(result$x, original_img))
expect_equal_to_r(result$y$boxes, matrix(c(10, 20, 50, 60, 0), ncol = 5))
})
test_that("item_transform_affine returns rotated boxes in xyxyr format", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_affine(item, angle = 25)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_equal_to_r(result$y$boxes[1, 5], 25)
})
test_that("item_transform_affine preserves image shape for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_affine(item, angle = 25, translate = c(10, 5),
scale = 1.2, shear = 8)
expect_tensor_shape(result$x, c(3, 100, 200))
})
test_that("item_transform_affine transforms the image like transform_affine", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
original_img <- item$x$clone()
result <- item_transform_affine(item, angle = 15, translate = c(4, -3),
scale = 0.9, shear = 5)
expected <- transform_affine(original_img, angle = 15, translate = c(4, -3),
scale = 0.9, shear = 5)
expect_true(torch_equal(result$x, expected))
})
test_that("item_transform_affine translates box centre", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_affine(item, translate = c(30, 10))
expect_equal_to_r(result$y$boxes, matrix(c(40, 30, 80, 70, 0), ncol = 5))
})
test_that("item_transform_affine scales boxes around the image center", {
item <- make_detection_item(matrix(c(80, 40, 120, 60), ncol = 4), image_size = c(100L, 200L))
result <- item_transform_affine(item, scale = 2)
expect_equal_to_r(result$y$boxes, matrix(c(60, 30, 140, 70, 0), ncol = 5))
})
test_that("item_transform_affine rotates boxes around the image center", {
item <- make_detection_item(matrix(c(50, 80, 150, 120), ncol = 4), image_size = c(200L, 200L))
result <- item_transform_affine(item, angle = 90)
expect_equal(as.numeric(result$y$boxes[1, 1]), 80, tolerance = 1e-4)
expect_equal(as.numeric(result$y$boxes[1, 2]), 50, tolerance = 1e-4)
expect_equal(as.numeric(result$y$boxes[1, 3]), 120, tolerance = 1e-4)
expect_equal(as.numeric(result$y$boxes[1, 4]), 150, tolerance = 1e-4)
expect_equal(as.numeric(result$y$boxes[1, 5]), 90, tolerance = 1e-4)
})
test_that("item_transform_affine handles empty boxes", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_affine(item, angle = 30)
expect_tensor_shape(result$y$boxes, c(0, 5))
})
test_that("item_transform_affine preserves labels for detection", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 5, 5, 15, 25), ncol = 4, byrow = TRUE),
labels = labels
)
result <- item_transform_affine(item, angle = 20)
expect_true(result$y$labels$eq(labels)$all()$item())
})
test_that("item_transform_affine does not mutate input for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
original_img <- item$x$clone()
original_boxes <- item$y$boxes$clone()
item_transform_affine(item, angle = 30, translate = c(10, 10))
expect_true(torch_equal(item$x, original_img))
expect_equal_to_r(item$y$boxes, as.array(original_boxes$cpu()))
})
test_that("item_transform_affine image dtype is preserved for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_affine(item, angle = 10)
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_affine can be composed", {
item <- make_detection_item(matrix(c(50, 80, 150, 120), ncol = 4), image_size = c(200L, 200L))
composed <- item |>
item_transform_affine(angle = 30) |>
item_transform_affine(angle = 30)
single <- item_transform_affine(item, angle = 60)
expect_s3_class(composed, "image_with_rotated_box")
expect_equal_to_r(composed$y$boxes, as.array(single$y$boxes$cpu()), tolerance = 1e-4)
})
test_that("item_transform_affine composed on empty boxes stays xyxyr", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
composed <- item |>
item_transform_affine(angle = 30) |>
item_transform_affine(angle = 30)
expect_tensor_shape(composed$y$boxes, c(0, 5))
})
test_that("item_transform_affine preserves image shape and class for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L))
result <- item_transform_affine(item, angle = 20, translate = c(5, 5), scale = 1.1)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_s3_class(result, "image_with_segmentation_mask")
})
test_that("item_transform_affine identity preserves masks", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_affine(item)
expect_true(result$y$masks$equal(original_masks))
})
test_that("item_transform_affine transforms masks and preserves dtype", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
result <- item_transform_affine(item, angle = 45)
expect_tensor_shape(result$y$masks, original_masks$shape)
expect_tensor_dtype(result$y$masks, torch_bool())
expect_false(result$y$masks$equal(original_masks))
})
test_that("item_transform_affine preserves labels for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_labels <- item$y$labels$clone()
result <- item_transform_affine(item, angle = 20)
expect_true(result$y$labels$eq(original_labels)$all()$item())
})
test_that("item_transform_affine works on a dataset", {
ds <- toy_item_dataset(function(index) {
make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
})
ds <- item_transform_affine(ds, translate = c(30, 10))
item <- ds$.getitem(1)
expect_s3_class(item, "image_with_rotated_box")
expect_equal_to_r(item$y$boxes, matrix(c(40, 30, 80, 70, 0), ncol = 5))
})
# item_transform_crop
test_that("item_transform_crop rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_crop(img, top = 1, left = 1, height = 50, width = 100),
"requires a dataset item"
)
expect_error(
item_transform_crop(42, top = 1, left = 1, height = 50, width = 100),
"requires a dataset item"
)
})
test_that("item_transform_crop crops detection items correctly", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
original_img <- item$x$clone()
result <- item_transform_crop(item, top = 11, left = 21, height = 80, width = 160)
expect_tensor_shape(result$x, c(3, 80, 160))
expect_equal(result$y$image_height, 80L)
expect_equal(result$y$image_width, 160L)
expect_true(torch_equal(result$x, transform_crop(original_img, top = 11, left = 21, height = 80, width = 160)))
# offset_x = left - 1 = 20, offset_y = top - 1 = 10
# new_x1 = max(0, 10 - 20) = 0, new_x2 = min(160, 50 - 20) = 30
# new_y1 = max(0, 20 - 10) = 10, new_y2 = min(80, 60 - 10) = 50
expect_equal_to_r(result$y$boxes[1, 1], 0)
expect_equal_to_r(result$y$boxes[1, 3], 30)
expect_equal_to_r(result$y$boxes[1, 2], 10)
expect_equal_to_r(result$y$boxes[1, 4], 50)
})
test_that("item_transform_crop removes boxes outside crop area", {
# Box at (150, 50, 180, 80) — intersects the crop (left = 140, width = 40, offset 139)
item <- make_detection_item(
matrix(c(150, 50, 180, 80), ncol = 4),
image_size = c(200L, 300L)
)
result <- item_transform_crop(item, top = 1, left = 140, height = 100, width = 40)
# offset_x = 139, new_x1 = 150 - 139 = 11, new_x2 = 180 - 139 = 41
# clipped: new_x1 = max(0, 11) = 11, new_x2 = min(40, 41) = 40
# keep = (40 > 11) & ... = TRUE
expect_tensor_shape(result$y$boxes, c(1, 4))
# Box at (200, 10, 250, 50) — entirely outside crop left=1, width=100 (x range [0, 100))
# offset_x = 0, new_x1 = 200, new_x2 = 250, clipped: x1=100, x2=100 → zero width
item_outside <- make_detection_item(
matrix(c(200, 10, 250, 50), ncol = 4),
image_size = c(200L, 300L)
)
result_outside <- item_transform_crop(item_outside, top = 1, left = 1, height = 100, width = 100)
expect_tensor_shape(result_outside$y$boxes, c(0, 4))
})
test_that("item_transform_crop handles multiple boxes and preserves labels", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 100, 20, 180, 80), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(100L, 200L)
)
result <- item_transform_crop(item, top = 11, left = 21, height = 80, width = 160)
expect_tensor_shape(result$y$boxes, c(2, 4))
expect_equal(result$y$labels$size(1), result$y$boxes$size(1))
})
test_that("item_transform_crop handles empty boxes", {
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_crop(item, top = 1, left = 1, height = 50, width = 100)
expect_tensor_shape(result$y$boxes, c(0, 4))
expect_tensor_dtype(result$y$boxes, torch_float())
})
test_that("item_transform_crop does not mutate input", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(torch_tensor(boxes))
original_img <- as_array(item$x)
original_class <- class(item)
result <- item_transform_crop(item, top = 6, left = 6, height = 50, width = 100)
expect_equal_to_r(item$x, original_img)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
})
test_that("item_transform_crop preserves class and dtype for detection", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4))
result <- item_transform_crop(item, top = 1, left = 1, height = 50, width = 100)
expect_s3_class(result, "image_with_bounding_box")
expect_tensor_dtype(result$x, item$x$dtype)
})
test_that("item_transform_crop transforms segmentation items", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
original_labels <- as.integer(as_array(item$y$labels))
original_dtype <- item$x$dtype
result <- item_transform_crop(item, top = 11, left = 21, height = 80, width = 160)
expected_masks <- transform_crop(original_masks, top = 11, left = 21, height = 80, width = 160)
expect_tensor_shape(result$x, c(3, 80, 160))
expect_tensor_dtype(result$x, original_dtype)
expect_tensor_shape(result$y$masks, c(2, 80, 160))
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(expected_masks))
expect_equal_to_r(result$y$labels, original_labels)
expect_equal(result$y$image_height, 80L)
expect_equal(result$y$image_width, 160L)
expect_s3_class(result, "image_with_segmentation_mask")
})
test_that("item_transform_crop transforms rotated-box items", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
rotated <- item_transform_rotate(item, angle = 30)
original_angles <- as_array(rotated$y$boxes[, 5])
result <- item_transform_crop(rotated, top = 90, left = 20, height = 100, width = 120)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$x, c(3, 100, 120))
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 120L)
expect_equal_to_r(result$y$boxes[, 5], original_angles)
})
test_that("item_transform_crop removes rotated boxes outside crop area", {
item <- make_detection_item(
matrix(c(150, 50, 180, 80), ncol = 4),
image_size = c(200L, 300L)
)
rotated <- item_transform_rotate(item, angle = 30)
result <- item_transform_crop(rotated, top = 1, left = 140, height = 100, width = 40)
expect_tensor_shape(result$y$boxes, c(0, 5))
})
# item_transform_pad
test_that("item_transform_pad rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_pad(img, padding = 10),
"requires a dataset item"
)
expect_error(
item_transform_pad(42, padding = 10),
"requires a dataset item"
)
})
test_that("item_transform_pad supports single-int, length-2, and length-4 padding", {
item <- make_detection_item(matrix(c(10, 20, 50, 60), ncol = 4), image_size = c(100L, 200L))
single <- item_transform_pad(item, padding = 10)
expect_tensor_shape(single$x, c(3, 120, 220))
expect_true(torch_equal(single$x, transform_pad(item$x, 10)))
expect_equal_to_r(single$y$boxes[1, ], c(20, 30, 60, 70))
expect_equal(single$y$image_height, 120L)
expect_equal(single$y$image_width, 220L)
len2 <- item_transform_pad(item, padding = c(5, 15))
expect_tensor_shape(len2$x, c(3, 130, 210))
expect_equal_to_r(len2$y$boxes[1, 1], 15)
expect_equal_to_r(len2$y$boxes[1, 2], 35)
expect_equal(len2$y$image_height, 130L)
expect_equal(len2$y$image_width, 210L)
len4 <- item_transform_pad(item, padding = c(5, 7, 9, 11))
expect_tensor_shape(len4$x, c(3, 120, 212))
expect_equal_to_r(len4$y$boxes[1, ], c(15, 29, 55, 69))
expect_equal(len4$y$image_height, 120L)
expect_equal(len4$y$image_width, 212L)
})
test_that("item_transform_pad preserves labels and metadata", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 5, 5, 15, 25), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(100L, 200L)
)
result <- item_transform_pad(item, padding = 5)
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_tensor_shape(result$y$area, c(2))
expect_tensor_shape(result$y$iscrowd, c(2))
expect_equal(result$y$image_height, 110L)
expect_equal(result$y$image_width, 210L)
})
test_that("item_transform_pad handles box edge cases", {
empty <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result_empty <- item_transform_pad(empty, padding = 5)
expect_tensor_shape(result_empty$y$boxes, c(0, 4))
expect_tensor_dtype(result_empty$y$boxes, torch_float())
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
multi <- make_detection_item(boxes, image_size = c(500L, 600L))
result_multi <- item_transform_pad(multi, padding = 10)
expect_tensor_shape(result_multi$y$boxes, c(3, 4))
expect_equal_to_r(result_multi$y$boxes[1, 1], 20)
expect_equal_to_r(result_multi$y$boxes[1, 3], 60)
expect_equal_to_r(result_multi$y$boxes[2, 1], 110)
expect_equal_to_r(result_multi$y$boxes[2, 3], 160)
expect_equal_to_r(result_multi$y$boxes[3, 1], 10)
expect_equal_to_r(result_multi$y$boxes[3, 3], 310)
})
test_that("item_transform_pad fills pixels without mutating input", {
boxes <- matrix(c(2, 2, 4, 4), ncol = 4)
item <- make_detection_item(torch_tensor(boxes), image_size = c(10L, 10L))
item$x <- torch_ones(3, 10, 10)
original_img <- as_array(item$x)
original_class <- class(item)
result <- item_transform_pad(item, padding = 2)
expect_equal(as.numeric(result$x[1, 1, 1]$cpu()), 0)
expect_equal(as.numeric(result$x[1, 3, 3]$cpu()), 1)
expect_equal(as.numeric(result$x[1, 2, 11]$cpu()), 0)
result_filled <- item_transform_pad(item, padding = 2, fill = 1)
expect_equal(as.numeric(result_filled$x[1, 1, 1]$cpu()), 1)
expect_equal_to_r(item$x, original_img)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
expect_equal(class(result), original_class)
item$x <- torch_randn(3, 100, 200, dtype = torch_float64())
result_dtype <- item_transform_pad(item, padding = 5)
expect_tensor_dtype(result_dtype$x, torch_float64())
})
test_that("item_transform_pad pads masks for segmentation", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
original_masks <- item$y$masks$clone()
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_pad(item, padding = 5)
expect_s3_class(result, "image_with_segmentation_mask")
expect_tensor_shape(result$x, c(3, 110, 210))
expect_tensor_shape(result$y$masks, c(2, 110, 210))
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(torch_equal(result$y$masks, transform_pad(original_masks, 5, fill = 0)))
expect_equal_to_r(result$y$labels, original_labels)
expect_equal(result$y$image_height, 110L)
expect_equal(result$y$image_width, 210L)
})
test_that("item_transform_pad handles rotated boxes", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(boxes, image_size = c(100L, 200L))
rotated <- item_transform_rotate(item, angle = 30)
original_boxes <- rotated$y$boxes$clone()
result <- item_transform_pad(rotated, padding = 5)
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_equal_to_r(result$y$boxes[1, 1], as_array(original_boxes[1, 1]) + 5)
expect_equal_to_r(result$y$boxes[1, 2], as_array(original_boxes[1, 2]) + 5)
expect_equal_to_r(result$y$boxes[1, 3], as_array(original_boxes[1, 3]) + 5)
expect_equal_to_r(result$y$boxes[1, 4], as_array(original_boxes[1, 4]) + 5)
expect_equal_to_r(result$y$boxes[1, 5], 30, tolerance = 1e-5)
})
test_that("item_transform_pad negative padding clips and drops boxes outside the crop", {
item <- make_detection_item(
matrix(c(0, 0, 10, 10, 120, 70, 180, 130), ncol = 4, byrow = TRUE),
labels = torch_tensor(c(1L, 2L), dtype = torch_long()),
image_size = c(200L, 400L)
)
result <- item_transform_pad(item, padding = c(-50, 0, 0, 0))
expect_equal_to_r(result$y$boxes, matrix(c(70, 70, 130, 130), ncol = 4))
expect_equal_to_r(result$y$labels, 2L)
expect_equal(result$y$image_height, 200L)
expect_equal(result$y$image_width, 350L)
})
# --- item_transform_perspective tests ---
test_that("item_transform_perspective rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
ep <- sp
expect_error(
item_transform_perspective(img, sp, ep),
"requires a dataset item"
)
expect_error(
item_transform_perspective(42, sp, ep),
"requires a dataset item"
)
})
test_that("item_transform_perspective applies box transforms without mutating input", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(torch_tensor(boxes), image_size = c(100L, 200L))
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
original_img <- as_array(item$x)
original_class <- class(item)
identity <- item_transform_perspective(item, sp, sp)
expect_s3_class(identity, "image_with_bounding_box")
expect_tensor_shape(identity$x, c(3, 100, 200))
expect_true(identity$x$allclose(item$x, atol = 1e-4))
expect_equal_to_r(identity$y$boxes, boxes)
expect_equal(identity$x$dtype, item$x$dtype)
ep <- list(c(10, 20), c(209, 20), c(209, 119), c(10, 119))
translated <- item_transform_perspective(item, sp, ep)
expect_equal_to_r(translated$y$boxes[1, 1], 20)
expect_equal_to_r(translated$y$boxes[1, 2], 40)
expect_equal_to_r(translated$y$boxes[1, 3], 60)
expect_equal_to_r(translated$y$boxes[1, 4], 80)
expect_equal_to_r(item$x, original_img)
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
})
test_that("item_transform_perspective preserves labels and metadata", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(10, 20, 50, 60, 100, 30, 150, 80), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(100L, 200L)
)
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
result <- item_transform_perspective(item, sp, sp)
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
})
test_that("item_transform_perspective handles box edge cases", {
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
empty <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result_empty <- item_transform_perspective(empty, sp, sp)
expect_tensor_shape(result_empty$y$boxes, c(0, 4))
expect_tensor_dtype(result_empty$y$boxes, torch_float())
boxes <- matrix(c(
10, 20, 50, 60,
100, 200, 150, 250,
0, 0, 300, 400
), ncol = 4, byrow = TRUE)
multi <- make_detection_item(boxes, image_size = c(500L, 600L))
sp600 <- list(c(0, 0), c(599, 0), c(599, 499), c(0, 499))
ep600 <- list(c(10, 20), c(609, 20), c(609, 519), c(10, 519))
result_multi <- item_transform_perspective(multi, sp600, ep600)
expect_tensor_shape(result_multi$y$boxes, c(3, 4))
expect_equal_to_r(result_multi$y$boxes[, 1], boxes[, 1] + 10)
expect_equal_to_r(result_multi$y$boxes[, 2], boxes[, 2] + 20)
expect_equal_to_r(result_multi$y$boxes[, 3], pmin(600, boxes[, 3] + 10))
expect_equal_to_r(result_multi$y$boxes[, 4], pmin(500, boxes[, 4] + 20))
outside <- make_detection_item(matrix(c(80, 80, 90, 90), ncol = 4), image_size = c(100L, 100L))
sp100 <- list(c(0, 0), c(99, 0), c(99, 99), c(0, 99))
ep100 <- list(c(50, 0), c(149, 0), c(149, 99), c(50, 99))
result_outside <- item_transform_perspective(outside, sp100, ep100)
expect_tensor_shape(result_outside$y$boxes, c(0, 4))
expect_tensor_shape(result_outside$y$labels, 0L)
})
test_that("item_transform_perspective transforms segmentation masks", {
item <- make_segmentation_item(image_size = c(100L, 200L), num_masks = 2L)
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
ep <- list(c(10, 5), c(190, 0), c(195, 95), c(5, 100))
original_masks <- item$y$masks$clone()
original_dtype <- item$x$dtype
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_perspective(item, sp, ep)
expect_s3_class(result, "image_with_segmentation_mask")
expect_tensor_dtype(result$x, original_dtype)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_tensor_shape(result$y$masks, original_masks$shape)
expect_tensor_dtype(result$y$masks, torch_bool())
expect_equal_to_r(result$y$labels, original_labels)
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expected_masks <- transform_perspective(original_masks, startpoints = sp, endpoints = ep,
interpolation = 0)
expect_true(result$y$masks$equal(expected_masks))
})
test_that("item_transform_perspective can be composed", {
boxes <- matrix(c(10, 20, 50, 60), ncol = 4)
item <- make_detection_item(boxes, image_size = c(100L, 200L))
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
ep <- list(c(10, 0), c(209, 0), c(209, 99), c(10, 99))
result <- item |>
item_transform_perspective(sp, ep) |>
item_transform_hflip()
expect_s3_class(result, "image_with_bounding_box")
# after shifting right by 10 then flipping horizontally: x -> 200 - (x + 10)
expect_equal_to_r(result$y$boxes[1, 1], 200 - 60)
expect_equal_to_r(result$y$boxes[1, 3], 200 - 20)
expect_equal_to_r(result$y$boxes[1, 2], 20)
expect_equal_to_r(result$y$boxes[1, 4], 60)
})
test_that("item_transform_perspective applies to datasets", {
sp <- list(c(0, 0), c(199, 0), c(199, 99), c(0, 99))
ep <- list(c(10, 20), c(209, 20), c(209, 119), c(10, 119))
items <- list(
make_detection_item(matrix(c(10, 10, 20, 20), ncol = 4)),
make_detection_item(matrix(c(5, 5, 15, 15), ncol = 4))
)
ds <- toy_item_dataset(function(index) items[[index]], size = length(items))
transformed <- item_transform_perspective(ds, sp, ep)
item1 <- transformed$.getitem(1)
item2 <- transformed$.getitem(2)
expect_s3_class(item1, "image_with_bounding_box")
expect_equal_to_r(item1$y$boxes[1, 1], 20)
expect_equal_to_r(item1$y$boxes[1, 2], 30)
expect_equal_to_r(item1$y$boxes[1, 3], 30)
expect_equal_to_r(item1$y$boxes[1, 4], 40)
expect_equal_to_r(item2$y$boxes[1, 1], 15)
expect_equal_to_r(item2$y$boxes[1, 2], 25)
expect_equal_to_r(item2$y$boxes[1, 3], 25)
expect_equal_to_r(item2$y$boxes[1, 4], 35)
})
# item_transform_resize
test_that("item_transform_resize rejects non-item inputs", {
img <- torch_randn(3, 100, 200)
expect_error(
item_transform_resize(img, size = c(50, 100)),
"requires a dataset item"
)
expect_error(
item_transform_resize(42, size = c(50, 100)),
"requires a dataset item"
)
})
test_that("item_transform_resize resizes detection items and rescales boxes", {
boxes <- matrix(c(120, 70, 180, 130), ncol = 4)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
original_img <- item$x$clone()
original_class <- class(item)
result <- item_transform_resize(item, size = c(100L, 200L))
expect_s3_class(result, "image_with_bounding_box")
expect_tensor_dtype(result$x, item$x$dtype)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expect_true(torch_equal(result$x, transform_resize(original_img, size = c(100L, 200L))))
expect_equal_to_r(result$y$boxes[1, ], boxes[1, ] / 2)
# a bare integer size matches the smaller edge
bare <- item_transform_resize(item, size = 100)
expect_true(torch_equal(bare$x, result$x))
expect_true(torch_equal(bare$y$boxes, result$y$boxes))
# input is not mutated
expect_equal_to_r(item$x, as_array(original_img))
expect_equal_to_r(item$y$boxes, boxes)
expect_equal(class(item), original_class)
})
test_that("item_transform_resize rescales boxes with an anisotropic size", {
boxes <- matrix(c(
120, 70, 180, 130,
0, 0, 400, 200
), ncol = 4, byrow = TRUE)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
result <- item_transform_resize(item, size = c(100L, 100L))
expect_tensor_shape(result$x, c(3, 100, 100))
expect_tensor_shape(result$y$boxes, c(2, 4))
expect_equal_to_r(result$y$boxes[1, ], c(30, 35, 45, 65))
expect_equal_to_r(result$y$boxes[2, ], c(0, 0, 100, 100))
})
test_that("item_transform_resize preserves labels and handles empty boxes", {
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(
matrix(c(120, 70, 180, 130, 210, 80, 280, 120), ncol = 4, byrow = TRUE),
labels = labels,
image_size = c(200L, 400L)
)
result <- item_transform_resize(item, size = c(100L, 200L))
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
item <- make_detection_item(
boxes = matrix(numeric(0), ncol = 4),
labels = torch_zeros(0L, dtype = torch_long())
)
result <- item_transform_resize(item, size = c(50L, 100L))
expect_tensor_shape(result$y$boxes, c(0, 4))
expect_tensor_dtype(result$y$boxes, torch_float())
})
test_that("item_transform_resize resizes segmentation items", {
item <- make_segmentation_item(image_size = c(200L, 400L), num_masks = 2L)
original_img <- item$x$clone()
original_masks <- item$y$masks$clone()
original_labels <- as.integer(as_array(item$y$labels))
result <- item_transform_resize(item, size = c(100L, 200L))
expect_s3_class(result, "image_with_segmentation_mask")
expect_tensor_dtype(result$x, item$x$dtype)
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal(result$y$image_height, 100L)
expect_equal(result$y$image_width, 200L)
expect_equal_to_r(result$y$labels, original_labels)
expect_true(torch_equal(result$x, transform_resize(original_img, size = c(100L, 200L))))
expected_masks <- transform_resize(original_masks, size = c(100L, 200L), interpolation = 0)
expect_tensor_shape(result$y$masks, c(2, 100, 200))
expect_tensor_dtype(result$y$masks, torch_bool())
expect_true(result$y$masks$equal(expected_masks))
})
test_that("item_transform_resize handles items without any mask", {
item <- make_segmentation_item(image_size = c(200L, 400L), num_masks = 0L)
result <- item_transform_resize(item, size = c(100L, 200L))
expect_tensor_shape(result$y$masks, c(0, 100, 200))
expect_tensor_dtype(result$y$masks, torch_bool())
})
test_that("item_transform_resize keeps masks aligned with the image for a bare integer size", {
item <- make_segmentation_item(image_size = c(101L, 199L), num_masks = 2L)
result <- item_transform_resize(item, size = 50)
expect_equal(result$y$masks$shape[2], result$x$shape[2])
expect_equal(result$y$masks$shape[3], result$x$shape[3])
expect_equal(result$y$image_height, result$x$shape[2])
expect_equal(result$y$image_width, result$x$shape[3])
})
test_that("item_transform_resize handles rotated boxes", {
boxes <- matrix(c(120, 70, 180, 130), ncol = 4)
item <- make_detection_item(boxes, image_size = c(200L, 400L))
rotated <- item_transform_rotate(item, angle = 30)
original_angle <- as_array(rotated$y$boxes[1, 5])
result <- item_transform_resize(rotated, size = c(100L, 200L))
expect_s3_class(result, "image_with_rotated_box")
expect_tensor_shape(result$y$boxes, c(1, 5))
expect_tensor_dtype(result$y$boxes, torch_float())
expect_equal_to_r(result$y$boxes[1, 5], original_angle)
})
test_that("item_transform_resize can be composed", {
boxes <- matrix(c(120, 70, 180, 130, 210, 80, 280, 120), ncol = 4, byrow = TRUE)
labels <- torch_tensor(c(1L, 2L), dtype = torch_long())
item <- make_detection_item(boxes, labels = labels, image_size = c(200L, 400L))
result <- item |>
item_transform_resize(size = c(100L, 200L)) |>
item_transform_hflip()
expect_s3_class(result, "image_with_bounding_box")
expect_tensor_shape(result$x, c(3, 100, 200))
expect_equal_to_r(result$y$labels, as.integer(as_array(labels)))
expect_equal_to_r(result$y$boxes[1, ], c(200 - 90, 35, 200 - 60, 65))
expect_equal_to_r(result$y$boxes[2, ], c(200 - 140, 40, 200 - 105, 60))
})
test_that("item_transform_resize works on detection and segmentation datasets", {
ds <- toy_item_dataset(function(index) {
make_detection_item(matrix(c(120, 70, 180, 130), ncol = 4), image_size = c(200L, 400L))
})
ds <- item_transform_resize(ds, size = c(100L, 200L))
item <- ds$.getitem(1)
expect_s3_class(item, "image_with_bounding_box")
expect_tensor_shape(item$x, c(3, 100, 200))
expect_equal_to_r(item$y$boxes[1, ], c(60, 35, 90, 65))
expect_equal(item$y$image_height, 100L)
expect_equal(item$y$image_width, 200L)
ds <- toy_item_dataset(function(index) {
make_segmentation_item(image_size = c(200L, 400L), num_masks = 2L)
})
ds <- item_transform_resize(ds, size = c(100L, 200L))
item <- ds$.getitem(1)
expect_s3_class(item, "image_with_segmentation_mask")
expect_tensor_shape(item$x, c(3, 100, 200))
expect_tensor_shape(item$y$masks, c(2, 100, 200))
})
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.