torch_em.data.datasets.electron_microscopy.nisb

NISB is a large-scale synthetic benchmark for neuron instance segmentation in connectomics.

It comprises 9 settings with varying difficulty and imaging conditions, each providing 5 training cubes, 1 validation cube, and 1 test cube. The train_100 setting is an exception with 100 training cubes for scaling analysis. Cubes are 27µm side length at 9x9x20 nm voxel size (liconn: 9x9x12 nm). The multichannel setting stores 8-channel embeddings instead of a single grayscale image.

Data is streamed directly from S3 via s3fs and written to local zarr v3 stores (chunk 64^3, shard 512^3, zstd compression) with (z, y, x) axis order. The source is zarr v2 with (x, y, z) axis order; spatial axes are transposed and the trailing singleton channel dim on img is squeezed during the write. Requires s3fs (pip install s3fs).

The data is described in https://doi.org/10.17617/1.r2mm-1h33. Please cite it if you use this dataset for a publication.

  1"""NISB is a large-scale synthetic benchmark for neuron instance segmentation in connectomics.
  2
  3It comprises 9 settings with varying difficulty and imaging conditions, each providing 5 training
  4cubes, 1 validation cube, and 1 test cube. The train_100 setting is an exception with 100 training
  5cubes for scaling analysis. Cubes are 27µm side length at 9x9x20 nm voxel size (liconn: 9x9x12 nm).
  6The multichannel setting stores 8-channel embeddings instead of a single grayscale image.
  7
  8Data is streamed directly from S3 via s3fs and written to local zarr v3 stores (chunk 64^3,
  9shard 512^3, zstd compression) with (z, y, x) axis order. The source is zarr v2 with (x, y, z)
 10axis order; spatial axes are transposed and the trailing singleton channel dim on img is squeezed
 11during the write. Requires s3fs (pip install s3fs).
 12
 13The data is described in https://doi.org/10.17617/1.r2mm-1h33.
 14Please cite it if you use this dataset for a publication.
 15"""
 16
 17import os
 18import shutil
 19import warnings
 20from glob import glob
 21from typing import List, Literal, Optional, Tuple, Union
 22
 23import numpy as np
 24from tqdm import tqdm
 25from torch.utils.data import DataLoader, Dataset
 26
 27import torch_em
 28from .. import util
 29
 30
 31NISB_S3_ENDPOINT = "https://s3.nexus.mpcdf.mpg.de:443"
 32NISB_S3_BUCKET = "nisb"
 33
 34NISB_SETTINGS = [
 35    "base", "train_100", "slice_perturbed", "pos_guidance", "neg_guidance",
 36    "no_touch_thick", "touching_thin", "liconn", "multichannel",
 37]
 38
 39NISB_CHUNK_SHAPE = (64, 64, 64)
 40NISB_SHARD_SHAPE = (512, 512, 512)
 41
 42
 43# The val and test cubes are stored under these seed ids on S3.
 44NISB_SEED_IDS = {"val": (100,), "test": (101,)}
 45
 46
 47def _nisb_seed_ids(setting: str, split: str) -> Tuple[int, ...]:
 48    if split in NISB_SEED_IDS:
 49        return NISB_SEED_IDS[split]
 50    return tuple(range(100 if setting == "train_100" else 5))
 51
 52
 53def _nisb_zarr_complete(zarr_path: str) -> bool:
 54    return (
 55        os.path.isfile(os.path.join(zarr_path, "zarr.json"))
 56        and os.path.isdir(os.path.join(zarr_path, "img"))
 57        and os.path.isdir(os.path.join(zarr_path, "seg"))
 58    )
 59
 60
 61def _nisb_create_v3_array(root, name, shape, dtype, is_label):
 62    from zarr.codecs import BloscCodec
 63    shuffle = "bitshuffle" if (np.issubdtype(np.dtype(dtype), np.integer) and is_label) else "shuffle"
 64    chunks = NISB_CHUNK_SHAPE + tuple(shape[3:])
 65    shards = NISB_SHARD_SHAPE + tuple(shape[3:])
 66    return root.create_array(
 67        name, shape=shape, chunks=chunks, shards=shards, dtype=dtype,
 68        compressors=BloscCodec(cname="zstd", clevel=6, shuffle=shuffle),
 69    )
 70
 71
 72def _nisb_write_cube_v3(src, v3_path: str) -> None:
 73    """Stream a NISB cube from a zarr v2 source to a local zarr v3 store.
 74
 75    Transposes axes from (x, y, z) to (z, y, x) and squeezes the trailing singleton
 76    channel dimension on the image array.
 77    """
 78    import zarr
 79
 80    img_v2 = src["img"]
 81    seg_v2 = src["seg"]
 82
 83    squeeze_img = img_v2.ndim == 4 and img_v2.shape[-1] == 1
 84    if squeeze_img:
 85        img_shape_v3 = (img_v2.shape[2], img_v2.shape[1], img_v2.shape[0])
 86    else:
 87        img_shape_v3 = (img_v2.shape[2], img_v2.shape[1], img_v2.shape[0], img_v2.shape[3])
 88    seg_shape_v3 = (seg_v2.shape[2], seg_v2.shape[1], seg_v2.shape[0])
 89
 90    tmp_path = v3_path + ".tmp"
 91    if os.path.exists(tmp_path):
 92        shutil.rmtree(tmp_path)
 93
 94    root = zarr.open_group(tmp_path, mode="w", zarr_format=3)
 95    img_v3 = _nisb_create_v3_array(root, "img", img_shape_v3, np.dtype("uint8"), False)
 96    seg_v3 = _nisb_create_v3_array(root, "seg", seg_shape_v3, np.dtype("uint16"), True)
 97
 98    Z, Y, X = seg_shape_v3
 99    sz, sy, sx = NISB_SHARD_SHAPE
