torch_em.data.datasets.histopathology.sinus

The SiNuS dataset contains annotations for singular nucleus segmentation in Dual In Situ Hybridization (DISH) images of breast cancer tissue.

NOTE: This dataset is sparsely annotated. It contains annotations for expert-selected singular nuclei suitable for HER2 grading, rather than for all nuclei in each image.

The dataset is located at https://data.mendeley.com/datasets/gtjrgwbntc/2. This dataset is from the publication https://doi.org/10.1016/j.dib.2026.112934. Please cite it if you use this dataset for your research.

  1"""The SiNuS dataset contains annotations for singular nucleus segmentation in
  2Dual In Situ Hybridization (DISH) images of breast cancer tissue.
  3
  4NOTE: This dataset is sparsely annotated. It contains annotations for expert-selected
  5singular nuclei suitable for HER2 grading, rather than for all nuclei in each image.
  6
  7The dataset is located at https://data.mendeley.com/datasets/gtjrgwbntc/2.
  8This dataset is from the publication https://doi.org/10.1016/j.dib.2026.112934.
  9Please cite it if you use this dataset for your research.
 10"""
 11
 12import json
 13import os
 14from glob import glob
 15from pathlib import Path
 16from tqdm import tqdm
 17from natsort import natsorted
 18from typing import List, Literal, Tuple, Union
 19
 20import numpy as np
 21import imageio.v3 as imageio
 22from skimage.draw import polygon
 23
 24from torch.utils.data import DataLoader, Dataset
 25
 26import torch_em
 27
 28from .. import util
 29
 30
 31URL = "https://data.mendeley.com/public-api/zip/gtjrgwbntc/download/2"
 32CHECKSUM = "aecd1399192ee511ba29f6c23e6f858b4e6a8328028c1ae91f9ee5a826728c5c"
 33
 34
 35def get_sinus_data(path: Union[os.PathLike, str], download: bool = False) -> str:
 36    """Download the SiNuS dataset.
 37
 38    Args:
 39        path: Filepath to a folder where the downloaded data will be saved.
 40        download: Whether to download the data if it is not present.
 41
 42    Returns:
 43        Filepath where the dataset is downloaded.
 44    """
 45    data_dir = os.path.join(path, "SiNuS A Comprehensive Dataset for Singular Nuclei", "SiNuS")
 46    if os.path.exists(data_dir):
 47        return data_dir
 48
 49    os.makedirs(path, exist_ok=True)
 50
 51    zip_path = os.path.join(path, "sinus.zip")
 52    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
 53    util.unzip(zip_path=zip_path, dst=path)
 54
 55    return data_dir
 56
 57
 58def _create_instance_labels(annotation_path: str, label_path: str) -> None:
 59    with open(annotation_path) as f:
 60        annotation = json.load(f)["annotation"]
 61
 62    shape = (annotation["size"]["height"], annotation["size"]["width"])
 63    labels = np.zeros(shape, dtype="uint16")
 64    for label_id, annotated_object in enumerate(annotation["objects"], 1):
 65        points = np.asarray(annotated_object["points"]["exterior"])
 66        rr, cc = polygon(points[:, 1], points[:, 0], shape=shape)
 67        labels[rr, cc] = label_id
 68
 69        for interior in annotated_object["points"]["interior"]:
 70            points = np.asarray(interior)
 71            rr, cc = polygon(points[:, 1], points[:, 0], shape=shape)
 72            labels[rr, cc] = 0
 73
 74    imageio.imwrite(label_path, labels, compression="zlib")
 75
 76
 77def get_sinus_paths(
 78    path: Union[os.PathLike, str],
 79    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
 80    download: bool = False,
 81) -> Tuple[List[str], List[str]]:
 82    """Get paths to the SiNuS data.
 83
 84    Args:
 85        path: Filepath to a folder where the downloaded data will be saved.
 86        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
 87            one expert, while the exclusive annotations contain nuclei selected by all experts.
 88        download: Whether to download the data if it is not present.
 89
 90    Returns:
 91        List of filepaths for the image data.
 92        List of filepaths for the label data.
 93    """
 94    if annotation_choice not in ("inclusive", "exclusive"):
 95        raise ValueError(f"'{annotation_choice}' is not a valid annotation choice.")
 96
 97    data_dir = get_sinus_data(path, download)
 98    raw_paths = natsorted(glob(os.path.join(data_dir, "Original", "*.JPG")))
 99
