torch_em.data.datasets.histopathology.cytonuke

The CytoNuke dataset contains annotations for nucleus and whole-cell segmentation in H&E stained bright-field histopathology images of head and neck squamous cell carcinoma. The images are re-distributed crops from the CPTAC-HNSCC collection.

This dataset is located at https://zenodo.org/records/10560728. This dataset is from the publication https://doi.org/10.1016/j.cmpb.2024.108215. Please cite it if you use this dataset for your research.

  1"""The CytoNuke dataset contains annotations for nucleus and whole-cell segmentation
  2in H&E stained bright-field histopathology images of head and neck squamous cell
  3carcinoma. The images are re-distributed crops from the CPTAC-HNSCC collection.
  4
  5This dataset is located at https://zenodo.org/records/10560728.
  6This dataset is from the publication https://doi.org/10.1016/j.cmpb.2024.108215.
  7Please cite it if you use this dataset for your research.
  8"""
  9
 10import os
 11from tqdm import tqdm
 12from natsort import natsorted
 13from typing import Union, Literal, Tuple, List
 14
 15import json
 16import numpy as np
 17import pandas as pd
 18import imageio.v3 as imageio
 19from skimage.draw import polygon as sk_polygon
 20from skimage.segmentation import relabel_sequential
 21from sklearn.model_selection import train_test_split
 22
 23from torch.utils.data import Dataset, DataLoader
 24
 25import torch_em
 26
 27from .. import util
 28
 29
 30URL = "https://zenodo.org/records/10560728/files/CytoNuke%20Dataset.zip"
 31CHECKSUM = "834836444fe749cc48daa7557458ba578bc00306a59286896e3d29666e98777a"
 32
 33CATEGORY_IDS = {"nuclei": 0, "cell": 1}
 34
 35
 36def _annotations_to_instances(coco, image_metadata, category_id):
 37    height, width = image_metadata["height"], image_metadata["width"]
 38    seg = np.zeros((height, width), dtype="uint32")
 39
 40    annotations = [a for a in coco["annotations"] if a["image_id"] == image_metadata["id"]]
 41    annotations = [a for a in annotations if a["category_id"] == category_id]
 42
 43    for seg_id, annotation in enumerate(annotations, 1):
 44        for polygon in annotation["segmentation"]:
 45            xs, ys = polygon[0::2], polygon[1::2]
 46            rr, cc = sk_polygon(ys, xs, shape=(height, width))
 47            seg[rr, cc] = seg_id
 48
 49    seg, _, _ = relabel_sequential(seg)
 50    return seg.astype("uint16")
 51
 52
 53def _create_segmentations_from_annotations(data_dir, annotations):
 54    image_dir = os.path.join(data_dir, "images")
 55    seg_dir = os.path.join(data_dir, "labels", annotations)
 56    os.makedirs(seg_dir, exist_ok=True)
 57
 58    with open(os.path.join(data_dir, "coco.json")) as f:
 59        coco = json.load(f)
 60
 61    category_id = CATEGORY_IDS[annotations]
 62
 63    image_paths, seg_paths = [], []
 64    for image_metadata in tqdm(coco["images"], desc=f"Creating '{annotations}' segmentations from coco annotations"):
 65        file_name = image_metadata["file_name"]
 66        image_path = os.path.join(image_dir, file_name)
 67        assert os.path.exists(image_path), image_path
 68        image_paths.append(image_path)
 69
 70        seg_path = os.path.join(seg_dir, file_name.replace(".png", ".tif"))
 71        seg_paths.append(seg_path)
 72        if os.path.exists(seg_path):
 73            continue
 74
 75        seg = _annotations_to_instances(coco, image_metadata, category_id)
 76        imageio.imwrite(seg_path, seg, compression="zlib")
 77
 78    return natsorted(image_paths), natsorted(seg_paths)
 79
 80
 81def _create_split_csv(path, image_paths):
 82    csv_path = os.path.join(path, "cytonuke_split.csv")
 83    if os.path.exists(csv_path):
 84        df = pd.read_csv(csv_path)
 85        return {split: json.loads(df.iloc[0][split].replace("'", '"')) for split in ("train", "val", "test")}
 86
 87    print(f"Creating a new split file at '{csv_path}'.")
 88    image_ids = natsorted(os.path.basename(p) for p in image_paths)
 89
 90    train_ids, test_ids = train_test_split(image_ids, test_size=0.2, random_state=42)
 91    train_ids, val_ids = train_test_split(train_ids, test_size=0.15, random_state=42)
 92    split_ids = {"train": train_ids, "val": val_ids, "test": test_ids}
 93
 94    df = pd.DataFrame.from_dict([split_ids])
 95    df.to_csv(csv_path, index=False)
 96
 97    return split_ids
 98
 99