100    for z0 in range(0, Z, sz):
101        for y0 in range(0, Y, sy):
102            for x0 in range(0, X, sx):
103                z1, y1, x1 = min(z0 + sz, Z), min(y0 + sy, Y), min(x0 + sx, X)
104                block_img = np.asarray(img_v2[x0:x1, y0:y1, z0:z1])
105                if squeeze_img:
106                    block_img = block_img[..., 0]
107                img_v3[z0:z1, y0:y1, x0:x1] = np.moveaxis(block_img, [0, 2], [2, 0])
108                block_seg = np.asarray(seg_v2[x0:x1, y0:y1, z0:z1])
109                seg_v3[z0:z1, y0:y1, x0:x1] = block_seg.transpose(2, 1, 0)
110
111    shutil.move(tmp_path, v3_path)
112
113
114def _nisb_open_remote(setting: str, split: str, seed_idx: int):
115    """Open a NISB seed cube from S3 as a zarr v2 group via s3fs."""
116    try:
117        import s3fs
118    except ImportError:
119        raise ImportError("The 's3fs' package is required to download NISB data. Install it with: pip install s3fs")
120    import zarr
121
122    fs = s3fs.S3FileSystem(anon=True, endpoint_url=NISB_S3_ENDPOINT)
123    s3_path = f"{NISB_S3_BUCKET}/{setting}/{split}/seed{seed_idx}/data.zarr"
124    with warnings.catch_warnings():
125        warnings.filterwarnings("ignore", message=".*asynchronous.*")
126        store = zarr.storage.FsspecStore(fs=fs, path=s3_path)
127        return zarr.open_group(store, mode="r", zarr_format=2)
128
129
130def get_nisb_data(path: Union[os.PathLike, str], setting: str, split: str, download: bool) -> str:
131    """Stream and cache NISB data for a given setting and split from S3.
132
133    Data is read from S3 via s3fs and written to local zarr v3 stores with (z, y, x) axis
134    order, sharding (chunk 64^3, shard 512^3), and zstd compression. Already-cached seeds
135    are skipped on subsequent calls.
136
137    Args:
138        path: Filepath to a folder where the cached data will be saved.
139        setting: The NISB setting. One of NISB_SETTINGS.
140        split: The data split, one of 'train', 'val', 'test'.
141        download: Whether to stream and cache the data if it is not present.
142
143    Returns:
144        The filepath to the split directory containing seed subdirectories.
145    """
146    assert setting in NISB_SETTINGS, f"Invalid setting '{setting}'. Choose from {NISB_SETTINGS}."
147    assert split in ("train", "val", "test"), f"Invalid split '{split}'. Choose 'train', 'val', or 'test'."
148
149    split_dir = os.path.join(str(path), setting, split)
150
151    for i in tqdm(_nisb_seed_ids(setting, split), desc=f"NISB {setting}/{split}", leave=False):
152        seed_dir = os.path.join(split_dir, f"seed{i}")
153        zarr_path = os.path.join(seed_dir, "data.zarr")
154
155        if _nisb_zarr_complete(zarr_path):
156            continue
157
158        if not download:
159            raise RuntimeError(
160                f"No NISB data for setting '{setting}' split '{split}' seed {i} at '{zarr_path}'. "
161                "Set download=True to stream it from S3."
162            )
163
164        os.makedirs(seed_dir, exist_ok=True)
165        print(f"Streaming NISB {setting}/{split}/seed{i} from S3 ...")
166        src = _nisb_open_remote(setting, split, i)
167        _nisb_write_cube_v3(src, zarr_path)
168
169    return split_dir
170
171
172def get_nisb_paths(
173    path: Union[os.PathLike, str],
174    setting: str = "base",
175    split: Literal["train", "val", "test"] = "train",
176    download: bool = False,
177) -> List[str]:
178    """Get paths to NISB zarr stores for a given setting and split.
179
180    Args:
181        path: Filepath to a folder where the cached data is saved.
182        setting: The NISB setting. One of NISB_SETTINGS.
183        split: The data split, one of 'train', 'val', 'test'.
184        download: Whether to stream and cache the data if it is not present.
185
186    Returns:
187        Sorted list of filepaths to the zarr stores, one per cube/seed.
188    """
189    split_dir = get_nisb_data(path, setting, split, download)
190    paths = sorted(glob(os.path.join(split_dir, "seed*", "data.zarr")))
191    if not paths:
192        raise RuntimeError(
193            f"No zarr files found in '{split_dir}'. The download may have failed or the directory is empty."
194        )
195    return paths
196
197
198def get_nisb_dataset(
199    path: Union[os.PathLike, str],
200    patch_shape: Tuple[int, int, int],
201    setting: str = "base",
202    split: Literal["train", "val", "test"] = "train",
203    download: bool = False,
204    offsets: Optional[List[List[int]]] = None,
205    boundaries: bool = False,
206    **kwargs,
207) -> Dataset:
208    """Get the NISB dataset for neuron instance segmentation in EM.
209
210    NISB provides 9 settings of varying difficulty, each with multiple cubes at 27µm side length.
211    Image data is stored under the zarr key 'img' with shape (z, y, x) and segmentation under 'seg'.
212    The multichannel setting stores 8-channel data with shape (z, y, x, 8).
213
214    Args:
215        path: Filepath to a folder where the cached data will be saved.
216        patch_shape: The patch shape to use for training.
217        setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
218        split: The data split, one of 'train', 'val', 'test'.
219        download: Whether to stream and cache the data if it is not present.
220            Requires s3fs (pip install s3fs).
221        offsets: Offset values for affinity computation used as target.
222        boundaries: Whether to compute boundaries as the target.
223        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
224
225    Returns:
226        The segmentation dataset.
227    """
228    assert len(patch_shape) == 3
229
230    paths = get_nisb_paths(path, setting, split, download)
231
232    kwargs = util.update_kwargs(kwargs, "is_seg_dataset", True)
233    kwargs, _ = util.add_instance_label_transform(
234        kwargs, add_binary_target=False, boundaries=boundaries, offsets=offsets
235    )
236
237    return torch_em.default_segmentation_dataset(
238        raw_paths=paths,
239        raw_key="img",
240        label_paths=paths,
241        label_key="seg",
242        patch_shape=patch_shape,
243        **kwargs,
244    )
245
246
247def get_nisb_loader(
248    path: Union[os.PathLike, str],
249    patch_shape: Tuple[int, int, int],
250    batch_size: int,
251    setting: str = "base",
252    split: Literal["train", "val", "test"] = "train",
253    download: bool = False,
254    offsets: Optional[List[List[int]]] = None,
255    boundaries: bool = False,
256    **kwargs,
257) -> DataLoader:
258    """Get the DataLoader for neuron instance segmentation in the NISB dataset.
259
260    Args:
261        path: Filepath to a folder where the cached data will be saved.
262        patch_shape: The patch shape to use for training.
263        batch_size: The batch size for training.
264        setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
265        split: The data split, one of 'train', 'val', 'test'.
266        download: Whether to stream and cache the data if it is not present.
267            Requires s3fs (pip install s3fs).
268        offsets: Offset values for affinity computation used as target.
269        boundaries: Whether to compute boundaries as the target.
270        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
271
272    Returns:
273        The DataLoader.
274    """
275    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
276    ds = get_nisb_dataset(
277        path=path,
278        patch_shape=patch_shape,
279        setting=setting,
280        split=split,
281        download=download,
282        offsets=offsets,
283        boundaries=boundaries,
284        **ds_kwargs,
285    )
286    return torch_em.get_data_loader(ds, batch_size=batch_size, **loader_kwargs)
NISB_S3_ENDPOINT = 'https://s3.nexus.mpcdf.mpg.de:443'
NISB_S3_BUCKET = 'nisb'
NISB_SETTINGS = ['base', 'train_100', 'slice_perturbed', 'pos_guidance', 'neg_guidance', 'no_touch_thick', 'touching_thin', 'liconn', 'multichannel']
NISB_CHUNK_SHAPE = (64, 64, 64)
NISB_SHARD_SHAPE = (512, 512, 512)
NISB_SEED_IDS = {'val': (100,), 'test': (101,)}
def get_nisb_data( path: Union[os.PathLike, str], setting: str, split: str, download: bool) -> str:
131def get_nisb_data(path: Union[os.PathLike, str], setting: str, split: str, download: bool) -> str:
132    """Stream and cache NISB data for a given setting and split from S3.
133
134    Data is read from S3 via s3fs and written to local zarr v3 stores with (z, y, x) axis
135    order, sharding (chunk 64^3, shard 512^3), and zstd compression. Already-cached seeds
136    are skipped on subsequent calls.
137
138    Args:
139        path: Filepath to a folder where the cached data will be saved.
140        setting: The NISB setting. One of NISB_SETTINGS.
141        split: The data split, one of 'train', 'val', 'test'.
142        download: Whether to stream and cache the data if it is not present.
143
144    Returns:
145        The filepath to the split directory containing seed subdirectories.
146    """
147    assert setting in NISB_SETTINGS, f"Invalid setting '{setting}'. Choose from {NISB_SETTINGS}."
148    assert split in ("train", "val", "test"), f"Invalid split '{split}'. Choose 'train', 'val', or 'test'."
149
150    split_dir = os.path.join(str(path), setting, split)
151
152    for i in tqdm(_nisb_seed_ids(setting, split), desc=f"NISB {setting}/{split}", leave=False):
153        seed_dir = os.path.join(split_dir, f"seed{i}")
154        zarr_path = os.path.join(seed_dir, "data.zarr")
155
156        if _nisb_zarr_complete(zarr_path):
157            continue
158
159        if not download:
160            raise RuntimeError(
161                f"No NISB data for setting '{setting}' split '{split}' seed {i} at '{zarr_path}'. "
162                "Set download=True to stream it from S3."
163            )
164
165        os.makedirs(seed_dir, exist_ok=True)
166        print(f"Streaming NISB {setting}/{split}/seed{i} from S3 ...")
167        src = _nisb_open_remote(setting, split, i)
168        _nisb_write_cube_v3(src, zarr_path)
169
170    return split_dir

