torch_em.data.datasets.histopathology.amonuseg

The AMONuSeg dataset contains nucleus annotations for H&E stained images from Nigeria.

The dataset holds 48 images of 1280 x 960 pixels, taken with a microscope camera, with 12 images each of breast, cervical, inguinal (lymph node) and skin tissue. Two experts annotated the nuclei: 'exp1' and 'exp2'. Both annotators corrected draft masks from Fiji by hand, and four pathologists reviewed and validated them. NOTE: The masks are semantic (nucleus vs. background), not instance masks. This module creates the instances as connected components, so touching nuclei merge into one instance. NOTE: Some mask files are stored with lossy compression artifacts, so this module binarizes all masks at half the intensity range before it labels them.

The dataset is located at https://github.com/zerouaoui/AMONUSEG (MIT license). This dataset is from the publication https://doi.org/10.1007/978-3-031-72114-4_10. Please cite it if you use this dataset for your research.

  1"""The AMONuSeg dataset contains nucleus annotations for H&E stained images from Nigeria.
  2
  3The dataset holds 48 images of 1280 x 960 pixels, taken with a microscope camera, with 12 images each of breast,
  4cervical, inguinal (lymph node) and skin tissue. Two experts annotated the nuclei: 'exp1' and 'exp2'.
  5Both annotators corrected draft masks from Fiji by hand, and four pathologists reviewed and validated them.
  6NOTE: The masks are semantic (nucleus vs. background), not instance masks. This module creates the instances as
  7connected components, so touching nuclei merge into one instance.
  8NOTE: Some mask files are stored with lossy compression artifacts, so this module binarizes all masks at half
  9the intensity range before it labels them.
 10
 11The dataset is located at https://github.com/zerouaoui/AMONUSEG (MIT license).
 12This dataset is from the publication https://doi.org/10.1007/978-3-031-72114-4_10.
 13Please cite it if you use this dataset for your research.
 14"""
 15
 16import os
 17from glob import glob
 18from tqdm import tqdm
 19from natsort import natsorted
 20from typing import Union, Tuple, List, Literal, Optional, Sequence
 21
 22import imageio.v3 as imageio
 23
 24from torch.utils.data import Dataset, DataLoader
 25
 26import torch_em
 27
 28from .. import util
 29
 30
 31# The commit of the repository that this module downloads, so that the data does not change.
 32COMMIT = "5614bf5c0aff11b3bf66420af39502d734e1c857"
 33RAW_URL = f"https://raw.githubusercontent.com/zerouaoui/AMONUSEG/{COMMIT}"
 34TREE_URL = f"https://api.github.com/repos/zerouaoui/AMONUSEG/git/trees/{COMMIT}?recursive=1"
 35ORGANS = ("breast", "cervical", "inguinal", "skin")
 36# The folders of the images and of the masks of both experts, per organ. The repository spells them differently.
 37FOLDERS = {
 38    "breast": ("Breast", "Breast Masks", "Brest Masks E2"),
 39    "cervical": ("Cervical", "Cervical Masks", "Cervical Masks E2"),
 40    "inguinal": ("Inguinal", "Inguinal Masks", "Inguinal Masks E2"),
 41    "skin": ("Skin", "Skin Masks", "Skin Masks E2"),
 42}
 43
 44
 45def _download_files(path, download):
 46    import json
 47    from urllib.parse import quote
 48
 49    tree_path = os.path.join(path, "tree.json")
 50    util.download_source(tree_path, TREE_URL, download, checksum=None)
 51    with open(tree_path) as f:
 52        files = [x["path"] for x in json.load(f)["tree"] if x["type"] == "blob" and x["path"].startswith("AMONuSeg/")]
 53    for name in files:
 54        local = os.path.join(path, name)
 55        os.makedirs(os.path.dirname(local), exist_ok=True)
 56        util.download_source(local, f"{RAW_URL}/{quote(name)}", download, checksum=None)
 57
 58
 59def _find_mask(mask_dir, stem):
 60    # The mask of image '<stem>.jpg' is '<stem>.png' or '<stem>-Mask-EXP1.png'.
 61    candidates = glob(os.path.join(mask_dir, f"{stem}.*")) + glob(os.path.join(mask_dir, f"{stem}-*"))
 62    assert len(candidates) == 1, (mask_dir, stem, candidates)
 63    return candidates[0]
 64
 65
 66def _to_instances(mask_path):
 67    from skimage.measure import label
 68
 69    mask = imageio.imread(mask_path)
 70    mask = mask[..., 0] if mask.ndim == 3 else mask
 71    return label(mask > (mask.max() / 2)).astype("uint32")
 72
 73
 74def _create_h5_files(path):
 75    import h5py
 76
 77    for organ in ORGANS:
 78        image_dir, exp1_dir, exp2_dir = (os.path.join(path, "AMONuSeg", folder) for folder in FOLDERS[organ])
 79        out_dir = os.path.join(path, "preprocessed", organ)
 80        os.makedirs(out_dir, exist_ok=True)
 81        for image_path in tqdm(natsorted(glob(os.path.join(image_dir, "*.jpg"))), desc=f"Preprocess {organ} images"):
 82            stem = os.path.splitext(os.path.basename(image_path))[0]
 83            image = imageio.imread(image_path)[..., :3]
 84            exp1 = _to_instances(_find_mask(exp1_dir, stem))
 85            exp2 = _to_instances(_find_mask(exp2_dir, stem))
 86            assert image.shape[:2] == exp1.shape == exp2.shape, stem
 87
 88            with h5py.File(os.path.join(out_dir, f"{stem}.h5"), "w") as f:
 89                f.create_dataset("raw", data=image.transpose(2, 0, 1), compression="gzip")
 90                f.create_dataset("labels/instances/exp1", data=exp1, compression="gzip")
 91                f.create_dataset("labels/instances/exp2", data=exp2, compression="gzip")
 92
 93
 94def get_amonuseg_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 95    """Download the AMONuSeg dataset.
 96
 97    Args:
 98        path: Filepath to a folder where the downloaded data is saved.
 99        download: Whether to download the data if it is not present.
100
101    Returns:
102        Filepath where the preprocessed data is stored.
103    """
104    data_dir = os.path.join(path, "preprocessed")
105    if os.path.exists(data_dir):
106        return data_dir
107
108    os.makedirs(path, exist_ok=True)
109    _download_files(path, download)
110    _create_h5_files(path)
111    return data_dir
112
113
114def get_amonuseg_paths(
115    path: Union[os.PathLike, str], organs: Optional[Sequence[str]] = None, download: bool = False
116) -> List[str]:
117    """Get paths to the AMONuSeg data.
118
119    Args:
120        path: Filepath to a folder where the downloaded data is saved.
121        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are returned.
122        download: Whether to download the data if it is not present.
123
124    Returns:
125        List of filepaths for the input data.
126    """
127    data_dir = get_amonuseg_data(path, download)
128    organs = ORGANS if organs is None else organs
129    assert all(organ in ORGANS for organ in organs), organs
130    return [p for organ in organs for p in natsorted(glob(os.path.join(data_dir, organ, "*.h5")))]
131
132
133def get_amonuseg_dataset(
134    path: Union[os.PathLike, str],
135    patch_shape: Tuple[int, int],
136    organs: Optional[Sequence[str]] = None,
137    annotator: Literal["exp1", "exp2"] = "exp1",
138    download: bool = False,
139    **kwargs
140) -> Dataset:
141    """Get the AMONuSeg dataset for nucleus segmentation.
142
143    Args:
144        path: Filepath to a folder where the downloaded data is saved.
145        patch_shape: The patch shape to use for training.
146        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
147        annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
148        download: Whether to download the data if it is not present.
149        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
150
151    Returns:
152        The segmentation dataset.
153    """
154    assert annotator in ("exp1", "exp2"), annotator
155    paths = get_amonuseg_paths(path, organs, download)
156    return torch_em.default_segmentation_dataset(
157        raw_paths=paths,
158        raw_key="raw",
159        label_paths=paths,
160        label_key=f"labels/instances/{annotator}",
161        patch_shape=patch_shape,
162        ndim=2,
163        with_channels=True,
164        **kwargs
165    )
166
167
168def get_amonuseg_loader(
169    path: Union[os.PathLike, str],
170    batch_size: int,
171    patch_shape: Tuple[int, int],
172    organs: Optional[Sequence[str]] = None,
173    annotator: Literal["exp1", "exp2"] = "exp1",
174    download: bool = False,
175    **kwargs
176) -> DataLoader:
177    """Get the AMONuSeg dataloader for nucleus segmentation.
178
179    Args:
180        path: Filepath to a folder where the downloaded data is saved.
181        batch_size: The batch size for training.
182        patch_shape: The patch shape to use for training.
183        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
184        annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
185        download: Whether to download the data if it is not present.
186        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
187
188    Returns:
189        The DataLoader.
190    """
191    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
192    dataset = get_amonuseg_dataset(path, patch_shape, organs, annotator, download, **ds_kwargs)
193    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
COMMIT = '5614bf5c0aff11b3bf66420af39502d734e1c857'
RAW_URL = 'https://raw.githubusercontent.com/zerouaoui/AMONUSEG/5614bf5c0aff11b3bf66420af39502d734e1c857'
TREE_URL = 'https://api.github.com/repos/zerouaoui/AMONUSEG/git/trees/5614bf5c0aff11b3bf66420af39502d734e1c857?recursive=1'
ORGANS = ('breast', 'cervical', 'inguinal', 'skin')
FOLDERS = {'breast': ('Breast', 'Breast Masks', 'Brest Masks E2'), 'cervical': ('Cervical', 'Cervical Masks', 'Cervical Masks E2'), 'inguinal': ('Inguinal', 'Inguinal Masks', 'Inguinal Masks E2'), 'skin': ('Skin', 'Skin Masks', 'Skin Masks E2')}
def get_amonuseg_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 95def get_amonuseg_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 96    """Download the AMONuSeg dataset.
 97
 98    Args:
 99        path: Filepath to a folder where the downloaded data is saved.
100        download: Whether to download the data if it is not present.
101
102    Returns:
103        Filepath where the preprocessed data is stored.
104    """
105    data_dir = os.path.join(path, "preprocessed")
106    if os.path.exists(data_dir):
107        return data_dir
108
109    os.makedirs(path, exist_ok=True)
110    _download_files(path, download)
111    _create_h5_files(path)
112    return data_dir

