torch_em.data.datasets.light_microscopy.mrose_bacteria

The M-ROSE bacteria dataset contains annotations for bacteria segmentation in Gram-stained bright-field microscopy images of respiratory specimens.

The images come from microbiological rapid on-site evaluation (M-ROSE) of patients with a lung infection. The dataset holds 6005 crops of 640 x 640 pixels with 11824 bacteria. Every bacterium has a polygon and a Gram status, so this loader can return instance labels or the two Gram classes.

NOTE: The annotations are sparse. A clinical smear holds debris and unlabelled objects, and every object without a polygon becomes background. Take this into account when you measure recall.

NOTE: The archive also holds detection labels that separate cocci from bacilli. This loader uses the segmentation folder only, and its labels carry the Gram status alone.

NOTE: The image '000077_0_6' is in none of the three split files, so the loader skips it. The splits cover 6004 of the 6005 crops.

The dataset is located at https://doi.org/10.5281/zenodo.10526360 under the CC BY 4.0 license. This dataset is from the publication https://doi.org/10.1038/s41597-024-03370-5. Please cite it if you use this dataset in your research.

  1"""The M-ROSE bacteria dataset contains annotations for bacteria segmentation
  2in Gram-stained bright-field microscopy images of respiratory specimens.
  3
  4The images come from microbiological rapid on-site evaluation (M-ROSE) of patients with a lung
  5infection. The dataset holds 6005 crops of 640 x 640 pixels with 11824 bacteria. Every bacterium
  6has a polygon and a Gram status, so this loader can return instance labels or the two Gram classes.
  7
  8NOTE: The annotations are sparse. A clinical smear holds debris and unlabelled objects, and every
  9object without a polygon becomes background. Take this into account when you measure recall.
 10
 11NOTE: The archive also holds detection labels that separate cocci from bacilli. This loader uses
 12the segmentation folder only, and its labels carry the Gram status alone.
 13
 14NOTE: The image '000077_0_6' is in none of the three split files, so the loader skips it. The splits
 15cover 6004 of the 6005 crops.
 16
 17The dataset is located at https://doi.org/10.5281/zenodo.10526360 under the CC BY 4.0 license.
 18This dataset is from the publication https://doi.org/10.1038/s41597-024-03370-5.
 19Please cite it if you use this dataset in your research.
 20"""
 21
 22import os
 23import json
 24import zipfile
 25from glob import glob
 26from pathlib import Path
 27from natsort import natsorted
 28from typing import List, Literal, Optional, Tuple, Union
 29
 30import numpy as np
 31import imageio.v3 as imageio
 32
 33from torch.utils.data import DataLoader, Dataset
 34
 35import torch_em
 36
 37from .. import util
 38
 39
 40URL = "https://zenodo.org/records/10526360/files/DeepDataSet.zip?download=1"
 41CHECKSUM = "c5ecaa65fa8c515b3495148c55f48aceeecea0114b4ecbf59c6faf43fb2df4a6"
 42
 43# The archive also holds a detection folder and a plain image folder, which this loader ignores.
 44ARCHIVE_FOLDER = "SegmentationDataSet"
 45
 46SPLITS = ("train", "val", "test")
 47
 48# The annotations mark a Gram-positive bacterium with 'G+' and a Gram-negative one with 'G'.
 49GRAM_IDS = {"G+": 1, "G": 2}
 50
 51
 52def _extract_segmentation_folder(zip_path: str, path: str) -> None:
 53    """Extract only the segmentation folder of the archive."""
 54    with zipfile.ZipFile(zip_path) as archive:
 55        members = [n for n in archive.namelist() if n.startswith(f"{ARCHIVE_FOLDER}/")]
 56        if not members:
 57            raise RuntimeError(f"The archive {zip_path} does not hold a '{ARCHIVE_FOLDER}' folder.")
 58        archive.extractall(path, members=members)
 59
 60
 61def _rasterize(shapes, shape: Tuple[int, int]) -> Tuple[np.ndarray, np.ndarray]:
 62    """Draw one label per bacterium, and a second image with the Gram status."""
 63    from skimage.draw import polygon as draw_polygon
 64
 65    instances = np.zeros(shape, dtype="uint16")
 66    semantic = np.zeros(shape, dtype="uint8")
 67    for instance_id, item in enumerate(shapes, start=1):
 68        points = np.array(item["points"], dtype=float)
 69        rows, columns = draw_polygon(points[:, 1], points[:, 0], shape=shape)
 70        instances[rows, columns] = instance_id
 71        semantic[rows, columns] = GRAM_IDS.get(item.get("label"), 0)
 72    return instances, semantic
 73
 74
 75def _create_labels(data_dir: str) -> Tuple[str, str]:
 76    """Rasterize the polygons of every crop into label images."""
 77    from tqdm import tqdm
 78
 79    instance_dir = os.path.join(data_dir, "instance_labels")
 80    semantic_dir = os.path.join(data_dir, "semantic_labels")
 81    os.makedirs(instance_dir, exist_ok=True)
 82    os.makedirs(semantic_dir, exist_ok=True)
 83
 84    json_paths = natsorted(glob(os.path.join(data_dir, "json", "*.json")))
 85    for json_path in tqdm(json_paths, desc="Preprocess the M-ROSE annotations"):
 86        stem = Path(json_path).stem
 87        instance_path = os.path.join(instance_dir, f"{stem}.tif")
 88        semantic_path = os.path.join(semantic_dir, f"{stem}.tif")
 89        if os.path.exists(instance_path) and os.path.exists(semantic_path):
 90            continue
 91
 92        with open(json_path) as f:
 93            annotation = json.load(f)
 94
 95        shape = (annotation["imageHeight"], annotation["imageWidth"])
 96        instances, semantic = _rasterize(annotation.get("shapes", []), shape)
 97        imageio.imwrite(instance_path, instances, compression="zlib")
 98        imageio.imwrite(semantic_path, semantic, compression="zlib")
 99