Stream and cache NISB data for a given setting and split from S3.

Data is read from S3 via s3fs and written to local zarr v3 stores with (z, y, x) axis order, sharding (chunk 64^3, shard 512^3), and zstd compression. Already-cached seeds are skipped on subsequent calls.

Arguments:
  • path: Filepath to a folder where the cached data will be saved.
  • setting: The NISB setting. One of NISB_SETTINGS.
  • split: The data split, one of 'train', 'val', 'test'.
  • download: Whether to stream and cache the data if it is not present.
Returns:

The filepath to the split directory containing seed subdirectories.

def get_nisb_paths( path: Union[os.PathLike, str], setting: str = 'base', split: Literal['train', 'val', 'test'] = 'train', download: bool = False) -> List[str]:
173def get_nisb_paths(
174    path: Union[os.PathLike, str],
175    setting: str = "base",
176    split: Literal["train", "val", "test"] = "train",
177    download: bool = False,
178) -> List[str]:
179    """Get paths to NISB zarr stores for a given setting and split.
180
181    Args:
182        path: Filepath to a folder where the cached data is saved.
183        setting: The NISB setting. One of NISB_SETTINGS.
184        split: The data split, one of 'train', 'val', 'test'.
185        download: Whether to stream and cache the data if it is not present.
186
187    Returns:
188        Sorted list of filepaths to the zarr stores, one per cube/seed.
189    """
190    split_dir = get_nisb_data(path, setting, split, download)
191    paths = sorted(glob(os.path.join(split_dir, "seed*", "data.zarr")))
192    if not paths:
193        raise RuntimeError(
194            f"No zarr files found in '{split_dir}'. The download may have failed or the directory is empty."
195        )
196    return paths

