Skip to content

Commit 1b9f1ae

Browse files
Feat: add item_transform_center_crop for detection and segmentation items (#370)
* Delegate center_crop_item cropping to transform_center_crop * Merge center_crop tests and add composition test * Apply differentiated per-class handling in item_transform_center_crop * Resize plots in center crop doc example before grid --------- Co-authored-by: cregouby <cregouby@users.noreply.github.com>
1 parent 2e7f991 commit 1b9f1ae

9 files changed

Lines changed: 406 additions & 2 deletions

NAMESPACE

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ S3method(draw_segmentation_masks,image_with_segmentation_mask)
99
S3method(draw_segmentation_masks,torch_tensor)
1010
S3method(get_image_size,"magick-image")
1111
S3method(get_image_size,torch_tensor)
12+
S3method(item_transform_center_crop,dataset)
13+
S3method(item_transform_center_crop,default)
14+
S3method(item_transform_center_crop,image_with_bounding_box)
15+
S3method(item_transform_center_crop,image_with_rotated_box)
16+
S3method(item_transform_center_crop,image_with_segmentation_mask)
1217
S3method(item_transform_hflip,dataset)
1318
S3method(item_transform_hflip,default)
1419
S3method(item_transform_hflip,image_with_bounding_box)
@@ -133,6 +138,7 @@ export(imagenet_1k_classes)
133138
export(imagenet_21k_classes)
134139
export(imagenet_21k_df)
135140
export(imagenet_classes)
141+
export(item_transform_center_crop)
136142
export(item_transform_hflip)
137143
export(item_transform_rotate)
138144
export(item_transform_vflip)

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* Added `item_transform_hflip()` for horizontally flipping dataset items, with support for detection and segmentation item types and datasets (@DerrickUnleashed, #366).
2121
* Added `item_transform_vflip()` for vertically flipping dataset items, with support for detection and segmentation item types and datasets (@DerrickUnleashed, #369).
2222
* Added SAHI (Slicing Aided Hyper Inference) support via a three-function pipeline: `prepare_sahi_split()` precomputes overlapping crop windows, then `transform_sahi_crop()` slices images and `target_transform_sahi_crop()` adjusts detection targets per crop. (@DerrickUnleashed, #324)
23+
* Added `item_transform_center_crop()` for cropping images from the center to a specified size for dataset items, with support for detection and segmentation item types and datasets (@DerrickUnleashed, #370).
2324

2425
## Bug fixes and improvements
2526

R/item-transforms-geometry.R

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,3 +328,182 @@ item_transform_vflip.image_with_rotated_box <- function(x) {
328328

329329
x
330330
}
331+
332+
# ---- item_transform_center_crop ----
333+
334+
#' Center crop a dataset item
335+
#'
336+
#' Center crops the image inside a dataset item. For detection items,
337+
#' bounding box coordinates are adjusted to remain correct after cropping.
338+
#' For segmentation items, both the image and the masks are center cropped.
339+
#' If the image is smaller than the requested crop size along any edge,
340+
#' the image is padded with zeros and then center cropped.
341+
#'
342+
#' @param x A dataset item, typically an \code{image_with_bounding_box} or
343+
#' \code{image_with_segmentation_mask} object containing an image tensor
344+
#' and associated target data.
345+
#' @param size Desired output size. If \code{size} is an integer vector of
346+
#' length 2 like \code{c(h, w)}, the output will be matched to this.
347+
#' If \code{size} is a bare integer, a square crop of \code{c(size, size)}
348+
#' is made.
349+
#'
350+
#' @return A dataset item of the same class with the image and target
351+
#' center cropped.
352+
#'
353+
#' @examples
354+
#' \dontrun{
355+
#' url <- "https://upload.wikimedia.org/wikipedia/commons/b/b6/Felis_catus-cat_on_snow.jpg"
356+
#' img <- base_loader(url) |> transform_to_tensor()
357+
#'
358+
#' boxes <- torch_tensor(matrix(c(600, 200, 2880, 1860), ncol = 4), dtype = torch_float32())
359+
#'
360+
#' before <- list(x = img, y = list(boxes = boxes, labels = "cat"))
361+
#' class(before) <- c("image_with_bounding_box", "list")
362+
#'
363+
#' after <- item_transform_center_crop(before, size = c(1500, 1500))
364+
#'
365+
#' before_plot <- draw_bounding_boxes(before, colors = "blue", width = 10)$to(torch_float())$div(255)
366+
#' after_plot <- draw_bounding_boxes(after, colors = "red", width = 10)$to(torch_float())$div(255)
367+
#'
368+
#' before_plot <- transform_resize(before_plot, c(1500, 1500))
369+
#' after_plot <- transform_resize(after_plot, c(1500, 1500))
370+
#'
371+
#' grid <- vision_make_grid(torch_stack(list(before_plot, after_plot)), scale = TRUE)
372+
#' tensor_image_browse(grid)
373+
#' }
374+
#'
375+
#' @family item_unitary_transforms
376+
#'
377+
#' @export
378+
item_transform_center_crop <- function(x, size) {
379+
UseMethod("item_transform_center_crop", x)
380+
}
381+
382+
#' @export
383+
item_transform_center_crop.default <- function(x, size) {
384+
cli_abort(
385+
"{.fn item_transform_center_crop} requires a dataset item (a list with {.var x} and {.var y} fields), not {.obj_type_friendly {x}}.
386+
To center crop a raw image tensor, use {.fn transform_center_crop} instead."
387+
)
388+
}
389+
390+
#' @export
391+
item_transform_center_crop.dataset <- function(x, size) {
392+
original_getitem <- x$.getitem
393+
unlockBinding(".getitem", as.environment(x))
394+
x$.getitem <- function(index) {
395+
item <- original_getitem(index)
396+
item_transform_center_crop(item, size = size)
397+
}
398+
x
399+
}
400+
401+
#' @export
402+
item_transform_center_crop.image_with_bounding_box <- function(x, size) {
403+
output_size <- as.integer(if (length(size) == 1) rep(size, 2) else size)
404+
crop_h <- output_size[1]
405+
crop_w <- output_size[2]
406+
407+
img_size <- get_image_size(x$x)
408+
img_w <- img_size[1]
409+
img_h <- img_size[2]
410+
411+
# crop offsets are relative to the (possibly padded) image, see
412+
# transform_center_crop
413+
crop_top <- as.integer((max(img_h, crop_h) - crop_h) / 2)
414+
crop_left <- as.integer((max(img_w, crop_w) - crop_w) / 2)
415+
416+
if (crop_top == 0L) crop_top <- 1L
417+
if (crop_left == 0L) crop_left <- 1L
418+
419+
x$x <- transform_center_crop(x$x, size)
420+
421+
left_offset <- crop_left - 1L
422+
top_offset <- crop_top - 1L
423+
424+
boxes <- x$y$boxes$clone()
425+
if (boxes$size(1) > 0) {
426+
boxes[, 1] <- torch_clamp(boxes[, 1] - left_offset, 0, crop_w)
427+
boxes[, 3] <- torch_clamp(boxes[, 3] - left_offset, 0, crop_w)
428+
boxes[, 2] <- torch_clamp(boxes[, 2] - top_offset, 0, crop_h)
429+
boxes[, 4] <- torch_clamp(boxes[, 4] - top_offset, 0, crop_h)
430+
431+
keep <- as.logical((boxes[, 3] > boxes[, 1]) & (boxes[, 4] > boxes[, 2]))
432+
if (!all(keep)) {
433+
boxes <- boxes[keep, ]
434+
x$y$labels <- x$y$labels[keep]
435+
if (!is.null(x$y$area)) {
436+
x$y$area <- x$y$area[keep]
437+
}
438+
if (!is.null(x$y$iscrowd)) {
439+
x$y$iscrowd <- x$y$iscrowd[keep]
440+
}
441+
}
442+
}
443+
x$y$boxes <- boxes
444+
x$y$image_height <- crop_h
445+
x$y$image_width <- crop_w
446+
447+
x
448+
}
449+
450+
#' @export
451+
item_transform_center_crop.image_with_segmentation_mask <- function(x, size) {
452+
output_size <- as.integer(if (length(size) == 1) rep(size, 2) else size)
453+
454+
x$x <- transform_center_crop(x$x, size)
455+
x$y$masks <- transform_center_crop(x$y$masks, size)
456+
x$y$image_height <- output_size[1]
457+
x$y$image_width <- output_size[2]
458+
459+
x
460+
}
461+
462+
#' @export
463+
item_transform_center_crop.image_with_rotated_box <- function(x, size) {
464+
output_size <- as.integer(if (length(size) == 1) rep(size, 2) else size)
465+
crop_h <- output_size[1]
466+
crop_w <- output_size[2]
467+
468+
img_size <- get_image_size(x$x)
469+
img_w <- img_size[1]
470+
img_h <- img_size[2]
471+
472+
# crop offsets are relative to the (possibly padded) image, see
473+
# transform_center_crop
474+
crop_top <- as.integer((max(img_h, crop_h) - crop_h) / 2)
475+
crop_left <- as.integer((max(img_w, crop_w) - crop_w) / 2)
476+
477+
if (crop_top == 0L) crop_top <- 1L
478+
if (crop_left == 0L) crop_left <- 1L
479+
480+
x$x <- transform_center_crop(x$x, size)
481+
482+
left_offset <- crop_left - 1L
483+
top_offset <- crop_top - 1L
484+
485+
boxes <- x$y$boxes$clone()
486+
if (boxes$size(1) > 0) {
487+
boxes[, 1] <- torch_clamp(boxes[, 1] - left_offset, 0, crop_w)
488+
boxes[, 3] <- torch_clamp(boxes[, 3] - left_offset, 0, crop_w)
489+
boxes[, 2] <- torch_clamp(boxes[, 2] - top_offset, 0, crop_h)
490+
boxes[, 4] <- torch_clamp(boxes[, 4] - top_offset, 0, crop_h)
491+
492+
keep <- as.logical((boxes[, 3] > boxes[, 1]) & (boxes[, 4] > boxes[, 2]))
493+
if (!all(keep)) {
494+
boxes <- boxes[keep, ]
495+
x$y$labels <- x$y$labels[keep]
496+
if (!is.null(x$y$area)) {
497+
x$y$area <- x$y$area[keep]
498+
}
499+
if (!is.null(x$y$iscrowd)) {
500+
x$y$iscrowd <- x$y$iscrowd[keep]
501+
}
502+
}
503+
}
504+
x$y$boxes <- boxes
505+
x$y$image_height <- crop_h
506+
x$y$image_width <- crop_w
507+
508+
x
509+
}

R/transforms-defaults.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ transform_center_crop.default <- function(img, size) {
4949
img <- transform_pad(img, padding_ltrb, fill = 0) # PIL uses fill value 0
5050

5151
size <- get_image_size(img)
52-
image_height <- size[1]
53-
image_width <- size[2]
52+
image_height <- size[2]
53+
image_width <- size[1]
5454

5555
if (crop_width == image_width && crop_height == image_height) return(img)
5656
}

man/item_transform_center_crop.Rd

Lines changed: 59 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/item_transform_hflip.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/item_transform_rotate.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/item_transform_vflip.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)