Download the AMONuSeg dataset.

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

Filepath where the preprocessed data is stored.

def get_amonuseg_paths( path: Union[os.PathLike, str], organs: Optional[Sequence[str]] = None, download: bool = False) -> List[str]:
115def get_amonuseg_paths(
116    path: Union[os.PathLike, str], organs: Optional[Sequence[str]] = None, download: bool = False
117) -> List[str]:
118    """Get paths to the AMONuSeg data.
119
120    Args:
121        path: Filepath to a folder where the downloaded data is saved.
122        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are returned.
123        download: Whether to download the data if it is not present.
124
125    Returns:
126        List of filepaths for the input data.
127    """
128    data_dir = get_amonuseg_data(path, download)
129    organs = ORGANS if organs is None else organs
130    assert all(organ in ORGANS for organ in organs), organs
131    return [p for organ in organs for p in natsorted(glob(os.path.join(data_dir, organ, "*.h5")))]

Get paths to the AMONuSeg data.

Arguments:
  • path: Filepath to a folder where the downloaded data is saved.
  • organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are returned.
  • download: Whether to download the data if it is not present.
Returns:

List of filepaths for the input data.

def get_amonuseg_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], organs: Optional[Sequence[str]] = None, annotator: Literal['exp1', 'exp2'] = 'exp1', download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
134def get_amonuseg_dataset(
135    path: Union[os.PathLike, str],
136    patch_shape: Tuple[int, int],
137    organs: Optional[Sequence[str]] = None,
138    annotator: Literal["exp1", "exp2"] = "exp1",
139    download: bool = False,
140    **kwargs
141) -> Dataset:
142    """Get the AMONuSeg dataset for nucleus segmentation.
143
144    Args:
145        path: Filepath to a folder where the downloaded data is saved.
146        patch_shape: The patch shape to use for training.
147        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
148        annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
149        download: Whether to download the data if it is not present.
150        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
151
152    Returns:
153        The segmentation dataset.
154    """
155    assert annotator in ("exp1", "exp2"), annotator
156    paths = get_amonuseg_paths(path, organs, download)
157    return torch_em.default_segmentation_dataset(
158        raw_paths=paths,
159        raw_key="raw",
160        label_paths=paths,
161        label_key=f"labels/instances/{annotator}",
162        patch_shape=patch_shape,
163        ndim=2,
164        with_channels=True,
165        **kwargs
166    )