Get paths to NISB zarr stores for a given setting and split.

Arguments:
  • path: Filepath to a folder where the cached data is saved.
  • setting: The NISB setting. One of NISB_SETTINGS.
  • split: The data split, one of 'train', 'val', 'test'.
  • download: Whether to stream and cache the data if it is not present.
Returns:

Sorted list of filepaths to the zarr stores, one per cube/seed.

def get_nisb_dataset( path: Union[os.PathLike, str], patch_shape: Tuple[int, int, int], setting: str = 'base', split: Literal['train', 'val', 'test'] = 'train', download: bool = False, offsets: Optional[List[List[int]]] = None, boundaries: bool = False, **kwargs) -> torch.utils.data.dataset.Dataset:
199def get_nisb_dataset(
200    path: Union[os.PathLike, str],
201    patch_shape: Tuple[int, int, int],
202    setting: str = "base",
203    split: Literal["train", "val", "test"] = "train",
204    download: bool = False,
205    offsets: Optional[List[List[int]]] = None,
206    boundaries: bool = False,
207    **kwargs,
208) -> Dataset:
209    """Get the NISB dataset for neuron instance segmentation in EM.
210
211    NISB provides 9 settings of varying difficulty, each with multiple cubes at 27µm side length.
212    Image data is stored under the zarr key 'img' with shape (z, y, x) and segmentation under 'seg'.
213    The multichannel setting stores 8-channel data with shape (z, y, x, 8).
214
215    Args:
216        path: Filepath to a folder where the cached data will be saved.
217        patch_shape: The patch shape to use for training.
218        setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
219        split: The data split, one of 'train', 'val', 'test'.
220        download: Whether to stream and cache the data if it is not present.
221            Requires s3fs (pip install s3fs).
222        offsets: Offset values for affinity computation used as target.
223        boundaries: Whether to compute boundaries as the target.
224        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset`.
225
226    Returns:
227        The segmentation dataset.
228    """
229    assert len(patch_shape) == 3
230
231    paths = get_nisb_paths(path, setting, split, download)
232
233    kwargs = util.update_kwargs(kwargs, "is_seg_dataset", True)
234    kwargs, _ = util.add_instance_label_transform(
235        kwargs, add_binary_target=False, boundaries=boundaries, offsets=offsets
236    )
237
238    return torch_em.default_segmentation_dataset(
239        raw_paths=paths,
240        raw_key="img",
241        label_paths=paths,
242        label_key="seg",
243        patch_shape=patch_shape,
244        **kwargs,
245    )