100def get_cytonuke_data(path: Union[os.PathLike, str], download: bool = False) -> str:
101    """Download the CytoNuke data.
102
103    Args:
104        path: Filepath to a folder where the downloaded data will be saved.
105        download: Whether to download the data if it is not present.
106
107    Returns:
108        Filepath where the dataset is downloaded and stored for further preprocessing.
109    """
110    data_dir = os.path.join(path, "data")
111    if os.path.exists(data_dir):
112        return data_dir
113
114    os.makedirs(path, exist_ok=True)
115    zip_path = os.path.join(path, "cytonuke.zip")
116    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
117    util.unzip(zip_path=zip_path, dst=data_dir)
118
119    return data_dir
120
121
122def get_cytonuke_paths(
123    path: Union[os.PathLike, str],
124    split: Literal["train", "val", "test"],
125    annotations: Literal["nuclei", "cell"] = "cell",
126    download: bool = False,
127) -> Tuple[List[str], List[str]]:
128    """Get paths to the CytoNuke data.
129
130    NOTE: The source publishes no official split, so this function creates and stores a
131    deterministic split (65% train, 15% val, 20% test) the first time it is called.
132
133    Args:
134        path: Filepath to a folder where the downloaded data will be saved.
135        split: The choice of data split.
136        annotations: The choice of annotations.
137        download: Whether to download the data if it is not present.
138
139    Returns:
140        List of filepaths to the image data.
141        List of filepaths to the label data.
142    """
143    if annotations not in CATEGORY_IDS:
144        raise ValueError(f"'{annotations}' is not a valid annotation choice.")
145
146    data_dir = get_cytonuke_data(path, download)
147    image_paths, seg_paths = _create_segmentations_from_annotations(data_dir, annotations)
148
149    split_ids = _create_split_csv(path, image_paths)[split]
150    raw_paths = [p for p in image_paths if os.path.basename(p) in split_ids]
151    label_paths = [p for p in seg_paths if os.path.basename(p).replace(".tif", ".png") in split_ids]
152
153    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
154    return raw_paths, label_paths
155
156
157def get_cytonuke_dataset(
158    path: Union[os.PathLike, str],
159    patch_shape: Tuple[int, int],
160    split: Literal["train", "val", "test"],
161    annotations: Literal["nuclei", "cell"] = "cell",
162    resize_inputs: bool = False,
163    download: bool = False,
164    **kwargs
165) -> Dataset:
166    """Get the CytoNuke dataset for nucleus and whole-cell segmentation.
167
168    Args:
169        path: Filepath to a folder where the downloaded data will be saved.
170        patch_shape: The patch shape to use for training.
171        split: The choice of data split.
172        annotations: The choice of annotations.
173        resize_inputs: Whether to resize the inputs.
174        download: Whether to download the data if it is not present.
175        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
176
177    Returns:
178        The segmentation dataset.
179    """
180    raw_paths, label_paths = get_cytonuke_paths(path, split, annotations, download)
181
182    if resize_inputs:
183        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
184        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
185            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
186        )
187
188    return torch_em.default_segmentation_dataset(
189        raw_paths=raw_paths,
190        raw_key=None,
191        label_paths=label_paths,
192        label_key=None,
193        is_seg_dataset=False,
194        patch_shape=patch_shape,
195        with_channels=True,
196        ndim=2,
197        **kwargs
198    )
199
200
201def get_cytonuke_loader(
202    path: Union[os.PathLike, str],
203    batch_size: int,
204    patch_shape: Tuple[int, int],
205    split: Literal["train", "val", "test"],
206    annotations: Literal["nuclei", "cell"] = "cell",
207    resize_inputs: bool = False,
208    download: bool = False,
209    **kwargs
210) -> DataLoader:
211    """Get the CytoNuke dataloader for nucleus and whole-cell segmentation.
212
213    Args:
214        path: Filepath to a folder where the downloaded data will be saved.
215        batch_size: The batch size for training.
216        patch_shape: The patch shape to use for training.
217        split: The choice of data split.
218        annotations: The choice of annotations.
219        resize_inputs: Whether to resize the inputs.
220        download: Whether to download the data if it is not present.
221        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
222
223    Returns:
224        The DataLoader.
225    """
226    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
227    dataset = get_cytonuke_dataset(path, patch_shape, split, annotations, resize_inputs, download, **ds_kwargs)
228    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://zenodo.org/records/10560728/files/CytoNuke%20Dataset.zip'
CHECKSUM = '834836444fe749cc48daa7557458ba578bc00306a59286896e3d29666e98777a'
CATEGORY_IDS = {'nuclei': 0, 'cell': 1}
def get_cytonuke_data(path: Union[os.PathLike, str], download: bool = False) -> str:
101def get_cytonuke_data(path: Union[os.PathLike, str], download: bool = False) -> str:
102    """Download the CytoNuke data.
103
104    Args:
105        path: Filepath to a folder where the downloaded data will be saved.
106        download: Whether to download the data if it is not present.
107
108    Returns:
109        Filepath where the dataset is downloaded and stored for further preprocessing.
110    """
111    data_dir = os.path.join(path, "data")
112    if os.path.exists(data_dir):
113        return data_dir
114
115    os.makedirs(path, exist_ok=True)
116    zip_path = os.path.join(path, "cytonuke.zip")
117    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
118    util.unzip(zip_path=zip_path, dst=data_dir)
119
120    return data_dir

