torch_em.data.datasets.light_microscopy.bbbc027

The BBBC027 dataset contains synthetic 3D fluorescence microscopy images of colon tissue with foreground segmentation ground truth for the cell nuclei.

The images were generated with a virtual microscope (CytoPacq) imitating a Zeiss S100 confocal microscope. The dataset contains 30 images of shape (129, 1030, 1300) voxels (ZYX), each provided in a low SNR and a high SNR variant. The ground truth is a binary foreground / background mask of all nuclei inside the tissue region (label 1 for nuclei, 0 for background); individual nuclei are not separated into instances.

The images and ground truth are distributed in the ICS format and are converted to HDF5 files (with the keys 'raw' and 'labels') when the data is prepared.

The dataset is located at https://bbbc.broadinstitute.org/BBBC027. This dataset is from the publication https://doi.org/10.1007/978-3-642-21596-4_4. Please cite it if you use this dataset in your research.

  1"""The BBBC027 dataset contains synthetic 3D fluorescence microscopy images of colon tissue
  2with foreground segmentation ground truth for the cell nuclei.
  3
  4The images were generated with a virtual microscope (CytoPacq) imitating a Zeiss S100 confocal
  5microscope. The dataset contains 30 images of shape (129, 1030, 1300) voxels (ZYX), each provided
  6in a low SNR and a high SNR variant. The ground truth is a binary foreground / background mask
  7of all nuclei inside the tissue region (label 1 for nuclei, 0 for background); individual nuclei are
  8not separated into instances.
  9
 10The images and ground truth are distributed in the ICS format and are converted to HDF5 files
 11(with the keys 'raw' and 'labels') when the data is prepared.
 12
 13The dataset is located at https://bbbc.broadinstitute.org/BBBC027.
 14This dataset is from the publication https://doi.org/10.1007/978-3-642-21596-4_4.
 15Please cite it if you use this dataset in your research.
 16"""
 17
 18import os
 19import shutil
 20import zlib
 21from glob import glob
 22from natsort import natsorted
 23from typing import List, Literal, Tuple, Union
 24
 25import numpy as np
 26
 27from torch.utils.data import Dataset, DataLoader
 28
 29import torch_em
 30
 31from .. import util
 32
 33
 34URL = "https://data.broadinstitute.org/bbbc/BBBC027/BBBC027_{snr}SNR_{kind}_part{part}.zip"
 35CHECKSUMS = {
 36    "low": {
 37        "images": [
 38            "bf537d5e63c63f3b86e31d0dfe1b888a5bac8df78d9562f7c4fb738d9ad4e5d1",
 39            "4e581c373a2656d50265345a0cdaa1ec32926028c56a46ac9a6cc33bd8fca295",
 40            "b0ca1814101ea7c8f71802c762195f3e69a639bf4ba24065d4efbb780ea94d63",
 41        ],
 42        "foreground": [
 43            "1530cd10f40479045e54c6a56e1ba0081f2b0383b7008376abcb9a774ebdf33e",
 44            "96a59ff604e515c084ecf8a8c45ac96685ff7259cb210759a06a8a4d4dff9cd3",
 45            "073c53019799ee40f9dfe2d5f93b841f8e0728f3c8419ae6f211b991ad2f3c84",
 46        ],
 47    },
 48    "high": {
 49        "images": [
 50            "f07d6aa56b990dfe564380e004e0b414336e610bfd8e93d3bfff1ea15843a1f0",
 51            "0ad770285041e53cc6a5950b22b82d23c50a2a4e053e31dcb9653b300fd03ab5",
 52            "1454e75882d7de39898e54cb02d6502d051cf2a75e7229031d30be6910090eb2",
 53        ],
 54        "foreground": [
 55            "0dd7c5e03fa216e71add8983e4c4a05c21b4208b800055d3c41596188308af5e",
 56            "d04115940d17d09918a0b2116a3389ced753c549ea792d0352e886fa7eeaab0c",
 57            "71dff9e5cc2fc202418db85084104ef729d36c61b5c79b8085c63cc186bacdd3",
 58        ],
 59    },
 60}
 61
 62
 63def _read_ics(ics_path):
 64    """Read an ICS 2.0 file (header and data in the same file, optionally gzip compressed).
 65    """
 66    with open(ics_path, "rb") as f:
 67        data = f.read()
 68
 69    # The first two bytes define the field and the line separator of the header.
 70    field_sep, line_sep = data[0:1], data[1:2]
 71    end_marker = b"end" + field_sep + line_sep
 72    header_end = data.index(end_marker)
 73
 74    header = {}
 75    for line in data[2:header_end].split(line_sep):
 76        fields = line.split(field_sep)
 77        if fields[0] in (b"layout", b"representation") and len(fields) > 2:
 78            header[(fields[0], fields[1])] = fields[2:]
 79
 80    order = [dim.decode() for dim in header[(b"layout", b"order")]]
 81    sizes = [int(size) for size in header[(b"layout", b"sizes")]]
 82    assert order[0] == "bits"
 83    bits, sizes, order = sizes[0], sizes[1:], order[1:]
 84
 85    fmt = header[(b"representation", b"format")][0]
 86    sign = header[(b"representation", b"sign")][0]
 87    if fmt == b"integer":
 88        dtype = np.dtype(f"{'u' if sign == b'unsigned' else 'i'}{bits // 8}")
 89    else:
 90        dtype = np.dtype(f"f{bits // 8}")
 91    byte_order = header[(b"representation", b"byte_order")]
 92    dtype = dtype.newbyteorder("<" if byte_order[0] == b"1" else ">")
 93
 94    raw = data[header_end + len(end_marker):]
 95    if header.get((b"representation", b"compression"), [b"uncompressed"])[0] == b"gzip":
 96        raw = zlib.decompress(raw, 16 + zlib.MAX_WBITS)
 97
 98    # The ICS layout lists the fastest varying axis first, so we reverse to get a C-ordered array.
 99    volume = np.frombuffer(raw, dtype=dtype).reshape(sizes[::-1])