100    return instance_dir, semantic_dir
101
102
103def get_mrose_bacteria_data(path: Union[os.PathLike, str], download: bool = False) -> str:
104    """Download the M-ROSE bacteria dataset.
105
106    Args:
107        path: Filepath to a folder where the downloaded data will be saved.
108        download: Whether to download the data if it is not present.
109
110    Returns:
111        The filepath to the extracted segmentation data.
112    """
113    data_dir = os.path.join(path, ARCHIVE_FOLDER)
114    if os.path.exists(data_dir):
115        return data_dir
116
117    os.makedirs(path, exist_ok=True)
118    zip_path = os.path.join(path, "DeepDataSet.zip")
119    util.download_source(zip_path, URL, download, CHECKSUM)
120    _extract_segmentation_folder(zip_path, path)
121
122    return data_dir
123
124
125def get_mrose_bacteria_paths(
126    path: Union[os.PathLike, str],
127    split: Literal["train", "val", "test"] = "train",
128    label_choice: Literal["instances", "semantic"] = "instances",
129    download: bool = False,
130) -> Tuple[List[str], List[str]]:
131    """Get paths to the M-ROSE bacteria data.
132
133    Args:
134        path: Filepath to a folder where the downloaded data will be saved.
135        split: The data split. Either 'train', 'val' or 'test'.
136        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
137            Gram classes, where one is Gram-positive and two is Gram-negative.
138        download: Whether to download the data if it is not present.
139
140    Returns:
141        List of filepaths for the image data.
142        List of filepaths for the label data.
143    """
144    if split not in SPLITS:
145        raise ValueError(f"'{split}' is not a valid split. Choose from {list(SPLITS)}.")
146    if label_choice not in ("instances", "semantic"):
147        raise ValueError(f"'{label_choice}' is not a valid label choice. Choose 'instances' or 'semantic'.")
148
149    data_dir = get_mrose_bacteria_data(path, download)
150    instance_dir, semantic_dir = _create_labels(data_dir)
151    label_dir = instance_dir if label_choice == "instances" else semantic_dir
152
153    split_path = os.path.join(data_dir, "txt", f"{split}.txt")
154    with open(split_path) as f:
155        stems = [line.strip() for line in f if line.strip()]
156
157    image_paths, label_paths = [], []
158    for stem in stems:
159        image_path = os.path.join(data_dir, "images", f"{stem}.jpg")
160        label_path = os.path.join(label_dir, f"{stem}.tif")
161        if not (os.path.exists(image_path) and os.path.exists(label_path)):
162            continue
163        image_paths.append(image_path)
164        label_paths.append(label_path)
165
166    if not image_paths:
167        raise RuntimeError(f"Could not find any M-ROSE data for the '{split}' split in {data_dir}.")
168
169    return image_paths, label_paths
170
171
172def get_mrose_bacteria_dataset(
173    path: Union[os.PathLike, str],
174    patch_shape: Tuple[int, int],
175    split: Literal["train", "val", "test"] = "train",
176    label_choice: Literal["instances", "semantic"] = "instances",
177    offsets: Optional[List[List[int]]] = None,
178    boundaries: bool = False,
179    binary: bool = False,
180    download: bool = False,
181    **kwargs,
182) -> Dataset:
183    """Get the M-ROSE bacteria dataset for bacteria segmentation.
184
185    Args:
186        path: Filepath to a folder where the downloaded data will be saved.
187        patch_shape: The 2D patch shape to use for training.
188        split: The data split. Either 'train', 'val' or 'test'.
189        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
190            Gram classes, where one is Gram-positive and two is Gram-negative.
191        offsets: Offset values for affinity computation used as target.
192        boundaries: Whether to compute boundaries as the target.
193        binary: Whether to use a binary segmentation target.
194        download: Whether to download the data if it is not present.
195        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
196
197    Returns:
198        The segmentation dataset.
199    """
200    if len(patch_shape) != 2:
201        raise ValueError(f"The M-ROSE patch shape must be two-dimensional, got {patch_shape}.")
202
203    image_paths, label_paths = get_mrose_bacteria_paths(path, split, label_choice, download)
204
205    if label_choice == "instances":
206        kwargs, _ = util.add_instance_label_transform(
207            kwargs, add_binary_target=True, offsets=offsets, boundaries=boundaries, binary=binary,
208        )
209    kwargs = util.ensure_transforms(ndim=2, **kwargs)
210
211    return torch_em.default_segmentation_dataset(
212        raw_paths=image_paths,
213        raw_key=None,
214        label_paths=label_paths,
215        label_key=None,
216        patch_shape=patch_shape,
217        is_seg_dataset=False,
218        ndim=2,
219        **kwargs,
220    )
221
222
223def get_mrose_bacteria_loader(
224    path: Union[os.PathLike, str],
225    batch_size: int,
226    patch_shape: Tuple[int, int],
227    split: Literal["train", "val", "test"] = "train",
228    label_choice: Literal["instances", "semantic"] = "instances",
229    offsets: Optional[List[List[int]]] = None,
230    boundaries: bool = False,
231    binary: bool = False,
232    download: bool = False,
233    **kwargs,
234) -> DataLoader:
235    """Get the M-ROSE bacteria dataloader for bacteria segmentation.
236
237    Args:
238        path: Filepath to a folder where the downloaded data will be saved.
239        batch_size: The batch size for training.
240        patch_shape: The 2D patch shape to use for training.
241        split: The data split. Either 'train', 'val' or 'test'.
242        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
243            Gram classes, where one is Gram-positive and two is Gram-negative.
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        download: Whether to download the data if it is not present.
248        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or the PyTorch DataLoader.
249
250    Returns:
251        The DataLoader.
252    """
253    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
254    dataset = get_mrose_bacteria_dataset(
255        path=path,
256        patch_shape=patch_shape,
257        split=split,
258        label_choice=label_choice,
259        offsets=offsets,
260        boundaries=boundaries,
261        binary=binary,
262        download=download,
263        **ds_kwargs,
264    )
265    return torch_em.get_data_loader(dataset, batch_size=batch_size, **loader_kwargs)
URL = 'https://zenodo.org/records/10526360/files/DeepDataSet.zip?download=1'
CHECKSUM = 'c5ecaa65fa8c515b3495148c55f48aceeecea0114b4ecbf59c6faf43fb2df4a6'
ARCHIVE_FOLDER = 'SegmentationDataSet'
SPLITS = ('train', 'val', 'test')
GRAM_IDS = {'G+': 1, 'G': 2}
def get_mrose_bacteria_data(path: Union[os.PathLike, str], download: bool = False) -> str:
104def get_mrose_bacteria_data(path: Union[os.PathLike, str], download: bool = False) -> str:
105    """Download the M-ROSE bacteria dataset.
106
107    Args:
108        path: Filepath to a folder where the downloaded data will be saved.
109        download: Whether to download the data if it is not present.
110
111    Returns:
112        The filepath to the extracted segmentation data.
113    """
114    data_dir = os.path.join(path, ARCHIVE_FOLDER)
115    if os.path.exists(data_dir):
116        return data_dir
117
118    os.makedirs(path, exist_ok=True)
119    zip_path = os.path.join(path, "DeepDataSet.zip")
120    util.download_source(zip_path, URL, download, CHECKSUM)
121    _extract_segmentation_folder(zip_path, path)
122
123    return data_dir

