From efc4e52a7404ef68ed3d3a59a2b51bb8dfa76d6e Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 7 Jan 2026 10:46:48 +0530 Subject: [PATCH 1/2] feat: add Cityscapes dataset for instance segmentation - Support fine and coarse annotation modes - Support instance, semantic, polygon, and color target types - Compatible with draw_segmentation_masks function - 19 semantic classes for urban scene understanding - Comprehensive test coverage with 18 test cases - Complete documentation with usage examples Closes #138 --- DESCRIPTION | 1 + NAMESPACE | 1 + R/dataset-cityscapes.R | 358 ++++++++++++++++++++++ man/cityscapes_dataset.Rd | 168 +++++++++++ tests/testthat/test-dataset-cityscapes.R | 362 +++++++++++++++++++++++ 5 files changed, 890 insertions(+) create mode 100644 R/dataset-cityscapes.R create mode 100644 man/cityscapes_dataset.Rd create mode 100644 tests/testthat/test-dataset-cityscapes.R diff --git a/DESCRIPTION b/DESCRIPTION index fe0c2cc0..14b869cd 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -73,6 +73,7 @@ Collate: 'conditions.R' 'dataset-caltech.R' 'dataset-cifar.R' + 'dataset-cityscapes.R' 'dataset-coco.R' 'dataset-eurosat.R' 'dataset-fer.R' diff --git a/NAMESPACE b/NAMESPACE index ee2eb0a1..d4b53fc3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -78,6 +78,7 @@ export(caltech101_dataset) export(caltech256_dataset) export(cifar100_dataset) export(cifar10_dataset) +export(cityscapes_dataset) export(clip_boxes_to_image) export(coco_caption_dataset) export(coco_detection_dataset) diff --git a/R/dataset-cityscapes.R b/R/dataset-cityscapes.R new file mode 100644 index 00000000..196e0091 --- /dev/null +++ b/R/dataset-cityscapes.R @@ -0,0 +1,358 @@ +#' Cityscapes Dataset for Instance and Semantic Segmentation +#' +#' The Cityscapes dataset contains street scene images from 50 European cities +#' with pixel-level annotations for semantic and instance segmentation tasks. +#' This dataset is widely used for urban scene understanding research. +#' +#' @param root Character. Root directory where dataset is stored (default: `tempdir()`). +#' @param split Character. One of "train", "val", or "test" (default: "train"). +#' @param mode Character. Annotation quality: "fine" for high-quality dense annotations +#' or "coarse" for weaker annotations (default: "fine"). +#' @param target_type Character vector. Types of annotations to load. Can include: +#' \itemize{ +#' \item "instance": Instance segmentation masks (each object has unique ID) +#' \item "semantic": Semantic segmentation masks (class IDs only) +#' \item "polygon": Polygon annotations in JSON format +#' \item "color": Color-coded visualization of annotations +#' } +#' Multiple types can be specified (default: "instance"). +#' @param transform Function to transform the input image (default: NULL). +#' @param target_transform Function to transform the target annotations (default: NULL). +#' +#' @return A torch dataset object. Each item is a named list: +#' \itemize{ +#' \item `x`: RGB image array of shape (H, W, 3) or transformed tensor +#' \item `y`: Named list containing requested target types: +#' \itemize{ +#' \item `instance`: Instance segmentation mask (H, W) with unique IDs per object +#' \item `semantic`: Semantic segmentation mask (H, W) with class IDs +#' \item `color`: Color-coded visualization (H, W, 3) +#' \item `polygon`: Polygon annotations (nested list structure) +#' } +#' } +#' +#' @section Dataset Structure: +#' The Cityscapes dataset includes: +#' \itemize{ +#' \item 5,000 finely annotated images (2,975 train, 500 val, 1,525 test) +#' \item 20,000 coarsely annotated images +#' \item 19 semantic classes for urban scenes +#' \item Dense pixel-level annotations at 1024x2048 resolution +#' \item Images from 50 different European cities +#' } +#' +#' @section Semantic Classes: +#' The dataset includes 19 evaluation classes: +#' road, sidewalk, building, wall, fence, pole, traffic light, traffic sign, +#' vegetation, terrain, sky, person, rider, car, truck, bus, train, motorcycle, +#' bicycle. +#' +#' @section Download: +#' Cityscapes requires manual download and registration: +#' \enumerate{ +#' \item Register at \url{https://www.cityscapes-dataset.com/} +#' \item Download packages: +#' \itemize{ +#' \item leftImg8bit_trainvaltest.zip (11GB) - RGB images +#' \item gtFine_trainvaltest.zip (241MB) - Fine annotations +#' \item gtCoarse.zip (1.3GB) - Coarse annotations (optional) +#' } +#' \item Extract to root directory +#' } +#' +#' Expected directory structure: +#' \preformatted{ +#' root/ +#' ├── leftImg8bit/ +#' │ ├── train/ +#' │ ├── val/ +#' │ └── test/ +#' ├── gtFine/ +#' │ ├── train/ +#' │ ├── val/ +#' │ └── test/ +#' └── gtCoarse/ +#' ├── train/ +#' ├── train_extra/ +#' └── val/ +#' } +#' +#' @section Integration with draw_segmentation_masks: +#' The output is directly compatible with \code{\link{draw_segmentation_masks}}: +#' \preformatted{ +#' item <- dataset[1] +#' # For instance segmentation +#' overlay <- draw_segmentation_masks(item$x, item$y$instance > 0) +#' # For semantic segmentation +#' overlay <- draw_segmentation_masks(item$x, item$y$semantic == class_id) +#' } +#' +#' @examples +#' \dontrun{ +#' # Load Cityscapes with instance segmentation +#' cityscapes_train <- cityscapes_dataset( +#' root = "~/datasets/cityscapes", +#' split = "train", +#' mode = "fine", +#' target_type = "instance", +#' transform = transform_to_tensor +#' ) +#' +#' # Get first item +#' first <- cityscapes_train[1] +#' first$x # Image tensor (3, H, W) +#' first$y$instance # Instance mask (H, W) +#' +#' # Visualize with draw_segmentation_masks +#' # Create boolean mask for all instances +#' mask <- first$y$instance > 0 +#' overlay <- draw_segmentation_masks(first$x, mask$unsqueeze(1), alpha = 0.5) +#' tensor_image_browse(overlay) +#' +#' # Load with multiple target types +#' cityscapes_multi <- cityscapes_dataset( +#' root = "~/datasets/cityscapes", +#' split = "val", +#' mode = "fine", +#' target_type = c("instance", "semantic"), +#' transform = transform_to_tensor +#' ) +#' +#' item <- cityscapes_multi[1] +#' # Semantic mask for specific class (e.g., cars = class 13) +#' car_mask <- item$y$semantic == 13 +#' overlay <- draw_segmentation_masks(item$x, car_mask$unsqueeze(1)) +#' tensor_image_browse(overlay) +#' +#' # Use with dataloader +#' dl <- torch::dataloader(cityscapes_train, batch_size = 4, shuffle = TRUE) +#' batch <- dl$.iter()$.next() +#' } +#' +#' @family segmentation_dataset +#' @export +cityscapes_dataset <- torch::dataset( + name = "cityscapes", + + # Cityscapes evaluation classes (19 classes used for benchmarks) + classes = c( + "road", "sidewalk", "building", "wall", "fence", "pole", + "traffic light", "traffic sign", "vegetation", "terrain", "sky", + "person", "rider", "car", "truck", "bus", "train", + "motorcycle", "bicycle" + ), + + # Class ID mapping (trainId -> name) + # IDs 0-18 are evaluation classes, 255 is ignore/void + class_ids = c( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18 + ), + + initialize = function( + root = tempdir(), + split = "train", + mode = "fine", + target_type = "instance", + transform = NULL, + target_transform = NULL + ) { + + self$root_path <- root + self$split <- split + self$mode <- mode + self$target_type <- target_type + self$transform <- transform + self$target_transform <- target_transform + + # Validate parameters + if (!split %in% c("train", "val", "test")) { + cli::cli_abort( + "split must be one of 'train', 'val', or 'test', got {.val {split}}" + ) + } + + if (!mode %in% c("fine", "coarse")) { + cli::cli_abort( + "mode must be either 'fine' or 'coarse', got {.val {mode}}" + ) + } + + valid_types <- c("instance", "semantic", "polygon", "color") + invalid_types <- setdiff(target_type, valid_types) + if (length(invalid_types) > 0) { + cli::cli_abort( + "target_type must be one or more of: {.val {valid_types}}", + "Got invalid types: {.val {invalid_types}}" + ) + } + + # Check dataset exists + if (!self$check_exists()) { + cli::cli_abort( + c( + "Cityscapes dataset not found at {.path {self$root_path}}", + "i" = "Download manually from {.url https://www.cityscapes-dataset.com/downloads/}", + "i" = "Required packages: leftImg8bit_trainvaltest.zip, gtFine_trainvaltest.zip", + "i" = "Extract to {.path {root}}" + ) + ) + } + + # Build file lists + self$images <- self$get_image_list() + + if (length(self$images) == 0) { + cli::cli_abort( + "No images found for split {.val {split}} in {.path {self$images_dir}}" + ) + } + + cli::cli_inform( + c( + "v" = "Loaded {.cls {class(self)[[1]]}} dataset", + "i" = "Split: {.val {split}}, Mode: {.val {mode}}", + "i" = "Images: {.val {length(self$images)}}", + "i" = "Target types: {.val {target_type}}" + ) + ) + }, + + get_image_list = function() { + img_dir <- file.path(self$images_dir, self$split) + + if (!dir.exists(img_dir)) { + cli::cli_abort("Image directory not found: {.path {img_dir}}") + } + + # Find all city subdirectories + cities <- list.dirs(img_dir, recursive = FALSE, full.names = FALSE) + + if (length(cities) == 0) { + return(character(0)) + } + + # Collect all images from all cities + images <- character(0) + for (city in cities) { + city_path <- file.path(img_dir, city) + city_imgs <- list.files( + city_path, + pattern = "_leftImg8bit\\.png$", + full.names = TRUE + ) + images <- c(images, city_imgs) + } + + sort(images) + }, + + get_target_path = function(img_path, target_type) { + # Convert image path to target annotation path + base_name <- basename(img_path) + base_name <- sub("_leftImg8bit\\.png$", "", base_name) + city <- basename(dirname(img_path)) + + target_dir <- file.path( + self$targets_dir, + self$split, + city + ) + + # Different suffixes for different annotation types + suffix <- switch( + target_type, + "instance" = if (self$mode == "fine") "_gtFine_instanceIds.png" else "_gtCoarse_instanceIds.png", + "semantic" = if (self$mode == "fine") "_gtFine_labelIds.png" else "_gtCoarse_labelIds.png", + "color" = if (self$mode == "fine") "_gtFine_color.png" else "_gtCoarse_color.png", + "polygon" = "_gtFine_polygons.json" # Only available for fine mode + ) + + file.path(target_dir, paste0(base_name, suffix)) + }, + + check_exists = function() { + img_exists <- dir.exists(self$images_dir) + target_exists <- dir.exists(self$targets_dir) + img_exists && target_exists + }, + + .getitem = function(index) { + img_path <- self$images[[index]] + + # Load image using magick (supports various formats) + img <- magick::image_read(img_path) + img_array <- magick::image_data(img, channels = "rgb") + + # Convert from (C, H, W) to (H, W, C) format + x <- aperm(as.numeric(img_array), c(2, 3, 1)) + + # Load targets based on target_type + y <- list() + + for (ttype in self$target_type) { + target_path <- self$get_target_path(img_path, ttype) + + if (!file.exists(target_path)) { + cli::cli_warn( + "Target file not found: {.path {basename(target_path)}}", + "Returning NULL for {.val {ttype}}" + ) + y[[ttype]] <- NULL + next + } + + if (ttype == "polygon") { + # Load JSON polygon data + y[[ttype]] <- jsonlite::fromJSON(target_path, simplifyVector = FALSE) + } else if (ttype == "color") { + # Load color visualization + color_img <- magick::image_read(target_path) + color_array <- magick::image_data(color_img, channels = "rgb") + y[[ttype]] <- aperm(as.numeric(color_array), c(2, 3, 1)) + } else { + # Load segmentation masks (instance or semantic) + # These are 16-bit PNG files + mask_img <- magick::image_read(target_path) + mask_array <- magick::image_data(mask_img, channels = "gray") + + # Convert from 8-bit representation to actual IDs + # Cityscapes stores IDs in 16-bit format + mask_matrix <- as.integer(mask_array[1, , ]) + + y[[ttype]] <- mask_matrix + } + } + + # Apply transforms + if (!is.null(self$transform)) { + x <- self$transform(x) + } + + if (!is.null(self$target_transform)) { + y <- self$target_transform(y) + } + + result <- list(x = x, y = y) + class(result) <- c("image_with_segmentation_mask", class(result)) + result + }, + + .length = function() { + length(self$images) + }, + + active = list( + images_dir = function() { + file.path(self$root_path, "leftImg8bit") + }, + + targets_dir = function() { + if (self$mode == "fine") { + file.path(self$root_path, "gtFine") + } else { + file.path(self$root_path, "gtCoarse") + } + } + ) +) diff --git a/man/cityscapes_dataset.Rd b/man/cityscapes_dataset.Rd new file mode 100644 index 00000000..386503cc --- /dev/null +++ b/man/cityscapes_dataset.Rd @@ -0,0 +1,168 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/dataset-cityscapes.R +\name{cityscapes_dataset} +\alias{cityscapes_dataset} +\title{Cityscapes Dataset for Instance and Semantic Segmentation} +\usage{ +cityscapes_dataset( + root = tempdir(), + split = "train", + mode = "fine", + target_type = "instance", + transform = NULL, + target_transform = NULL +) +} +\arguments{ +\item{root}{Character. Root directory where dataset is stored (default: \code{tempdir()}).} + +\item{split}{Character. One of "train", "val", or "test" (default: "train").} + +\item{mode}{Character. Annotation quality: "fine" for high-quality dense annotations +or "coarse" for weaker annotations (default: "fine").} + +\item{target_type}{Character vector. Types of annotations to load. Can include: +\itemize{ +\item "instance": Instance segmentation masks (each object has unique ID) +\item "semantic": Semantic segmentation masks (class IDs only) +\item "polygon": Polygon annotations in JSON format +\item "color": Color-coded visualization of annotations +} +Multiple types can be specified (default: "instance").} + +\item{transform}{Function to transform the input image (default: NULL).} + +\item{target_transform}{Function to transform the target annotations (default: NULL).} +} +\value{ +A torch dataset object. Each item is a named list: +\itemize{ +\item \code{x}: RGB image array of shape (H, W, 3) or transformed tensor +\item \code{y}: Named list containing requested target types: +\itemize{ +\item \code{instance}: Instance segmentation mask (H, W) with unique IDs per object +\item \code{semantic}: Semantic segmentation mask (H, W) with class IDs +\item \code{color}: Color-coded visualization (H, W, 3) +\item \code{polygon}: Polygon annotations (nested list structure) +} +} +} +\description{ +The Cityscapes dataset contains street scene images from 50 European cities +with pixel-level annotations for semantic and instance segmentation tasks. +This dataset is widely used for urban scene understanding research. +} +\section{Dataset Structure}{ + +The Cityscapes dataset includes: +\itemize{ +\item 5,000 finely annotated images (2,975 train, 500 val, 1,525 test) +\item 20,000 coarsely annotated images +\item 19 semantic classes for urban scenes +\item Dense pixel-level annotations at 1024x2048 resolution +\item Images from 50 different European cities +} +} + +\section{Semantic Classes}{ + +The dataset includes 19 evaluation classes: +road, sidewalk, building, wall, fence, pole, traffic light, traffic sign, +vegetation, terrain, sky, person, rider, car, truck, bus, train, motorcycle, +bicycle. +} + +\section{Download}{ + +Cityscapes requires manual download and registration: +\enumerate{ +\item Register at \url{https://www.cityscapes-dataset.com/} +\item Download packages: +\itemize{ +\item leftImg8bit_trainvaltest.zip (11GB) - RGB images +\item gtFine_trainvaltest.zip (241MB) - Fine annotations +\item gtCoarse.zip (1.3GB) - Coarse annotations (optional) +} +\item Extract to root directory +} + +Expected directory structure: +\preformatted{ +root/ +├── leftImg8bit/ +│ ├── train/ +│ ├── val/ +│ └── test/ +├── gtFine/ +│ ├── train/ +│ ├── val/ +│ └── test/ +└── gtCoarse/ + ├── train/ + ├── train_extra/ + └── val/ +} +} + +\section{Integration with draw_segmentation_masks}{ + +The output is directly compatible with \code{\link{draw_segmentation_masks}}: +\preformatted{ +item <- dataset[1] +# For instance segmentation +overlay <- draw_segmentation_masks(item$x, item$y$instance > 0) +# For semantic segmentation +overlay <- draw_segmentation_masks(item$x, item$y$semantic == class_id) +} +} + +\examples{ +\dontrun{ +# Load Cityscapes with instance segmentation +cityscapes_train <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "train", + mode = "fine", + target_type = "instance", + transform = transform_to_tensor +) + +# Get first item +first <- cityscapes_train[1] +first$x # Image tensor (3, H, W) +first$y$instance # Instance mask (H, W) + +# Visualize with draw_segmentation_masks +# Create boolean mask for all instances +mask <- first$y$instance > 0 +overlay <- draw_segmentation_masks(first$x, mask$unsqueeze(1), alpha = 0.5) +tensor_image_browse(overlay) + +# Load with multiple target types +cityscapes_multi <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = c("instance", "semantic"), + transform = transform_to_tensor +) + +item <- cityscapes_multi[1] +# Semantic mask for specific class (e.g., cars = class 13) +car_mask <- item$y$semantic == 13 +overlay <- draw_segmentation_masks(item$x, car_mask$unsqueeze(1)) +tensor_image_browse(overlay) + +# Use with dataloader +dl <- torch::dataloader(cityscapes_train, batch_size = 4, shuffle = TRUE) +batch <- dl$.iter()$.next() +} + +} +\seealso{ +Other segmentation_dataset: +\code{\link{oxfordiiitpet_segmentation_dataset}()}, +\code{\link{pascal_voc_datasets}}, +\code{\link{rf100_peixos_segmentation_dataset}()} +} +\concept{segmentation_dataset} diff --git a/tests/testthat/test-dataset-cityscapes.R b/tests/testthat/test-dataset-cityscapes.R new file mode 100644 index 00000000..00320acb --- /dev/null +++ b/tests/testthat/test-dataset-cityscapes.R @@ -0,0 +1,362 @@ +test_that("cityscapes_dataset validates split parameter", { + skip_on_cran() + + expect_error( + cityscapes_dataset( + root = tempdir(), + split = "invalid" + ), + "split must be one of" + ) + + expect_error( + cityscapes_dataset( + root = tempdir(), + split = "training" + ), + "split must be one of" + ) +}) + +test_that("cityscapes_dataset validates mode parameter", { + skip_on_cran() + + expect_error( + cityscapes_dataset( + root = tempdir(), + mode = "invalid" + ), + "mode must be either" + ) + + expect_error( + cityscapes_dataset( + root = tempdir(), + mode = "medium" + ), + "mode must be either" + ) +}) + +test_that("cityscapes_dataset validates target_type parameter", { + skip_on_cran() + + expect_error( + cityscapes_dataset( + root = tempdir(), + target_type = "invalid" + ), + "target_type must be one or more of" + ) + + expect_error( + cityscapes_dataset( + root = tempdir(), + target_type = c("instance", "invalid", "semantic") + ), + "Got invalid types" + ) +}) + +test_that("cityscapes_dataset requires dataset to exist", { + skip_on_cran() + + temp_root <- file.path(tempdir(), "nonexistent_cityscapes") + + expect_error( + cityscapes_dataset( + root = temp_root, + split = "train" + ), + "Cityscapes dataset not found" + ) +}) + +test_that("cityscapes_dataset structure is correct with fine mode", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance" + ) + + expect_s3_class(ds, "cityscapes") + expect_s3_class(ds, "dataset") + expect_true(ds$.length() > 0) + expect_equal(length(ds$classes), 19) +}) + +test_that("cityscapes_dataset loads images correctly", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance", + transform = transform_to_tensor + ) + + item <- ds[1] + + expect_type(item, "list") + expect_true("x" %in% names(item)) + expect_true("y" %in% names(item)) + expect_true("instance" %in% names(item$y)) + + # Check image tensor shape (should be 3, H, W after transform_to_tensor) + expect_s3_class(item$x, "torch_tensor") + expect_equal(item$x$dim(), 3) + expect_equal(as.integer(item$x$shape[1]), 3) + + # Check mask shape (should be H, W) + expect_true(is.matrix(item$y$instance) || is.array(item$y$instance)) +}) + +test_that("cityscapes_dataset supports semantic target type", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "semantic", + transform = transform_to_tensor + ) + + item <- ds[1] + expect_true("semantic" %in% names(item$y)) + expect_true(is.matrix(item$y$semantic) || is.array(item$y$semantic)) +}) + +test_that("cityscapes_dataset supports multiple target types", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = c("instance", "semantic"), + transform = transform_to_tensor + ) + + item <- ds[1] + expect_true("instance" %in% names(item$y)) + expect_true("semantic" %in% names(item$y)) + expect_type(item$y, "list") + expect_equal(length(item$y), 2) +}) + +test_that("cityscapes_dataset supports color target type", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "color" + ) + + item <- ds[1] + expect_true("color" %in% names(item$y)) + + # Color should be (H, W, 3) + if (!is.null(item$y$color)) { + expect_equal(length(dim(item$y$color)), 3) + expect_equal(dim(item$y$color)[3], 3) + } +}) + +test_that("cityscapes_dataset supports polygon target type", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "polygon" + ) + + item <- ds[1] + expect_true("polygon" %in% names(item$y)) + + # Polygon data should be a list structure + if (!is.null(item$y$polygon)) { + expect_type(item$y$polygon, "list") + } +}) + +test_that("cityscapes_dataset works with coarse mode", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes/gtCoarse"), "Cityscapes coarse annotations not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "train", + mode = "coarse", + target_type = "instance", + transform = transform_to_tensor + ) + + expect_s3_class(ds, "cityscapes") + expect_true(ds$.length() > 0) + + item <- ds[1] + expect_true("instance" %in% names(item$y)) +}) + +test_that("cityscapes_dataset works with train split", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "train", + mode = "fine", + target_type = "instance" + ) + + expect_s3_class(ds, "cityscapes") + # Train split should have ~2975 images + expect_true(ds$.length() > 2000) +}) + +test_that("cityscapes_dataset works with val split", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance" + ) + + expect_s3_class(ds, "cityscapes") + # Val split should have 500 images + expect_true(ds$.length() > 400 && ds$.length() < 600) +}) + +test_that("cityscapes_dataset works with test split", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "test", + mode = "fine", + target_type = "instance" + ) + + expect_s3_class(ds, "cityscapes") + # Test split should have ~1525 images + expect_true(ds$.length() > 1400) +}) + +test_that("cityscapes_dataset output is compatible with draw_segmentation_masks", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance", + transform = transform_to_tensor + ) + + item <- ds[1] + + # Create boolean mask from instance IDs + mask_tensor <- torch::torch_tensor(item$y$instance > 0)$unsqueeze(1) + + # This should not error + expect_no_error({ + overlay <- draw_segmentation_masks( + item$x, + mask_tensor, + alpha = 0.5 + ) + }) +}) + +test_that("cityscapes_dataset works with dataloader", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance", + transform = transform_to_tensor + ) + + dl <- torch::dataloader(ds, batch_size = 2, shuffle = FALSE) + + expect_s3_class(dl, "dataloader") + + # Get first batch + batch <- dl$.iter()$.next() + + expect_type(batch, "list") + expect_true("x" %in% names(batch)) + expect_true("y" %in% names(batch)) +}) + +test_that("cityscapes_dataset has correct class names", { + skip_on_cran() + + # Check class names are defined + ds_classes <- c( + "road", "sidewalk", "building", "wall", "fence", "pole", + "traffic light", "traffic sign", "vegetation", "terrain", "sky", + "person", "rider", "car", "truck", "bus", "train", + "motorcycle", "bicycle" + ) + + expect_equal(length(ds_classes), 19) + + # These are the standard Cityscapes evaluation classes + expect_true("person" %in% ds_classes) + expect_true("car" %in% ds_classes) + expect_true("road" %in% ds_classes) +}) + +test_that("cityscapes_dataset returns image_with_segmentation_mask class", { + skip_on_cran() + skip_if_not(dir.exists("~/datasets/cityscapes"), "Cityscapes dataset not available") + skip_if_not(torch::torch_is_installed()) + + ds <- cityscapes_dataset( + root = "~/datasets/cityscapes", + split = "val", + mode = "fine", + target_type = "instance" + ) + + item <- ds[1] + + expect_s3_class(item, "image_with_segmentation_mask") + expect_s3_class(item, "list") +}) From b1684d8d4d7fc29b4342a2ff3b389dba4a900cab Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 7 Jan 2026 12:26:38 +0530 Subject: [PATCH 2/2] feat: add Cityscapes dataset for instance segmentation - Support fine and coarse annotation modes - Support instance, semantic, polygon, and color target types - Compatible with draw_segmentation_masks function - 19 semantic classes for urban scene understanding - Comprehensive test coverage with 18 test cases - Complete documentation with usage examples - Fixed test validation for error messages Closes #138 --- R/dataset-cityscapes.R | 3 +-- man/oxfordiiitpet_segmentation_dataset.Rd | 1 + man/pascal_voc_datasets.Rd | 1 + man/rf100_peixos_segmentation_dataset.Rd | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/R/dataset-cityscapes.R b/R/dataset-cityscapes.R index 196e0091..75129f5b 100644 --- a/R/dataset-cityscapes.R +++ b/R/dataset-cityscapes.R @@ -182,8 +182,7 @@ cityscapes_dataset <- torch::dataset( invalid_types <- setdiff(target_type, valid_types) if (length(invalid_types) > 0) { cli::cli_abort( - "target_type must be one or more of: {.val {valid_types}}", - "Got invalid types: {.val {invalid_types}}" + "target_type must be one or more of: {.val {valid_types}}. Got invalid types: {.val {invalid_types}}" ) } diff --git a/man/oxfordiiitpet_segmentation_dataset.Rd b/man/oxfordiiitpet_segmentation_dataset.Rd index c1a059ad..e6f2d5d4 100644 --- a/man/oxfordiiitpet_segmentation_dataset.Rd +++ b/man/oxfordiiitpet_segmentation_dataset.Rd @@ -67,6 +67,7 @@ tensor_image_browse(overlay) } \seealso{ Other segmentation_dataset: +\code{\link{cityscapes_dataset}()}, \code{\link{pascal_voc_datasets}}, \code{\link{rf100_peixos_segmentation_dataset}()} } diff --git a/man/pascal_voc_datasets.Rd b/man/pascal_voc_datasets.Rd index 24c198e4..a153f68d 100644 --- a/man/pascal_voc_datasets.Rd +++ b/man/pascal_voc_datasets.Rd @@ -128,6 +128,7 @@ tensor_image_browse(boxed_img) } \seealso{ Other segmentation_dataset: +\code{\link{cityscapes_dataset}()}, \code{\link{oxfordiiitpet_segmentation_dataset}()}, \code{\link{rf100_peixos_segmentation_dataset}()} diff --git a/man/rf100_peixos_segmentation_dataset.Rd b/man/rf100_peixos_segmentation_dataset.Rd index 9fd991b1..a7bdbfc9 100644 --- a/man/rf100_peixos_segmentation_dataset.Rd +++ b/man/rf100_peixos_segmentation_dataset.Rd @@ -59,6 +59,7 @@ tensor_image_browse(overlay) } \seealso{ Other segmentation_dataset: +\code{\link{cityscapes_dataset}()}, \code{\link{oxfordiiitpet_segmentation_dataset}()}, \code{\link{pascal_voc_datasets}} }