Download the CytoNuke data.

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:

Filepath where the dataset is downloaded and stored for further preprocessing.

def get_cytonuke_paths( path: Union[os.PathLike, str], split: Literal['train', 'val', 'test'], annotations: Literal['nuclei', 'cell'] = 'cell', download: bool = False) -> Tuple[List[str], List[str]]:
123def get_cytonuke_paths(
124    path: Union[os.PathLike, str],
125    split: Literal["train", "val", "test"],
126    annotations: Literal["nuclei", "cell"] = "cell",
127    download: bool = False,
128) -> Tuple[List[str], List[str]]:
129    """Get paths to the CytoNuke data.
130
131    NOTE: The source publishes no official split, so this function creates and stores a
132    deterministic split (65% train, 15% val, 20% test) the first time it is called.
133
134    Args:
135        path: Filepath to a folder where the downloaded data will be saved.
136        split: The choice of data split.
137        annotations: The choice of annotations.
138        download: Whether to download the data if it is not present.
139
140    Returns:
141        List of filepaths to the image data.
142        List of filepaths to the label data.
143    """
144    if annotations not in CATEGORY_IDS:
145        raise ValueError(f"'{annotations}' is not a valid annotation choice.")
146
147    data_dir = get_cytonuke_data(path, download)
148    image_paths, seg_paths = _create_segmentations_from_annotations(data_dir, annotations)
149
150    split_ids = _create_split_csv(path, image_paths)[split]
151    raw_paths = [p for p in image_paths if os.path.basename(p) in split_ids]
152    label_paths = [p for p in seg_paths if os.path.basename(p).replace(".tif", ".png") in split_ids]
153
154    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
155    return raw_paths, label_paths

Get paths to the CytoNuke data.

NOTE: The source publishes no official split, so this function creates and stores a deterministic split (65% train, 15% val, 20% test) the first time it is called.

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

List of filepaths to the image data. List of filepaths to the label data.

def get_cytonuke_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'], annotations: Literal['nuclei', 'cell'] = 'cell', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
158def get_cytonuke_dataset(
159    path: Union[os.PathLike, str],
160    patch_shape: Tuple[int, int],
161    split: Literal["train", "val", "test"],
162    annotations: Literal["nuclei", "cell"] = "cell",
163    resize_inputs: bool = False,
164    download: bool = False,
165    **kwargs
166) -> Dataset:
167    """Get the CytoNuke dataset for nucleus and whole-cell segmentation.
168
169    Args:
170        path: Filepath to a folder where the downloaded data will be saved.
171        patch_shape: The patch shape to use for training.
172        split: The choice of data split.
173        annotations: The choice of annotations.
174        resize_inputs: Whether to resize the inputs.
175        download: Whether to download the data if it is not present.
176        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
177
178    Returns:
179        The segmentation dataset.
180    """
181    raw_paths, label_paths = get_cytonuke_paths(path, split, annotations, download)
182
183    if resize_inputs:
184        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
185        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
186            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
187        )
188
189    return torch_em.default_segmentation_dataset(
190        raw_paths=raw_paths,
191        raw_key=None,
192        label_paths=label_paths,
193        label_key=None,
194        is_seg_dataset=False,
195        patch_shape=patch_shape,
196        with_channels=True,
197        ndim=2,
198        **kwargs
199    )

Get the CytoNuke dataset for nucleus and whole-cell segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • patch_shape: The patch shape to use for training.
  • split: The choice of data split.
  • annotations: The choice of annotations.
  • resize_inputs: Whether to resize the inputs.
  • 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_cytonuke_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], split: Literal['train', 'val', 'test'], annotations: Literal['nuclei', 'cell'] = 'cell', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
202def get_cytonuke_loader(
203    path: Union[os.PathLike, str],
204    batch_size: int,
205    patch_shape: Tuple[int, int],
206    split: Literal["train", "val", "test"],
207    annotations: Literal["nuclei", "cell"] = "cell",
208    resize_inputs: bool = False,
209    download: bool = False,
210    **kwargs
211) -> DataLoader:
212    """Get the CytoNuke dataloader for nucleus and whole-cell segmentation.
213
214    Args:
215        path: Filepath to a folder where the downloaded data will be saved.
216        batch_size: The batch size for training.
217        patch_shape: The patch shape to use for training.
218        split: The choice of data split.
219        annotations: The choice of annotations.
220        resize_inputs: Whether to resize the inputs.
221        download: Whether to download the data if it is not present.
222        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
223
224    Returns:
225        The DataLoader.
226    """
227    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
228    dataset = get_cytonuke_dataset(path, patch_shape, split, annotations, resize_inputs, download, **ds_kwargs)
229    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the CytoNuke dataloader for nucleus and whole-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.
  • split: The choice of data split.
  • annotations: The choice of annotations.
  • resize_inputs: Whether to resize the inputs.
  • 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.