100    selection = f"{annotation_choice.capitalize()} Nuclei Selection"
101    suffix = "ins" if annotation_choice == "inclusive" else "ens"
102    annotation_paths = natsorted(glob(os.path.join(data_dir, selection, "Image *", f"*_annotation_{suffix}.json")))
103
104    label_dir = os.path.join(data_dir, "preprocessed_labels", annotation_choice)
105    os.makedirs(label_dir, exist_ok=True)
106    label_paths = []
107    for annotation_path in tqdm(annotation_paths, desc=f"Preprocessing {annotation_choice} SiNuS labels"):
108        image_name = Path(annotation_path).name.split("_annotation")[0]
109        label_path = os.path.join(label_dir, f"{image_name}.tif")
110        label_paths.append(label_path)
111        if os.path.exists(label_path):
112            continue
113
114        _create_instance_labels(annotation_path, label_path)
115
116    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
117    assert all(Path(raw_path).stem == Path(label_path).stem for raw_path, label_path in zip(raw_paths, label_paths))
118
119    return raw_paths, label_paths
120
121
122def get_sinus_dataset(
123    path: Union[os.PathLike, str],
124    patch_shape: Tuple[int, int],
125    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
126    resize_inputs: bool = False,
127    download: bool = False,
128    **kwargs,
129) -> Dataset:
130    """Get the SiNuS dataset for singular nucleus segmentation.
131
132    Args:
133        path: Filepath to a folder where the downloaded data will be saved.
134        patch_shape: The patch shape to use for training.
135        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
136            one expert, while the exclusive annotations contain nuclei selected by all experts.
137        resize_inputs: Whether to resize the inputs.
138        download: Whether to download the data if it is not present.
139        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
140
141    Returns:
142        The segmentation dataset.
143    """
144    raw_paths, label_paths = get_sinus_paths(path, annotation_choice, download)
145
146    if resize_inputs:
147        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
148        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
149            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
150        )
151
152    return torch_em.default_segmentation_dataset(
153        raw_paths=raw_paths,
154        raw_key=None,
155        label_paths=label_paths,
156        label_key=None,
157        is_seg_dataset=False,
158        patch_shape=patch_shape,
159        ndim=2,
160        with_channels=True,
161        **kwargs,
162    )
163
164
165def get_sinus_loader(
166    path: Union[os.PathLike, str],
167    batch_size: int,
168    patch_shape: Tuple[int, int],
169    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
170    resize_inputs: bool = False,
171    download: bool = False,
172    **kwargs,
173) -> DataLoader:
174    """Get the SiNuS dataloader for singular nucleus segmentation.
175
176    Args:
177        path: Filepath to a folder where the downloaded data will be saved.
178        batch_size: The batch size for training.
179        patch_shape: The patch shape to use for training.
180        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
181            one expert, while the exclusive annotations contain nuclei selected by all experts.
182        resize_inputs: Whether to resize the inputs.
183        download: Whether to download the data if it is not present.
184        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
185
186    Returns:
187        The DataLoader.
188    """
189    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
190    dataset = get_sinus_dataset(
191        path=path,
192        patch_shape=patch_shape,
193        annotation_choice=annotation_choice,
194        resize_inputs=resize_inputs,
195        download=download,
196        **ds_kwargs,
197    )
198    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://data.mendeley.com/public-api/zip/gtjrgwbntc/download/2'
CHECKSUM = 'aecd1399192ee511ba29f6c23e6f858b4e6a8328028c1ae91f9ee5a826728c5c'
def get_sinus_data(path: Union[os.PathLike, str], download: bool = False) -> str:
36def get_sinus_data(path: Union[os.PathLike, str], download: bool = False) -> str:
37    """Download the SiNuS dataset.
38
39    Args:
40        path: Filepath to a folder where the downloaded data will be saved.
41        download: Whether to download the data if it is not present.
42
43    Returns:
44        Filepath where the dataset is downloaded.
45    """
46    data_dir = os.path.join(path, "SiNuS A Comprehensive Dataset for Singular Nuclei", "SiNuS")
47    if os.path.exists(data_dir):
48        return data_dir
49
50    os.makedirs(path, exist_ok=True)
51
52    zip_path = os.path.join(path, "sinus.zip")
53    util.download_source(path=zip_path, url=URL, download=download, checksum=CHECKSUM)
54    util.unzip(zip_path=zip_path, dst=path)
55
56    return data_dir

Download the SiNuS 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:

Filepath where the dataset is downloaded.