Download the M-ROSE bacteria dataset.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • download: Whether to download the data if it is not present.
Returns:

The filepath to the extracted segmentation data.

def get_mrose_bacteria_paths( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'] = 'train', label_choice: Literal['instances', 'semantic'] = 'instances', download: bool = False) -> Tuple[List[str], List[str]]:
126def get_mrose_bacteria_paths(
127    path: Union[os.PathLike, str],
128    split: Literal["train", "val", "test"] = "train",
129    label_choice: Literal["instances", "semantic"] = "instances",
130    download: bool = False,
131) -> Tuple[List[str], List[str]]:
132    """Get paths to the M-ROSE bacteria data.
133
134    Args:
135        path: Filepath to a folder where the downloaded data will be saved.
136        split: The data split. Either 'train', 'val' or 'test'.
137        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
138            Gram classes, where one is Gram-positive and two is Gram-negative.
139        download: Whether to download the data if it is not present.
140
141    Returns:
142        List of filepaths for the image data.
143        List of filepaths for the label data.
144    """
145    if split not in SPLITS:
146        raise ValueError(f"'{split}' is not a valid split. Choose from {list(SPLITS)}.")
147    if label_choice not in ("instances", "semantic"):
148        raise ValueError(f"'{label_choice}' is not a valid label choice. Choose 'instances' or 'semantic'.")
149
150    data_dir = get_mrose_bacteria_data(path, download)
151    instance_dir, semantic_dir = _create_labels(data_dir)
152    label_dir = instance_dir if label_choice == "instances" else semantic_dir
153
154    split_path = os.path.join(data_dir, "txt", f"{split}.txt")
155    with open(split_path) as f:
156        stems = [line.strip() for line in f if line.strip()]
157
158    image_paths, label_paths = [], []
159    for stem in stems:
160        image_path = os.path.join(data_dir, "images", f"{stem}.jpg")
161        label_path = os.path.join(label_dir, f"{stem}.tif")
162        if not (os.path.exists(image_path) and os.path.exists(label_path)):
163            continue
164        image_paths.append(image_path)
165        label_paths.append(label_path)
166
167    if not image_paths:
168        raise RuntimeError(f"Could not find any M-ROSE data for the '{split}' split in {data_dir}.")
169
170    return image_paths, label_paths

Get paths to the M-ROSE bacteria data.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • split: The data split. Either 'train', 'val' or 'test'.
  • label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the Gram classes, where one is Gram-positive and two is Gram-negative.
  • 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.

def get_mrose_bacteria_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'] = 'train', label_choice: Literal['instances', 'semantic'] = 'instances', offsets: Optional[List[List[int]]] = None, boundaries: bool = False, binary: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
173def get_mrose_bacteria_dataset(
174    path: Union[os.PathLike, str],
175    patch_shape: Tuple[int, int],
176    split: Literal["train", "val", "test"] = "train",
177    label_choice: Literal["instances", "semantic"] = "instances",
178    offsets: Optional[List[List[int]]] = None,
179    boundaries: bool = False,
180    binary: bool = False,
181    download: bool = False,
182    **kwargs,
183) -> Dataset:
184    """Get the M-ROSE bacteria dataset for bacteria segmentation.
185
186    Args:
187        path: Filepath to a folder where the downloaded data will be saved.
188        patch_shape: The 2D patch shape to use for training.
189        split: The data split. Either 'train', 'val' or 'test'.
190        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
191            Gram classes, where one is Gram-positive and two is Gram-negative.
192        offsets: Offset values for affinity computation used as target.
193        boundaries: Whether to compute boundaries as the target.
194        binary: Whether to use a binary segmentation target.
195        download: Whether to download the data if it is not present.
196        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
197
198    Returns:
199        The segmentation dataset.
200    """
201    if len(patch_shape) != 2:
202        raise ValueError(f"The M-ROSE patch shape must be two-dimensional, got {patch_shape}.")
203
204    image_paths, label_paths = get_mrose_bacteria_paths(path, split, label_choice, download)
205
206    if label_choice == "instances":
207        kwargs, _ = util.add_instance_label_transform(
208            kwargs, add_binary_target=True, offsets=offsets, boundaries=boundaries, binary=binary,
209        )
210    kwargs = util.ensure_transforms(ndim=2, **kwargs)
211
212    return torch_em.default_segmentation_dataset(
213        raw_paths=image_paths,
214        raw_key=None,
215        label_paths=label_paths,
216        label_key=None,
217        patch_shape=patch_shape,
218        is_seg_dataset=False,
219        ndim=2,
220        **kwargs,
221    )

Get the M-ROSE bacteria dataset for bacteria segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • patch_shape: The 2D patch shape to use for training.
  • split: The data split. Either 'train', 'val' or 'test'.
  • label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the Gram classes, where one is Gram-positive and two is Gram-negative.
  • 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.
  • 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.

def get_mrose_bacteria_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'] = 'train', label_choice: Literal['instances', 'semantic'] = 'instances', offsets: Optional[List[List[int]]] = None, boundaries: bool = False, binary: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
224def get_mrose_bacteria_loader(
225    path: Union[os.PathLike, str],
226    batch_size: int,
227    patch_shape: Tuple[int, int],
228    split: Literal["train", "val", "test"] = "train",
229    label_choice: Literal["instances", "semantic"] = "instances",
230    offsets: Optional[List[List[int]]] = None,
231    boundaries: bool = False,
232    binary: bool = False,
233    download: bool = False,
234    **kwargs,
235) -> DataLoader:
236    """Get the M-ROSE bacteria dataloader for bacteria segmentation.
237
238    Args:
239        path: Filepath to a folder where the downloaded data will be saved.
240        batch_size: The batch size for training.
241        patch_shape: The 2D patch shape to use for training.
242        split: The data split. Either 'train', 'val' or 'test'.
243        label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the
244            Gram classes, where one is Gram-positive and two is Gram-negative.
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        download: Whether to download the data if it is not present.
249        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or the PyTorch DataLoader.
250
251    Returns:
252        The DataLoader.
253    """
254    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
255    dataset = get_mrose_bacteria_dataset(
256        path=path,
257        patch_shape=patch_shape,
258        split=split,
259        label_choice=label_choice,
260        offsets=offsets,
261        boundaries=boundaries,
262        binary=binary,
263        download=download,
264        **ds_kwargs,
265    )
266    return torch_em.get_data_loader(dataset, batch_size=batch_size, **loader_kwargs)

Get the M-ROSE bacteria dataloader for bacteria segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • batch_size: The batch size for training.
  • patch_shape: The 2D patch shape to use for training.
  • split: The data split. Either 'train', 'val' or 'test'.
  • label_choice: The label to use. Either 'instances' for the bacteria, or 'semantic' for the Gram classes, where one is Gram-positive and two is Gram-negative.
  • 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.
  • download: Whether to download the data if it is not present.
  • kwargs: Additional keyword arguments for torch_em.default_segmentation_dataset or the PyTorch DataLoader.
Returns:

The DataLoader.