100    # The volumes in BBBC027 are stored in the order x, y, z, i.e. we get a zyx array after reversing.
101    assert order[::-1] == ["z", "y", "x"], f"Unexpected axis order in {ics_path}: {order}"
102    return volume
103
104
105def _convert_to_h5(image_dir, label_dir, data_dir):
106    import h5py
107
108    image_paths = natsorted(glob(os.path.join(image_dir, "**", "image-final_*.ics"), recursive=True))
109    label_paths = natsorted(glob(os.path.join(label_dir, "**", "image-labels_*.ics"), recursive=True))
110    assert len(image_paths) == len(label_paths) and len(image_paths) > 0
111
112    for image_path, label_path in zip(image_paths, label_paths):
113        image_id = os.path.basename(image_path).replace("image-final_", "").replace(".ics", "")
114        assert os.path.basename(label_path) == f"image-labels_{image_id}.ics"
115
116        raw = _read_ics(image_path)
117        labels = _read_ics(label_path)
118        assert raw.shape == labels.shape, f"{raw.shape}, {labels.shape}"
119
120        with h5py.File(os.path.join(data_dir, f"image_{image_id}.h5"), "w") as f:
121            f.create_dataset("raw", data=raw, compression="gzip")
122            f.create_dataset("labels", data=labels, compression="gzip")
123
124
125def get_bbbc027_data(
126    path: Union[os.PathLike, str], snr: Literal["low", "high"] = "high", download: bool = False
127) -> str:
128    """Download the BBBC027 dataset.
129
130    Args:
131        path: Filepath to a folder where the downloaded data will be saved.
132        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
133        download: Whether to download the data if it is not present.
134
135    Returns:
136        Filepath where the data for the chosen SNR level is stored.
137    """
138    if snr not in ("low", "high"):
139        raise ValueError(f"'{snr}' is not a valid SNR level. Choose from 'low' or 'high'.")
140
141    data_dir = os.path.join(path, "BBBC027", f"{snr}SNR")
142    if os.path.exists(data_dir):
143        return data_dir
144
145    os.makedirs(path, exist_ok=True)
146
147    tmp_dir = os.path.join(path, "BBBC027", f"{snr}SNR_ics")
148    for kind in ("images", "foreground"):
149        for part in (1, 2, 3):
150            zip_path = os.path.join(path, f"BBBC027_{snr}SNR_{kind}_part{part}.zip")
151            url = URL.format(snr=snr, kind=kind, part=part)
152            util.download_source(zip_path, url, download, CHECKSUMS[snr][kind][part - 1])
153            util.unzip(zip_path, os.path.join(tmp_dir, kind))
154
155    os.makedirs(data_dir, exist_ok=True)
156    _convert_to_h5(os.path.join(tmp_dir, "images"), os.path.join(tmp_dir, "foreground"), data_dir)
157    shutil.rmtree(tmp_dir)
158
159    return data_dir
160
161
162def get_bbbc027_paths(
163    path: Union[os.PathLike, str], snr: Literal["low", "high"] = "high", download: bool = False
164) -> List[str]:
165    """Get paths to the BBBC027 data.
166
167    Args:
168        path: Filepath to a folder where the downloaded data will be saved.
169        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
170        download: Whether to download the data if it is not present.
171
172    Returns:
173        List of filepaths for the HDF5 files, which contain the image data (key 'raw') and the labels (key 'labels').
174    """
175    data_dir = get_bbbc027_data(path, snr, download)
176    volume_paths = natsorted(glob(os.path.join(data_dir, "image_*.h5")))
177    assert len(volume_paths) > 0
178    return volume_paths
179
180
181def get_bbbc027_dataset(
182    path: Union[os.PathLike, str],
183    patch_shape: Tuple[int, ...],
184    snr: Literal["low", "high"] = "high",
185    resize_inputs: bool = False,
186    download: bool = False,
187    **kwargs
188) -> Dataset:
189    """Get the BBBC027 dataset for nucleus foreground segmentation.
190
191    Args:
192        path: Filepath to a folder where the downloaded data will be saved.
193        patch_shape: The patch shape to use for training.
194        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
195        resize_inputs: Whether to resize the inputs to the patch shape.
196        download: Whether to download the data if it is not present.
197        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
198
199    Returns:
200        The segmentation dataset.
201    """
202    volume_paths = get_bbbc027_paths(path, snr, download)
203
204    if resize_inputs:
205        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
206        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
207            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
208        )
209
210    return torch_em.default_segmentation_dataset(
211        raw_paths=volume_paths,
212        raw_key="raw",
213        label_paths=volume_paths,
214        label_key="labels",
215        patch_shape=patch_shape,
216        is_seg_dataset=True,
217        **kwargs
218    )
219
220
221def get_bbbc027_loader(
222    path: Union[os.PathLike, str],
223    batch_size: int,
224    patch_shape: Tuple[int, ...],
225    snr: Literal["low", "high"] = "high",
226    resize_inputs: bool = False,
227    download: bool = False,
228    **kwargs
229) -> DataLoader:
230    """Get the BBBC027 dataloader for nucleus foreground segmentation.
231
232    Args:
233        path: Filepath to a folder where the downloaded data will be saved.
234        batch_size: The batch size for training.
235        patch_shape: The patch shape to use for training.
236        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
237        resize_inputs: Whether to resize the inputs to the patch shape.
238        download: Whether to download the data if it is not present.
239        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
240
241    Returns:
242        The DataLoader.
243    """
244    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
245    dataset = get_bbbc027_dataset(path, patch_shape, snr, resize_inputs, download, **ds_kwargs)
246    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)
URL = 'https://data.broadinstitute.org/bbbc/BBBC027/BBBC027_{snr}SNR_{kind}_part{part}.zip'
CHECKSUMS = {'low': {'images': ['bf537d5e63c63f3b86e31d0dfe1b888a5bac8df78d9562f7c4fb738d9ad4e5d1', '4e581c373a2656d50265345a0cdaa1ec32926028c56a46ac9a6cc33bd8fca295', 'b0ca1814101ea7c8f71802c762195f3e69a639bf4ba24065d4efbb780ea94d63'], 'foreground': ['1530cd10f40479045e54c6a56e1ba0081f2b0383b7008376abcb9a774ebdf33e', '96a59ff604e515c084ecf8a8c45ac96685ff7259cb210759a06a8a4d4dff9cd3', '073c53019799ee40f9dfe2d5f93b841f8e0728f3c8419ae6f211b991ad2f3c84']}, 'high': {'images': ['f07d6aa56b990dfe564380e004e0b414336e610bfd8e93d3bfff1ea15843a1f0', '0ad770285041e53cc6a5950b22b82d23c50a2a4e053e31dcb9653b300fd03ab5', '1454e75882d7de39898e54cb02d6502d051cf2a75e7229031d30be6910090eb2'], 'foreground': ['0dd7c5e03fa216e71add8983e4c4a05c21b4208b800055d3c41596188308af5e', 'd04115940d17d09918a0b2116a3389ced753c549ea792d0352e886fa7eeaab0c', '71dff9e5cc2fc202418db85084104ef729d36c61b5c79b8085c63cc186bacdd3']}}
def get_bbbc027_data( path: Union[os.PathLike, str], snr: Literal['low', 'high'] = 'high', download: bool = False) -> str:
126def get_bbbc027_data(
127    path: Union[os.PathLike, str], snr: Literal["low", "high"] = "high", download: bool = False
128) -> str:
129    """Download the BBBC027 dataset.
130
131    Args:
132        path: Filepath to a folder where the downloaded data will be saved.
133        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
134        download: Whether to download the data if it is not present.
135
136    Returns:
137        Filepath where the data for the chosen SNR level is stored.
138    """
139    if snr not in ("low", "high"):
140        raise ValueError(f"'{snr}' is not a valid SNR level. Choose from 'low' or 'high'.")
141
142    data_dir = os.path.join(path, "BBBC027", f"{snr}SNR")
143    if os.path.exists(data_dir):
144        return data_dir
145
146    os.makedirs(path, exist_ok=True)
147
148    tmp_dir = os.path.join(path, "BBBC027", f"{snr}SNR_ics")
149    for kind in ("images", "foreground"):
150        for part in (1, 2, 3):
151            zip_path = os.path.join(path, f"BBBC027_{snr}SNR_{kind}_part{part}.zip")
152            url = URL.format(snr=snr, kind=kind, part=part)
153            util.download_source(zip_path, url, download, CHECKSUMS[snr][kind][part - 1])
154            util.unzip(zip_path, os.path.join(tmp_dir, kind))
155
156    os.makedirs(data_dir, exist_ok=True)
157    _convert_to_h5(os.path.join(tmp_dir, "images"), os.path.join(tmp_dir, "foreground"), data_dir)
158    shutil.rmtree(tmp_dir)
159
160    return data_dir