Get the AMONuSeg dataset for nucleus segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data is saved.
  • patch_shape: The patch shape to use for training.
  • organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
  • annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
  • 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_amonuseg_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], organs: Optional[Sequence[str]] = None, annotator: Literal['exp1', 'exp2'] = 'exp1', download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
169def get_amonuseg_loader(
170    path: Union[os.PathLike, str],
171    batch_size: int,
172    patch_shape: Tuple[int, int],
173    organs: Optional[Sequence[str]] = None,
174    annotator: Literal["exp1", "exp2"] = "exp1",
175    download: bool = False,
176    **kwargs
177) -> DataLoader:
178    """Get the AMONuSeg dataloader for nucleus segmentation.
179
180    Args:
181        path: Filepath to a folder where the downloaded data is saved.
182        batch_size: The batch size for training.
183        patch_shape: The patch shape to use for training.
184        organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
185        annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
186        download: Whether to download the data if it is not present.
187        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
188
189    Returns:
190        The DataLoader.
191    """
192    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
193    dataset = get_amonuseg_dataset(path, patch_shape, organs, annotator, download, **ds_kwargs)
194    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the AMONuSeg dataloader for nucleus segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data is saved.
  • batch_size: The batch size for training.
  • patch_shape: The patch shape to use for training.
  • organs: The organs, out of 'breast', 'cervical', 'inguinal' and 'skin'. By default, all organs are used.
  • annotator: The annotations of the first ('exp1') or the second ('exp2') expert.
  • download: Whether to download the data if it is not present.
  • kwargs: Additional keyword arguments for torch_em.default_segmentation_dataset or for the PyTorch DataLoader.
Returns:

The DataLoader.