torch_em.data.datasets.light_microscopy.stomataquant
The StomataQuant dataset contains annotations for stomata, stomatal pore and pavement cell segmentation in bright-field microscopy images of leaf epidermis.
Two segmentation tasks are available, selected with the 'task' argument:
- 'pores': stomata (class 0) and stomatal pores (class 1), from 500 development images.
- 'pavement_cells': stomata (class 0) and pavement cells (class 1), from 613 development images.
The development images are split into 'train' and 'val'. The independent test images of the publication (45 per task, from other species and imaging conditions) are available as the 'test' split. The annotations are distributed as YOLO polygons (class id and normalized polygon coordinates), which are rasterized to label images by this module. The class ids are not documented by the authors. The order above was inferred from the data: pores are small polygons lying inside stomata polygons, and pavement cells are the more numerous class.
Label images can be one of two types, selected with the 'label_type' argument:
- 'instances': every polygon gets its own id. Where polygons overlap, stomata are drawn over pavement cells and
pores are drawn over stomata (see
INSTANCE_DRAW_ORDER). - 'semantic': the background is 0 and every polygon is labeled with its class id plus one, using the same drawing order.
The images differ in size and some are stored as grayscale, palette or RGBA images. These are converted to RGB. The large detection data (Supplementary Dataset S1-1) contains bounding boxes only and is not used.
The data is located at https://doi.org/10.5281/zenodo.18934358 and released under a CC-BY-4.0 license. This dataset is from the publication https://doi.org/10.1093/jpe/rtag063. Please cite it if you use this dataset in your research.
1"""The StomataQuant dataset contains annotations for stomata, stomatal pore and pavement cell segmentation 2in bright-field microscopy images of leaf epidermis. 3 4Two segmentation tasks are available, selected with the 'task' argument: 5- 'pores': stomata (class 0) and stomatal pores (class 1), from 500 development images. 6- 'pavement_cells': stomata (class 0) and pavement cells (class 1), from 613 development images. 7 8The development images are split into 'train' and 'val'. The independent test images of the publication 9(45 per task, from other species and imaging conditions) are available as the 'test' split. The annotations are 10distributed as YOLO polygons (class id and normalized polygon coordinates), which are rasterized to label images 11by this module. The class ids are not documented by the authors. The order above was inferred from the data: 12pores are small polygons lying inside stomata polygons, and pavement cells are the more numerous class. 13 14Label images can be one of two types, selected with the 'label_type' argument: 15- 'instances': every polygon gets its own id. Where polygons overlap, stomata are drawn over pavement cells and 16 pores are drawn over stomata (see `INSTANCE_DRAW_ORDER`). 17- 'semantic': the background is 0 and every polygon is labeled with its class id plus one, using the same 18 drawing order. 19 20The images differ in size and some are stored as grayscale, palette or RGBA images. These are converted to RGB. 21The large detection data (Supplementary Dataset S1-1) contains bounding boxes only and is not used. 22 23The data is located at https://doi.org/10.5281/zenodo.18934358 and released under a CC-BY-4.0 license. 24This dataset is from the publication https://doi.org/10.1093/jpe/rtag063. 25Please cite it if you use this dataset in your research. 26""" 27 28import os 29import uuid 30from glob import glob 31from tqdm import tqdm 32from natsort import natsorted 33from concurrent import futures 34from typing import Union, Tuple, Optional, Literal, List 35 36from torch.utils.data import Dataset, DataLoader 37 38import torch_em 39 40from .. import util 41 42 43BASE_URL = "https://zenodo.org/api/records/18934358/files" 44 45FILES = { 46 "pores": "Supplementary Dataset S1-2_Stomata_and_pores_segmentation_model.zip", 47 "pavement_cells": "Supplementary Dataset S1-3_Stomata_and_pavement_cells_segmentation_model.zip", 48 "test": "Supplementary Dataset S2.zip", 49} 50 51CHECKSUMS = { 52 "pores": "d6c06377cf21e5f9c42fc2f65df6cd1f5c18b9c36e4fd3edca5eef4fac618eef", 53 "pavement_cells": "8db4ccb60ba4f8153a7f915b6691fa9841eb70ed9462f2cf5e0387b39d02eb7f", 54 "test": "4378b7a6ffd125e7ef036b7ca9baa8aa03ccd771c6009407e9450b6eeb0c705c", 55} 56 57TASKS = ("pores", "pavement_cells") 58SPLITS = ("train", "val", "test") 59LABEL_TYPES = ("instances", "semantic") 60 61TEST_FOLDERS = { 62 "pores": "Test_stomata_and_pores_segmentation_model", 63 "pavement_cells": "Test_stomata_and_pavement_cells_segmentation_model", 64} 65 66INSTANCE_DRAW_ORDER = {"pores": (0, 1), "pavement_cells": (1, 0)} 67"""The order in which the classes are drawn for each task, later classes overwrite earlier ones.""" 68 69 70def _read_polygons(txt_path): 71 import numpy as np 72 73 polygons = {} 74 with open(txt_path) as f: 75 for line in f: 76 tokens = line.split() 77 if len(tokens) < 7 or (len(tokens) - 1) % 2 != 0: 78 continue 79 polygons.setdefault(int(tokens[0]), []).append(np.array(tokens[1:], dtype="float64").reshape(-1, 2)) 80 return polygons 81 82 83def _write_atomic(path, array): 84 import imageio.v3 as imageio 85 86 extension = os.path.splitext(path)[1] 87 tmp_path = f"{os.path.splitext(path)[0]}.{uuid.uuid4().hex}.incomplete{extension}" 88 imageio.imwrite(tmp_path, array) 89 os.replace(tmp_path, path) 90 91 92def _process_item(image_path, txt_path, rgb_path, label_path, task, label_type): 93 import numpy as np 94 from PIL import Image, ImageDraw 95 96 with Image.open(image_path) as image: 97 width, height = image.size 98 needs_conversion = image.mode != "RGB" 99 100 if needs_conversion and not os.path.exists(rgb_path): 101 with Image.open(image_path) as image: 102 _write_atomic(rgb_path, np.asarray(image.convert("RGB"))) 103 104 if os.path.exists(label_path): 105 return 106 107 polygons = _read_polygons(txt_path) 108 # skimage.draw.polygon needs about 0.5 s per polygon on these large images, the PIL scanline fill takes ms. 109 canvas = Image.new("I", (width, height), 0) 110 draw = ImageDraw.Draw(canvas) 111 next_id = 1 112 for class_id in INSTANCE_DRAW_ORDER[task]: 113 for points in polygons.get(class_id, []): 114 vertices = [(x * width, y * height) for x, y in points] 115 draw.polygon(vertices, fill=next_id if label_type == "instances" else class_id + 1) 116 next_id += 1 117 118 _write_atomic(label_path, np.asarray(canvas).astype("uint16")) 119 120 121def _list_items(data_dir, task, split): 122 if split == "test": 123 folder = os.path.join(data_dir, "test", TEST_FOLDERS[task]) 124 image_paths = natsorted(glob(os.path.join(folder, "imgs", "*"))) 125 label_dir = os.path.join(folder, "groundtruth_labels") 126 else: 127 folder = os.path.join(data_dir, task) 128 image_paths = natsorted(glob(os.path.join(folder, "images", split, "*"))) 129 label_dir = os.path.join(folder, "labels", split) 130 131 items = [] 132 for image_path in image_paths: 133 stem = os.path.splitext(os.path.basename(image_path))[0] 134 txt_path = os.path.join(label_dir, f"{stem}.txt") 135 if os.path.exists(txt_path): 136 items.append((image_path, txt_path, stem)) 137 138 return items 139 140 141def get_stomataquant_data( 142 path: Union[os.PathLike, str], task: Literal["pores", "pavement_cells"], split: Literal["train", "val", "test"], 143 download: bool = False, 144) -> str: 145 """Download the StomataQuant dataset. 146 147 Args: 148 path: Filepath to a folder where the downloaded data will be saved. 149 task: The segmentation task. Either 'pores' or 'pavement_cells'. 150 split: The data split. One of 'train', 'val' or 'test'. 151 download: Whether to download the data if it is not present. 152 153 Returns: 154 Filepath where the data is downloaded. 155 """ 156 if task not in TASKS: 157 raise ValueError(f"'{task}' is not a valid task. Choose one of {TASKS}.") 158 if split not in SPLITS: 159 raise ValueError(f"'{split}' is not a valid split. Choose one of {SPLITS}.") 160 161 archive = "test" if split == "test" else task 162 dst = os.path.join(path, archive) 163 if os.path.exists(dst) and os.listdir(dst): 164 return path 165 166 os.makedirs(path, exist_ok=True) 167 zip_path = os.path.join(path, f"{archive}.zip") 168 url = f"{BASE_URL}/{FILES[archive].replace(' ', '%20')}/content" 169 util.download_source(path=zip_path, url=url, download=download, checksum=CHECKSUMS[archive]) 170 util.unzip(zip_path=zip_path, dst=dst, remove=False) 171 172 return path 173 174 175def get_stomataquant_paths( 176 path: Union[os.PathLike, str], 177 task: Literal["pores", "pavement_cells"], 178 split: Literal["train", "val", "test"], 179 label_type: Literal["instances", "semantic"] = "instances", 180 download: bool = False, 181) -> Tuple[List[str], List[str]]: 182 """Get paths to the StomataQuant data. 183 184 Args: 185 path: Filepath to a folder where the downloaded data will be saved. 186 task: The segmentation task. Either 'pores' or 'pavement_cells'. 187 split: The data split. One of 'train', 'val' or 'test'. 188 label_type: The type of label image. Either 'instances' or 'semantic'. 189 download: Whether to download the data if it is not present. 190 191 Returns: 192 List of filepaths for the image data. 193 List of filepaths for the label data. 194 """ 195 if label_type not in LABEL_TYPES: 196 raise ValueError(f"'{label_type}' is not a valid label type. Choose one of {LABEL_TYPES}.") 197 198 data_dir = get_stomataquant_data(path, task, split, download) 199 items = _list_items(data_dir, task, split) 200 assert len(items) > 0, f"No images with annotations were found for task '{task}' and split '{split}'." 201 202 rgb_dir = os.path.join(path, "images_rgb", task, split) 203 label_dir = os.path.join(path, "labels", task, label_type, split) 204 os.makedirs(rgb_dir, exist_ok=True) 205 os.makedirs(label_dir, exist_ok=True) 206 207 jobs = [ 208 (image_path, txt_path, os.path.join(rgb_dir, f"{stem}.png"), os.path.join(label_dir, f"{stem}.tif")) 209 for image_path, txt_path, stem in items 210 ] 211 todo = [job for job in jobs if not os.path.exists(job[3])] 212 if todo: 213 with futures.ThreadPoolExecutor(min(8, os.cpu_count() or 1)) as pool: 214 tasks = [pool.submit(_process_item, *job, task, label_type) for job in todo] 215 for job in tqdm(futures.as_completed(tasks), total=len(tasks), desc="Preprocess StomataQuant"): 216 job.result() 217 218 image_paths = [job[2] if os.path.exists(job[2]) else job[0] for job in jobs] 219 label_paths = [job[3] for job in jobs] 220 return image_paths, label_paths 221 222 223def get_stomataquant_dataset( 224 path: Union[os.PathLike, str], 225 patch_shape: Tuple[int, int], 226 task: Literal["pores", "pavement_cells"], 227 split: Literal["train", "val", "test"], 228 label_type: Literal["instances", "semantic"] = "instances", 229 offsets: Optional[List[List[int]]] = None, 230 boundaries: bool = False, 231 binary: bool = False, 232 resize_inputs: bool = False, 233 download: bool = False, 234 **kwargs 235) -> Dataset: 236 """Get the StomataQuant dataset for stomata, pore and pavement cell segmentation. 237 238 Args: 239 path: Filepath to a folder where the downloaded data will be saved. 240 patch_shape: The patch shape to use for training. 241 task: The segmentation task. Either 'pores' or 'pavement_cells'. 242 split: The data split. One of 'train', 'val' or 'test'. 243 label_type: The type of label image. Either 'instances' or 'semantic'. 244 offsets: Offset values for affinity computation used as target. 245 boundaries: Whether to compute boundaries as the target. 246 binary: Whether to use a binary segmentation target. 247 resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size. 248 download: Whether to download the data if it is not present. 249 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 250 251 Returns: 252 The segmentation dataset. 253 """ 254 image_paths, label_paths = get_stomataquant_paths(path, task, split, label_type, download) 255 256 if resize_inputs: 257 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 258 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 259 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 260 ) 261 262 if label_type == "instances": 263 kwargs, _ = util.add_instance_label_transform( 264 kwargs, add_binary_target=True, offsets=offsets, boundaries=boundaries, binary=binary 265 ) 266 267 return torch_em.default_segmentation_dataset( 268 raw_paths=image_paths, 269 raw_key=None, 270 label_paths=label_paths, 271 label_key=None, 272 patch_shape=patch_shape, 273 is_seg_dataset=False, 274 **kwargs 275 ) 276 277 278def get_stomataquant_loader( 279 path: Union[os.PathLike, str], 280 batch_size: int, 281 patch_shape: Tuple[int, int], 282 task: Literal["pores", "pavement_cells"], 283 split: Literal["train", "val", "test"], 284 label_type: Literal["instances", "semantic"] = "instances", 285 offsets: Optional[List[List[int]]] = None, 286 boundaries: bool = False, 287 binary: bool = False, 288 resize_inputs: bool = False, 289 download: bool = False, 290 **kwargs 291) -> DataLoader: 292 """Get the StomataQuant dataloader for stomata, pore and pavement cell segmentation. 293 294 Args: 295 path: Filepath to a folder where the downloaded data will be saved. 296 batch_size: The batch size for training. 297 patch_shape: The patch shape to use for training. 298 task: The segmentation task. Either 'pores' or 'pavement_cells'. 299 split: The data split. One of 'train', 'val' or 'test'. 300 label_type: The type of label image. Either 'instances' or 'semantic'. 301 offsets: Offset values for affinity computation used as target. 302 boundaries: Whether to compute boundaries as the target. 303 binary: Whether to use a binary segmentation target. 304 resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size. 305 download: Whether to download the data if it is not present. 306 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 307 308 Returns: 309 The DataLoader. 310 """ 311 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 312 dataset = get_stomataquant_dataset( 313 path, patch_shape, task, split, label_type, offsets, boundaries, binary, resize_inputs, download, **ds_kwargs 314 ) 315 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
The order in which the classes are drawn for each task, later classes overwrite earlier ones.
142def get_stomataquant_data( 143 path: Union[os.PathLike, str], task: Literal["pores", "pavement_cells"], split: Literal["train", "val", "test"], 144 download: bool = False, 145) -> str: 146 """Download the StomataQuant dataset. 147 148 Args: 149 path: Filepath to a folder where the downloaded data will be saved. 150 task: The segmentation task. Either 'pores' or 'pavement_cells'. 151 split: The data split. One of 'train', 'val' or 'test'. 152 download: Whether to download the data if it is not present. 153 154 Returns: 155 Filepath where the data is downloaded. 156 """ 157 if task not in TASKS: 158 raise ValueError(f"'{task}' is not a valid task. Choose one of {TASKS}.") 159 if split not in SPLITS: 160 raise ValueError(f"'{split}' is not a valid split. Choose one of {SPLITS}.") 161 162 archive = "test" if split == "test" else task 163 dst = os.path.join(path, archive) 164 if os.path.exists(dst) and os.listdir(dst): 165 return path 166 167 os.makedirs(path, exist_ok=True) 168 zip_path = os.path.join(path, f"{archive}.zip") 169 url = f"{BASE_URL}/{FILES[archive].replace(' ', '%20')}/content" 170 util.download_source(path=zip_path, url=url, download=download, checksum=CHECKSUMS[archive]) 171 util.unzip(zip_path=zip_path, dst=dst, remove=False) 172 173 return path
Download the StomataQuant dataset.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- task: The segmentation task. Either 'pores' or 'pavement_cells'.
- split: The data split. One of 'train', 'val' or 'test'.
- download: Whether to download the data if it is not present.
Returns:
Filepath where the data is downloaded.
176def get_stomataquant_paths( 177 path: Union[os.PathLike, str], 178 task: Literal["pores", "pavement_cells"], 179 split: Literal["train", "val", "test"], 180 label_type: Literal["instances", "semantic"] = "instances", 181 download: bool = False, 182) -> Tuple[List[str], List[str]]: 183 """Get paths to the StomataQuant data. 184 185 Args: 186 path: Filepath to a folder where the downloaded data will be saved. 187 task: The segmentation task. Either 'pores' or 'pavement_cells'. 188 split: The data split. One of 'train', 'val' or 'test'. 189 label_type: The type of label image. Either 'instances' or 'semantic'. 190 download: Whether to download the data if it is not present. 191 192 Returns: 193 List of filepaths for the image data. 194 List of filepaths for the label data. 195 """ 196 if label_type not in LABEL_TYPES: 197 raise ValueError(f"'{label_type}' is not a valid label type. Choose one of {LABEL_TYPES}.") 198 199 data_dir = get_stomataquant_data(path, task, split, download) 200 items = _list_items(data_dir, task, split) 201 assert len(items) > 0, f"No images with annotations were found for task '{task}' and split '{split}'." 202 203 rgb_dir = os.path.join(path, "images_rgb", task, split) 204 label_dir = os.path.join(path, "labels", task, label_type, split) 205 os.makedirs(rgb_dir, exist_ok=True) 206 os.makedirs(label_dir, exist_ok=True) 207 208 jobs = [ 209 (image_path, txt_path, os.path.join(rgb_dir, f"{stem}.png"), os.path.join(label_dir, f"{stem}.tif")) 210 for image_path, txt_path, stem in items 211 ] 212 todo = [job for job in jobs if not os.path.exists(job[3])] 213 if todo: 214 with futures.ThreadPoolExecutor(min(8, os.cpu_count() or 1)) as pool: 215 tasks = [pool.submit(_process_item, *job, task, label_type) for job in todo] 216 for job in tqdm(futures.as_completed(tasks), total=len(tasks), desc="Preprocess StomataQuant"): 217 job.result() 218 219 image_paths = [job[2] if os.path.exists(job[2]) else job[0] for job in jobs] 220 label_paths = [job[3] for job in jobs] 221 return image_paths, label_paths
Get paths to the StomataQuant data.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- task: The segmentation task. Either 'pores' or 'pavement_cells'.
- split: The data split. One of 'train', 'val' or 'test'.
- label_type: The type of label image. Either 'instances' or 'semantic'.
- download: Whether to download the data if it is not present.
Returns:
List of filepaths for the image data. List of filepaths for the label data.
224def get_stomataquant_dataset( 225 path: Union[os.PathLike, str], 226 patch_shape: Tuple[int, int], 227 task: Literal["pores", "pavement_cells"], 228 split: Literal["train", "val", "test"], 229 label_type: Literal["instances", "semantic"] = "instances", 230 offsets: Optional[List[List[int]]] = None, 231 boundaries: bool = False, 232 binary: bool = False, 233 resize_inputs: bool = False, 234 download: bool = False, 235 **kwargs 236) -> Dataset: 237 """Get the StomataQuant dataset for stomata, pore and pavement cell segmentation. 238 239 Args: 240 path: Filepath to a folder where the downloaded data will be saved. 241 patch_shape: The patch shape to use for training. 242 task: The segmentation task. Either 'pores' or 'pavement_cells'. 243 split: The data split. One of 'train', 'val' or 'test'. 244 label_type: The type of label image. Either 'instances' or 'semantic'. 245 offsets: Offset values for affinity computation used as target. 246 boundaries: Whether to compute boundaries as the target. 247 binary: Whether to use a binary segmentation target. 248 resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size. 249 download: Whether to download the data if it is not present. 250 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 251 252 Returns: 253 The segmentation dataset. 254 """ 255 image_paths, label_paths = get_stomataquant_paths(path, task, split, label_type, download) 256 257 if resize_inputs: 258 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 259 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 260 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 261 ) 262 263 if label_type == "instances": 264 kwargs, _ = util.add_instance_label_transform( 265 kwargs, add_binary_target=True, offsets=offsets, boundaries=boundaries, binary=binary 266 ) 267 268 return torch_em.default_segmentation_dataset( 269 raw_paths=image_paths, 270 raw_key=None, 271 label_paths=label_paths, 272 label_key=None, 273 patch_shape=patch_shape, 274 is_seg_dataset=False, 275 **kwargs 276 )
Get the StomataQuant dataset for stomata, pore and pavement cell segmentation.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- patch_shape: The patch shape to use for training.
- task: The segmentation task. Either 'pores' or 'pavement_cells'.
- split: The data split. One of 'train', 'val' or 'test'.
- label_type: The type of label image. Either 'instances' or 'semantic'.
- offsets: Offset values for affinity computation used as target.
- boundaries: Whether to compute boundaries as the target.
- binary: Whether to use a binary segmentation target.
- resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size.
- download: Whether to download the data if it is not present.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_dataset.
Returns:
The segmentation dataset.
279def get_stomataquant_loader( 280 path: Union[os.PathLike, str], 281 batch_size: int, 282 patch_shape: Tuple[int, int], 283 task: Literal["pores", "pavement_cells"], 284 split: Literal["train", "val", "test"], 285 label_type: Literal["instances", "semantic"] = "instances", 286 offsets: Optional[List[List[int]]] = None, 287 boundaries: bool = False, 288 binary: bool = False, 289 resize_inputs: bool = False, 290 download: bool = False, 291 **kwargs 292) -> DataLoader: 293 """Get the StomataQuant dataloader for stomata, pore and pavement cell segmentation. 294 295 Args: 296 path: Filepath to a folder where the downloaded data will be saved. 297 batch_size: The batch size for training. 298 patch_shape: The patch shape to use for training. 299 task: The segmentation task. Either 'pores' or 'pavement_cells'. 300 split: The data split. One of 'train', 'val' or 'test'. 301 label_type: The type of label image. Either 'instances' or 'semantic'. 302 offsets: Offset values for affinity computation used as target. 303 boundaries: Whether to compute boundaries as the target. 304 binary: Whether to use a binary segmentation target. 305 resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size. 306 download: Whether to download the data if it is not present. 307 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 308 309 Returns: 310 The DataLoader. 311 """ 312 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 313 dataset = get_stomataquant_dataset( 314 path, patch_shape, task, split, label_type, offsets, boundaries, binary, resize_inputs, download, **ds_kwargs 315 ) 316 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the StomataQuant dataloader for stomata, pore and pavement cell segmentation.
Arguments:
- path: Filepath to a folder where the downloaded data will be saved.
- batch_size: The batch size for training.
- patch_shape: The patch shape to use for training.
- task: The segmentation task. Either 'pores' or 'pavement_cells'.
- split: The data split. One of 'train', 'val' or 'test'.
- label_type: The type of label image. Either 'instances' or 'semantic'.
- offsets: Offset values for affinity computation used as target.
- boundaries: Whether to compute boundaries as the target.
- binary: Whether to use a binary segmentation target.
- resize_inputs: Whether to resize the inputs to the patch shape. The images differ in size.
- download: Whether to download the data if it is not present.
- kwargs: Additional keyword arguments for
torch_em.default_segmentation_datasetor for the PyTorch DataLoader.
Returns:
The DataLoader.