Download the BBBC027 dataset.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
  • download: Whether to download the data if it is not present.
Returns:

Filepath where the data for the chosen SNR level is stored.

def get_bbbc027_paths( path: Union[os.PathLike, str], snr: Literal['low', 'high'] = 'high', download: bool = False) -> List[str]:
163def get_bbbc027_paths(
164    path: Union[os.PathLike, str], snr: Literal["low", "high"] = "high", download: bool = False
165) -> List[str]:
166    """Get paths to the BBBC027 data.
167
168    Args:
169        path: Filepath to a folder where the downloaded data will be saved.
170        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
171        download: Whether to download the data if it is not present.
172
173    Returns:
174        List of filepaths for the HDF5 files, which contain the image data (key 'raw') and the labels (key 'labels').
175    """
176    data_dir = get_bbbc027_data(path, snr, download)
177    volume_paths = natsorted(glob(os.path.join(data_dir, "image_*.h5")))
178    assert len(volume_paths) > 0
179    return volume_paths

Get paths to the BBBC027 data.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
  • download: Whether to download the data if it is not present.
Returns:

List of filepaths for the HDF5 files, which contain the image data (key 'raw') and the labels (key 'labels').

def get_bbbc027_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, ...], snr: Literal['low', 'high'] = 'high', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
182def get_bbbc027_dataset(
183    path: Union[os.PathLike, str],
184    patch_shape: Tuple[int, ...],
185    snr: Literal["low", "high"] = "high",
186    resize_inputs: bool = False,
187    download: bool = False,
188    **kwargs
189) -> Dataset:
190    """Get the BBBC027 dataset for nucleus foreground segmentation.
191
192    Args:
193        path: Filepath to a folder where the downloaded data will be saved.
194        patch_shape: The patch shape to use for training.
195        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
196        resize_inputs: Whether to resize the inputs to the patch shape.
197        download: Whether to download the data if it is not present.
198        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
199
200    Returns:
201        The segmentation dataset.
202    """
203    volume_paths = get_bbbc027_paths(path, snr, download)
204
205    if resize_inputs:
206        resize_kwargs = {"patch_shape": patch_shape, "is_rgb": False}
207        kwargs, patch_shape = util.update_kwargs_for_resize_trafo(
208            kwargs=kwargs, patch_shape=patch_shape, resize_inputs=resize_inputs, resize_kwargs=resize_kwargs
209        )
210
211    return torch_em.default_segmentation_dataset(
212        raw_paths=volume_paths,
213        raw_key="raw",
214        label_paths=volume_paths,
215        label_key="labels",
216        patch_shape=patch_shape,
217        is_seg_dataset=True,
218        **kwargs
219    )