Get the NISB dataset for neuron instance segmentation in EM.

NISB provides 9 settings of varying difficulty, each with multiple cubes at 27µm side length. Image data is stored under the zarr key 'img' with shape (z, y, x) and segmentation under 'seg'. The multichannel setting stores 8-channel data with shape (z, y, x, 8).

Arguments:
  • path: Filepath to a folder where the cached data will be saved.
  • patch_shape: The patch shape to use for training.
  • setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
  • split: The data split, one of 'train', 'val', 'test'.
  • download: Whether to stream and cache the data if it is not present. Requires s3fs (pip install s3fs).
  • offsets: Offset values for affinity computation used as target.
  • boundaries: Whether to compute boundaries as the target.
  • kwargs: Additional keyword arguments for torch_em.default_segmentation_dataset.
Returns:

The segmentation dataset.

def get_nisb_loader( path: Union[os.PathLike, str], patch_shape: Tuple[int, int, int], batch_size: int, setting: str = 'base', split: Literal['train', 'val', 'test'] = 'train', download: bool = False, offsets: Optional[List[List[int]]] = None, boundaries: bool = False, **kwargs) -> torch.utils.data.dataloader.DataLoader:
248def get_nisb_loader(
249    path: Union[os.PathLike, str],
250    patch_shape: Tuple[int, int, int],
251    batch_size: int,
252    setting: str = "base",
253    split: Literal["train", "val", "test"] = "train",
254    download: bool = False,
255    offsets: Optional[List[List[int]]] = None,
256    boundaries: bool = False,
257    **kwargs,
258) -> DataLoader:
259    """Get the DataLoader for neuron instance segmentation in the NISB dataset.
260
261    Args:
262        path: Filepath to a folder where the cached data will be saved.
263        patch_shape: The patch shape to use for training.
264        batch_size: The batch size for training.
265        setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
266        split: The data split, one of 'train', 'val', 'test'.
267        download: Whether to stream and cache the data if it is not present.
268            Requires s3fs (pip install s3fs).
269        offsets: Offset values for affinity computation used as target.
270        boundaries: Whether to compute boundaries as the target.
271        kwargs: Additional keyword arguments for `torch_em.default_segmentation_dataset` or for the PyTorch DataLoader.
272
273    Returns:
274        The DataLoader.
275    """
276    ds_kwargs, loader_kwargs = util.split_kwargs(torch_em.default_segmentation_dataset, **kwargs)
277    ds = get_nisb_dataset(
278        path=path,
279        patch_shape=patch_shape,
280        setting=setting,
281        split=split,
282        download=download,
283        offsets=offsets,
284        boundaries=boundaries,
285        **ds_kwargs,
286    )
287    return torch_em.get_data_loader(ds, batch_size=batch_size, **loader_kwargs)

Get the DataLoader for neuron instance segmentation in the NISB dataset.

Arguments:
  • path: Filepath to a folder where the cached data will be saved.
  • patch_shape: The patch shape to use for training.
  • batch_size: The batch size for training.
  • setting: The NISB setting. One of NISB_SETTINGS. Default 'base'.
  • split: The data split, one of 'train', 'val', 'test'.
  • download: Whether to stream and cache the data if it is not present. Requires s3fs (pip install s3fs).
  • offsets: Offset values for affinity computation used as target.
  • boundaries: Whether to compute boundaries as the target.
  • kwargs: Additional keyword arguments for torch_em.default_segmentation_dataset or for the PyTorch DataLoader.
Returns:

The DataLoader.