torch_em.data.datasets.medical.maternal_fetal_us_video
The Maternal-Fetal Ultrasound Video dataset contains annotations for segmentation of the pubic symphysis and the fetal head in transperineal intrapartum ultrasound videos.
The dataset consists of 774 videos (68,106 frames in total) collected from three medical centers (JNU, SYSU and SMU) with different ultrasound devices. A subset of the frames is manually annotated with pixel-level semantic segmentation masks for two structures: the pubic symphysis (class 1) and the fetal head (class 2). This wrapper extracts the annotated frames from the videos (using the frame index encoded in the annotation filename) and pairs them with the corresponding segmentation mask, so that the data can be used as a regular 2d image segmentation dataset.
The dataset ships three splits with slightly different storage conventions:
- 'train': videos with annotated frames are stored in 'train/pos/
/', with the video itself (' .avi') and the per-frame masks ('mask/ _ _*.png') in the same folder. The masks use pixel values 7 (pubic symphysis) and 8 (fetal head), which are remapped to 1 and 2 respectively to match the 'test' and 'val' splits (see NOTE below). - 'test': videos are stored in 'test/video/Source_/
.avi' and the per-frame masks in 'test/seg_label/ _ .npy' (pixel values 0, 1, 2). - 'val': videos are stored in 'val/videos/
_ .avi' (the frame index is part of the video filename) and the single annotated mask per video in 'val/label_seg/ _seg.npy' (pixel values 0, 1, 2).
NOTE: The 'train' split masks use different pixel values (7, 8) than the 'test' and 'val' splits (1, 2). This wrapper remaps the 'train' masks to the same (1, 2) convention when caching the frames, so that all splits share one label convention: 0 (background), 1 (pubic symphysis), 2 (fetal head).
This dataset is located at https://doi.org/10.5281/zenodo.16869288. This dataset is from the publication https://doi.org/10.1038/s41597-026-06900-5. The dataset is licensed under CC-BY-4.0. Please cite the publication above if you use this dataset for your research.
1"""The Maternal-Fetal Ultrasound Video dataset contains annotations for segmentation of the pubic 2symphysis and the fetal head in transperineal intrapartum ultrasound videos. 3 4The dataset consists of 774 videos (68,106 frames in total) collected from three medical centers 5(JNU, SYSU and SMU) with different ultrasound devices. A subset of the frames is manually annotated 6with pixel-level semantic segmentation masks for two structures: the pubic symphysis (class 1) and 7the fetal head (class 2). This wrapper extracts the annotated frames from the videos (using the frame 8index encoded in the annotation filename) and pairs them with the corresponding segmentation mask, so 9that the data can be used as a regular 2d image segmentation dataset. 10 11The dataset ships three splits with slightly different storage conventions: 12- 'train': videos with annotated frames are stored in 'train/pos/<video_id>/', with the video itself 13 ('<video_id>.avi') and the per-frame masks ('mask/<video_id>_<frame_idx>_*.png') in the same folder. 14 The masks use pixel values 7 (pubic symphysis) and 8 (fetal head), which are remapped to 1 and 2 15 respectively to match the 'test' and 'val' splits (see NOTE below). 16- 'test': videos are stored in 'test/video/Source*_*/<video_id>.avi' and the per-frame masks in 17 'test/seg_label/<video_id>_<frame_idx>.npy' (pixel values 0, 1, 2). 18- 'val': videos are stored in 'val/videos/<video_id>_<frame_idx>.avi' (the frame index is part of the 19 video filename) and the single annotated mask per video in 'val/label_seg/<video_id>_seg.npy' 20 (pixel values 0, 1, 2). 21 22NOTE: The 'train' split masks use different pixel values (7, 8) than the 'test' and 'val' splits 23(1, 2). This wrapper remaps the 'train' masks to the same (1, 2) convention when caching the frames, 24so that all splits share one label convention: 0 (background), 1 (pubic symphysis), 2 (fetal head). 25 26This dataset is located at https://doi.org/10.5281/zenodo.16869288. 27This dataset is from the publication https://doi.org/10.1038/s41597-026-06900-5. 28The dataset is licensed under CC-BY-4.0. 29Please cite the publication above if you use this dataset for your research. 30""" 31 32import os 33import re 34from glob import glob 35from tqdm import tqdm 36from natsort import natsorted 37from typing import Union, Tuple, List, Literal 38 39import numpy as np 40import imageio.v3 as imageio 41 42from torch.utils.data import Dataset, DataLoader 43 44import torch_em 45 46from .. import util 47 48 49URL = "https://zenodo.org/records/16869288/files/IUGC2024.rar" 50CHECKSUM = "048730551419192aeaf7b23bcf6dbdcda26ab2d4acde4f5007b350128d188052" 51 52SPLITS = ["train", "val", "test"] 53 54# The 'train' split masks use 7 / 8 for the two foreground classes, the 'test' / 'val' splits use 1 / 2. 55TRAIN_LABEL_MAP = {0: 0, 7: 1, 8: 2} 56 57 58def get_maternal_fetal_us_video_data(path: Union[os.PathLike, str], download: bool = False) -> str: 59 """Download the Maternal-Fetal Ultrasound Video dataset. 60 61 Args: 62 path: Filepath to a folder where the data is downloaded for further processing. 63 download: Whether to download the data if it is not present. 64 65 Returns: 66 Filepath where the data is downloaded. 67 """ 68 data_dir = os.path.join(path, "IUGC2024") 69 if os.path.exists(data_dir): 70 return data_dir 71 72 os.makedirs(path, exist_ok=True) 73 74 rar_path = os.path.join(path, "IUGC2024.rar") 75 util.download_source(path=rar_path, url=URL, download=download, checksum=CHECKSUM) 76 util.unzip_rarfile(rar_path=rar_path, dst=path) 77 78 return data_dir 79 80 81def _read_frame(video_path, frame_idx): 82 import cv2 83 84 cap = cv2.VideoCapture(video_path) 85 cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx) 86 success, frame = cap.read() 87 cap.release() 88 if not success: 89 raise RuntimeError(f"Could not read frame {frame_idx} from '{video_path}'.") 90 return cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 91 92 93def _cache_train_split(data_dir, cache_dir): 94 video_dirs = natsorted(glob(os.path.join(data_dir, "train", "pos", "*"))) 95 96 image_paths, label_paths = [], [] 97 for video_dir in tqdm(video_dirs, desc="Caching 'train' frames"): 98 video_id = os.path.basename(video_dir) 99 video_path = os.path.join(video_dir, f"{video_id}.avi") 100 mask_paths = natsorted(glob(os.path.join(video_dir, "mask", f"{video_id}_*.png"))) 101 102 for mask_path in mask_paths: 103 fname = os.path.basename(mask_path) 104 rest = fname[len(video_id):-len(".png")].strip("_") 105 frame_idx = int(rest.split("_")[0]) 106 107 image_path = os.path.join(cache_dir, f"train_{video_id}_{frame_idx}_image.tif") 108 label_path = os.path.join(cache_dir, f"train_{video_id}_{frame_idx}_label.tif") 109 image_paths.append(image_path) 110 label_paths.append(label_path) 111 if os.path.exists(image_path) and os.path.exists(label_path): 112 continue 113 114 frame = _read_frame(video_path, frame_idx) 115 mask = imageio.imread(mask_path) 116 label = np.vectorize(TRAIN_LABEL_MAP.get)(mask).astype("uint8") 117 118 imageio.imwrite(image_path, frame, compression="zlib") 119 imageio.imwrite(label_path, label, compression="zlib") 120 121 return image_paths, label_paths 122 123 124def _cache_test_split(data_dir, cache_dir): 125 mask_paths = natsorted(glob(os.path.join(data_dir, "test", "seg_label", "*.npy"))) 126 video_paths = { 127 os.path.splitext(os.path.basename(p))[0]: p 128 for p in glob(os.path.join(data_dir, "test", "video", "Source*_*", "*.avi")) 129 } 130 131 image_paths, label_paths = [], [] 132 for mask_path in tqdm(mask_paths, desc="Caching 'test' frames"): 133 fname = os.path.splitext(os.path.basename(mask_path))[0] 134 match = re.match(r"(.+)_(\d+)$", fname) 135 video_id, frame_idx = match.group(1), int(match.group(2)) 136 137 video_path = video_paths.get(video_id) 138 if video_path is None: 139 raise RuntimeError(f"Could not find the video for '{video_id}' in the 'test' split.") 140 141 image_path = os.path.join(cache_dir, f"test_{video_id}_{frame_idx}_image.tif") 142 label_path = os.path.join(cache_dir, f"test_{video_id}_{frame_idx}_label.tif") 143 image_paths.append(image_path) 144 label_paths.append(label_path) 145 if os.path.exists(image_path) and os.path.exists(label_path): 146 continue 147 148 frame = _read_frame(video_path, frame_idx) 149 label = np.load(mask_path).astype("uint8") 150 151 imageio.imwrite(image_path, frame, compression="zlib") 152 imageio.imwrite(label_path, label, compression="zlib") 153 154 return image_paths, label_paths 155 156 157def _cache_val_split(data_dir, cache_dir): 158 mask_paths = natsorted(glob(os.path.join(data_dir, "val", "label_seg", "*_seg.npy"))) 159 video_paths = { 160 re.match(r"(.+)_(\d+)$", os.path.splitext(os.path.basename(p))[0]).group(1): p 161 for p in glob(os.path.join(data_dir, "val", "videos", "*.avi")) 162 } 163 164 image_paths, label_paths = [], [] 165 for mask_path in tqdm(mask_paths, desc="Caching 'val' frames"): 166 video_id = os.path.basename(mask_path)[:-len("_seg.npy")] 167 168 video_path = video_paths.get(video_id) 169 if video_path is None: 170 raise RuntimeError(f"Could not find the video for '{video_id}' in the 'val' split.") 171 172 frame_idx = int(re.match(r".+_(\d+)$", os.path.splitext(os.path.basename(video_path))[0]).group(1)) 173 174 image_path = os.path.join(cache_dir, f"val_{video_id}_{frame_idx}_image.tif") 175 label_path = os.path.join(cache_dir, f"val_{video_id}_{frame_idx}_label.tif") 176 image_paths.append(image_path) 177 label_paths.append(label_path) 178 if os.path.exists(image_path) and os.path.exists(label_path): 179 continue 180 181 frame = _read_frame(video_path, frame_idx) 182 label = np.load(mask_path).astype("uint8") 183 184 imageio.imwrite(image_path, frame, compression="zlib") 185 imageio.imwrite(label_path, label, compression="zlib") 186 187 return image_paths, label_paths 188 189 190def get_maternal_fetal_us_video_paths( 191 path: Union[os.PathLike, str], split: Literal["train", "val", "test", "all"] = "all", download: bool = False, 192) -> Tuple[List[str], List[str]]: 193 """Get paths to the Maternal-Fetal Ultrasound Video data. 194 195 This extracts the annotated frames from the videos and caches them (together with their 196 segmentation masks) as tif files, so that repeated calls avoid re-decoding the videos. 197 198 Args: 199 path: Filepath to a folder where the data is downloaded for further processing. 200 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 201 download: Whether to download the data if it is not present. 202 203 Returns: 204 List of filepaths for the image data. 205 List of filepaths for the label data. 206 """ 207 if split == "all": 208 splits = SPLITS 209 elif split in SPLITS: 210 splits = [split] 211 else: 212 raise ValueError(f"'{split}' is not a valid split. Choose from {SPLITS + ['all']}.") 213 214 data_dir = get_maternal_fetal_us_video_data(path, download) 215 216 cache_dir = os.path.join(path, "frames") 217 os.makedirs(cache_dir, exist_ok=True) 218 219 cache_funcs = {"train": _cache_train_split, "test": _cache_test_split, "val": _cache_val_split} 220 221 image_paths, label_paths = [], [] 222 for this_split in splits: 223 this_image_paths, this_label_paths = cache_funcs[this_split](data_dir, cache_dir) 224 image_paths.extend(this_image_paths) 225 label_paths.extend(this_label_paths) 226 227 assert len(image_paths) == len(label_paths) and len(image_paths) > 0 228 229 return image_paths, label_paths 230 231 232def get_maternal_fetal_us_video_dataset( 233 path: Union[os.PathLike, str], 234 patch_shape: Tuple[int, int], 235 split: Literal["train", "val", "test", "all"] = "all", 236 resize_inputs: bool = False, 237 download: bool = False, 238 **kwargs 239) -> Dataset: 240 """Get the Maternal-Fetal Ultrasound Video dataset for pubic symphysis and fetal head segmentation. 241 242 Args: 243 path: Filepath to a folder where the data is downloaded for further processing. 244 patch_shape: The patch shape to use for training. 245 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 246 resize_inputs: Whether to resize the inputs to the expected patch shape. 247 download: Whether to download the data if it is not present. 248 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`. 249 250 Returns: 251 The segmentation dataset. 252 """ 253 image_paths, label_paths = get_maternal_fetal_us_video_paths(path, split, download) 254 255 if resize_inputs: 256 resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True} 257 kwargs, patch_shape = util.update_kwargs_for_resize_trafo( 258 kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs 259 ) 260 261 return torch_em.default_segmentation_dataset( 262 raw_paths=image_paths, 263 raw_key=None, 264 label_paths=label_paths, 265 label_key=None, 266 patch_shape=patch_shape, 267 is_seg_dataset=False, 268 **kwargs 269 ) 270 271 272def get_maternal_fetal_us_video_loader( 273 path: Union[os.PathLike, str], 274 batch_size: int, 275 patch_shape: Tuple[int, int], 276 split: Literal["train", "val", "test", "all"] = "all", 277 resize_inputs: bool = False, 278 download: bool = False, 279 **kwargs 280) -> DataLoader: 281 """Get the Maternal-Fetal Ultrasound Video dataloader for pubic symphysis and fetal head segmentation. 282 283 Args: 284 path: Filepath to a folder where the data is downloaded for further processing. 285 batch_size: The batch size for training. 286 patch_shape: The patch shape to use for training. 287 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 288 resize_inputs: Whether to resize the inputs to the expected patch shape. 289 download: Whether to download the data if it is not present. 290 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 291 292 Returns: 293 The DataLoader. 294 """ 295 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 296 dataset = get_maternal_fetal_us_video_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 297 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
59def get_maternal_fetal_us_video_data(path: Union[os.PathLike, str], download: bool = False) -> str: 60 """Download the Maternal-Fetal Ultrasound Video dataset. 61 62 Args: 63 path: Filepath to a folder where the data is downloaded for further processing. 64 download: Whether to download the data if it is not present. 65 66 Returns: 67 Filepath where the data is downloaded. 68 """ 69 data_dir = os.path.join(path, "IUGC2024") 70 if os.path.exists(data_dir): 71 return data_dir 72 73 os.makedirs(path, exist_ok=True) 74 75 rar_path = os.path.join(path, "IUGC2024.rar") 76 util.download_source(path=rar_path, url=URL, download=download, checksum=CHECKSUM) 77 util.unzip_rarfile(rar_path=rar_path, dst=path) 78 79 return data_dir
Download the Maternal-Fetal Ultrasound Video dataset.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- download: Whether to download the data if it is not present.
Returns:
Filepath where the data is downloaded.
191def get_maternal_fetal_us_video_paths( 192 path: Union[os.PathLike, str], split: Literal["train", "val", "test", "all"] = "all", download: bool = False, 193) -> Tuple[List[str], List[str]]: 194 """Get paths to the Maternal-Fetal Ultrasound Video data. 195 196 This extracts the annotated frames from the videos and caches them (together with their 197 segmentation masks) as tif files, so that repeated calls avoid re-decoding the videos. 198 199 Args: 200 path: Filepath to a folder where the data is downloaded for further processing. 201 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 202 download: Whether to download the data if it is not present. 203 204 Returns: 205 List of filepaths for the image data. 206 List of filepaths for the label data. 207 """ 208 if split == "all": 209 splits = SPLITS 210 elif split in SPLITS: 211 splits = [split] 212 else: 213 raise ValueError(f"'{split}' is not a valid split. Choose from {SPLITS + ['all']}.") 214 215 data_dir = get_maternal_fetal_us_video_data(path, download) 216 217 cache_dir = os.path.join(path, "frames") 218 os.makedirs(cache_dir, exist_ok=True) 219 220 cache_funcs = {"train": _cache_train_split, "test": _cache_test_split, "val": _cache_val_split} 221 222 image_paths, label_paths = [], [] 223 for this_split in splits: 224 this_image_paths, this_label_paths = cache_funcs[this_split](data_dir, cache_dir) 225 image_paths.extend(this_image_paths) 226 label_paths.extend(this_label_paths) 227 228 assert len(image_paths) == len(label_paths) and len(image_paths) > 0 229 230 return image_paths, label_paths
Get paths to the Maternal-Fetal Ultrasound Video data.
This extracts the annotated frames from the videos and caches them (together with their segmentation masks) as tif files, so that repeated calls avoid re-decoding the videos.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits).
- 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.
233def get_maternal_fetal_us_video_dataset( 234 path: Union[os.PathLike, str], 235 patch_shape: Tuple[int, int], 236 split: Literal["train", "val", "test", "all"] = "all", 237 resize_inputs: bool = False, 238 download: bool = False, 239 **kwargs 240) -> Dataset: 241 """Get the Maternal-Fetal Ultrasound Video dataset for pubic symphysis and fetal head segmentation. 242 243 Args: 244 path: Filepath to a folder where the data is downloaded for further processing. 245 patch_shape: The patch shape to use for training. 246 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 247 resize_inputs: Whether to resize the inputs to the expected patch shape. 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_maternal_fetal_us_video_paths(path, split, 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 return torch_em.default_segmentation_dataset( 263 raw_paths=image_paths, 264 raw_key=None, 265 label_paths=label_paths, 266 label_key=None, 267 patch_shape=patch_shape, 268 is_seg_dataset=False, 269 **kwargs 270 )
Get the Maternal-Fetal Ultrasound Video dataset for pubic symphysis and fetal head segmentation.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- patch_shape: The patch shape to use for training.
- split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits).
- resize_inputs: Whether to resize the inputs to the expected patch shape.
- 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.
273def get_maternal_fetal_us_video_loader( 274 path: Union[os.PathLike, str], 275 batch_size: int, 276 patch_shape: Tuple[int, int], 277 split: Literal["train", "val", "test", "all"] = "all", 278 resize_inputs: bool = False, 279 download: bool = False, 280 **kwargs 281) -> DataLoader: 282 """Get the Maternal-Fetal Ultrasound Video dataloader for pubic symphysis and fetal head segmentation. 283 284 Args: 285 path: Filepath to a folder where the data is downloaded for further processing. 286 batch_size: The batch size for training. 287 patch_shape: The patch shape to use for training. 288 split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits). 289 resize_inputs: Whether to resize the inputs to the expected patch shape. 290 download: Whether to download the data if it is not present. 291 kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader. 292 293 Returns: 294 The DataLoader. 295 """ 296 ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs) 297 dataset = get_maternal_fetal_us_video_dataset(path, patch_shape, split, resize_inputs, download, **ds_kwargs) 298 return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
Get the Maternal-Fetal Ultrasound Video dataloader for pubic symphysis and fetal head segmentation.
Arguments:
- path: Filepath to a folder where the data is downloaded for further processing.
- batch_size: The batch size for training.
- patch_shape: The patch shape to use for training.
- split: The choice of data split. One of 'train', 'val', 'test' or 'all' (uses all splits).
- resize_inputs: Whether to resize the inputs to the expected patch shape.
- 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.