Get the BBBC027 dataset for nucleus foreground segmentation.

Arguments:
  • path: Filepath to a folder where the downloaded data will be saved.
  • patch_shape: The patch shape to use for training.
  • snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
  • resize_inputs: Whether to resize the inputs to the 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.

def get_bbbc027_loader( path: Union[os.PathLike, str], batch_size: int, patch_shape: Tuple[int, ...], snr: Literal['low', 'high'] = 'high', resize_inputs: bool = False, download: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
222def get_bbbc027_loader(
223    path: Union[os.PathLike, str],
224    batch_size: int,
225    patch_shape: Tuple[int, ...],
226    snr: Literal["low", "high"] = "high",
227    resize_inputs: bool = False,
228    download: bool = False,
229    **kwargs
230) -> DataLoader:
231    """Get the BBBC027 dataloader for nucleus foreground segmentation.
232
233    Args:
234        path: Filepath to a folder where the downloaded data will be saved.
235        batch_size: The batch size for training.
236        patch_shape: The patch shape to use for training.
237        snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
238        resize_inputs: Whether to resize the inputs to the patch shape.
239        download: Whether to download the data if it is not present.
240        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
241
242    Returns:
243        The DataLoader.
244    """
245    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
246    dataset = get_bbbc027_dataset(path, patch_shape, snr, resize_inputs, download, **ds_kwargs)
247    return torch_em.get_data_loader(dataset, batch_size, **loader_kwargs)

Get the BBBC027 dataloader for nucleus foreground 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.
  • snr: The signal-to-noise ratio of the images. Either 'low' or 'high'.
  • resize_inputs: Whether to resize the inputs to the patch shape.
  • 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.