def get_sinus_paths( path: Union[os.PathLike, str], annotation_choice: Literal['inclusive', 'exclusive'] = 'inclusive', download: bool = False) -> Tuple[List[str], List[str]]:
 78def get_sinus_paths(
 79    path: Union[os.PathLike, str],
 80    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
 81    download: bool = False,
 82) -> Tuple[List[str], List[str]]:
 83    """Get paths to the SiNuS data.
 84
 85    Args:
 86        path: Filepath to a folder where the downloaded data will be saved.
 87        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
 88            one expert, while the exclusive annotations contain nuclei selected by all experts.
 89        download: Whether to download the data if it is not present.
 90
 91    Returns:
 92        List of filepaths for the image data.
 93        List of filepaths for the label data.
 94    """
 95    if annotation_choice not in ("inclusive", "exclusive"):
 96        raise ValueError(f"'{annotation_choice}' is not a valid annotation choice.")
 97
 98    data_dir = get_sinus_data(path, download)
 99    raw_paths = natsorted(glob(os.path.join(data_dir, "Original", "*.JPG")))
100
101    selection = f"{annotation_choice.capitalize()} Nuclei Selection"
102    suffix = "ins" if annotation_choice == "inclusive" else "ens"
103    annotation_paths = natsorted(glob(os.path.join(data_dir, selection, "Image *", f"*_annotation_{suffix}.json")))
104
105    label_dir = os.path.join(data_dir, "preprocessed_labels", annotation_choice)
106    os.makedirs(label_dir, exist_ok=True)
107    label_paths = []
108    for annotation_path in tqdm(annotation_paths, desc=f"Preprocessing {annotation_choice} SiNuS labels"):
109        image_name = Path(annotation_path).name.split("_annotation")[0]
110        label_path = os.path.join(label_dir, f"{image_name}.tif")
111        label_paths.append(label_path)
112        if os.path.exists(label_path):
113            continue
114
115        _create_instance_labels(annotation_path, label_path)
116
117    assert len(raw_paths) == len(label_paths) and len(raw_paths) > 0
118    assert all(Path(raw_path).stem == Path(label_path).stem for raw_path, label_path in zip(raw_paths, label_paths))
119
120    return raw_paths, label_paths

Get paths to the SiNuS data.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least one expert, while the exclusive annotations contain nuclei selected by all experts.
  • 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_sinus_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int], annotation_choice: Literal['inclusive', 'exclusive'] = 'inclusive', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
123def get_sinus_dataset(
124    path: Union[os.PathLike, str],
125    patch_shape: Tuple[int, int],
126    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
127    resize_inputs: bool = False,
128    download: bool = False,
129    **kwargs,
130) -> Dataset:
131    """Get the SiNuS dataset for singular nucleus segmentation.
132
133    Args:
134        path: Filepath to a folder where the downloaded data will be saved.
135        patch_shape: The patch shape to use for training.
136        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
137            one expert, while the exclusive annotations contain nuclei selected by all experts.
138        resize_inputs: Whether to resize the inputs.
139        download: Whether to download the data if it is not present.
140        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
141
142    Returns:
143        The segmentation dataset.
144    """
145    raw_paths, label_paths = get_sinus_paths(path, annotation_choice, download)
146
147    if resize_inputs:
148        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": True}
149        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
150            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
151        )
152
153    return torch_em.default_segmentation_dataset(
154        raw_paths=raw_paths,
155        raw_key=None,
156        label_paths=label_paths,
157        label_key=None,
158        is_seg_dataset=False,
159        patch_shape=patch_shape,
160        ndim=2,
161        with_channels=True,
162        **kwargs,
163    )

Get the SiNuS dataset for singular nucleus segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • patch_shape: The patch shape to use for training.
  • annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least one expert, while the exclusive annotations contain nuclei selected by all experts.
  • 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_sinus_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, int], annotation_choice: Literal['inclusive', 'exclusive'] = 'inclusive', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
166def get_sinus_loader(
167    path: Union[os.PathLike, str],
168    batch_size: int,
169    patch_shape: Tuple[int, int],
170    annotation_choice: Literal["inclusive", "exclusive"] = "inclusive",
171    resize_inputs: bool = False,
172    download: bool = False,
173    **kwargs,
174) -> DataLoader:
175    """Get the SiNuS dataloader for singular nucleus segmentation.
176
177    Args:
178        path: Filepath to a folder where the downloaded data will be saved.
179        batch_size: The batch size for training.
180        patch_shape: The patch shape to use for training.
181        annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least
182            one expert, while the exclusive annotations contain nuclei selected by all experts.
183        resize_inputs: Whether to resize the inputs.
184        download: Whether to download the data if it is not present.
185        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
186
187    Returns:
188        The DataLoader.
189    """
190    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
191    dataset = get_sinus_dataset(
192        path=path,
193        patch_shape=patch_shape,
194        annotation_choice=annotation_choice,
195        resize_inputs=resize_inputs,
196        download=download,
197        **ds_kwargs,
198    )
199    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the SiNuS dataloader for singular nucleus 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.
  • annotation_choice: The annotation selection. The inclusive annotations contain nuclei selected by at least one expert, while the exclusive annotations contain nuclei selected by all experts.
  • 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.