Skip to content

Commit 3902fac

Browse files
committed
Changed multiprocessor; Add NibabelRO IO; Changed File Manager
1 parent 546230b commit 3902fac

File tree

21 files changed

+425
-176
lines changed

21 files changed

+425
-176
lines changed

.github/workflows/test_and_deploy.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ on:
77
push:
88
branches:
99
- main
10-
- npe2
1110
tags:
1211
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
1312
pull_request:
1413
branches:
1514
- main
16-
- npe2
1715
workflow_dispatch:
1816

1917
jobs:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Local dev/testing scripts and folders
22
playground.py
3+
metric.py
34
stats/
45
dataset_cfg/
56
tests/temp/

examples/template.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: DatasetName
22

33
# Define an arbitrary number of layers
44
layers:
5-
- name: SomeImages # unique layer name
5+
SomeImages: # unique layer name
66
type: image # 'image' for image data and for label data one of: 'semseg' | 'multilabel'
77
path: some/path/to/data # directory
88
file_type: .png # required extension incl. dot (e.g., .png | .nii.gz | .b2nd)
@@ -11,7 +11,7 @@ layers:
1111
channels: 3 # number of channels, required for image
1212
file_stack: False # True if each channel is a separate file: *_0000, *_0001, ...
1313

14-
- name: SomeLabels
14+
SomeLabels:
1515
type: semseg # semseg --> Semantic Segmentation | multilabel --> Multilabel Segmentation
1616
path: some/path/to/data
1717
file_type: .png|.nii.gz|.b2nd|...

src/vidata/config_manager.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from pathlib import Path
22

3-
from omegaconf import DictConfig, OmegaConf
3+
from omegaconf import DictConfig, ListConfig, OmegaConf
44

55
from vidata.file_manager import FileManager, FileManagerStacked
66
from vidata.io import load_json
@@ -330,7 +330,20 @@ def __init__(self, config: dict | DictConfig | str | Path, strict: bool = True):
330330
self.layers = []
331331

332332
split_cfg = self.config.get("splits", {})
333-
for layer_cfg in self.config.get("layers", []):
333+
334+
layers_cfg = self.config.get("layers", [])
335+
if isinstance(layers_cfg, (dict | DictConfig)):
336+
layer_list = []
337+
for layer_name, layer_cfg in layers_cfg.items():
338+
layer_cfg = dict(layer_cfg)
339+
layer_cfg["name"] = layer_name
340+
layer_list.append(layer_cfg)
341+
elif isinstance(layers_cfg, (list | ListConfig)):
342+
layer_list = layers_cfg
343+
else:
344+
raise ValueError(f"Invalid type for layers: {type(layers_cfg)}. Must be dict or list.")
345+
346+
for layer_cfg in layer_list:
334347
layer_split = {}
335348

336349
for k in _VALID_SPLITS:
@@ -358,3 +371,6 @@ def layer_names(self):
358371

359372
def __len__(self):
360373
return len(self.layers)
374+
375+
def __getitem__(self, layer_name: str) -> LayerConfigManager:
376+
return self.layer(layer_name)

src/vidata/file_manager/file_manager.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class FileManager:
2323
Keep files whose RELATIVE path contains ANY of these substrings.
2424
exclude_names: list[str] | None
2525
Drop files whose RELATIVE path contains ANY of these substrings. (Exclude wins.)
26+
recursive: bool
27+
Whether to recursively search subdirectories.
2628
"""
2729

2830
def __init__(
@@ -32,12 +34,14 @@ def __init__(
3234
pattern: str | None = None,
3335
include_names: list[str] | None = None,
3436
exclude_names: list[str] | None = None,
37+
recursive: bool = False,
3538
):
3639
self.path = path
3740
self.file_type = file_type
3841
self.pattern = pattern
3942
self.include_names = include_names
4043
self.exclude_names = exclude_names
44+
self.recursive = recursive
4145
self.collect_files()
4246
self.filter_files()
4347

@@ -69,17 +73,31 @@ def collect_files(self):
6973
pattern = "*" + self.pattern
7074
else:
7175
pattern = self.pattern
72-
files = list(Path(self.path).glob(pattern + self.file_type))
76+
77+
if self.recursive:
78+
files = list(Path(self.path).rglob(pattern + self.file_type))
79+
else:
80+
files = list(Path(self.path).glob(pattern + self.file_type))
7381
self.files = natsorted(files, key=lambda p: p.name)
7482

7583
def get_name(self, file: str | int, with_file_type=True) -> str:
84+
"""Just keep this for backwards compatibility"""
85+
return self.name_from_path(file, with_file_type)
86+
87+
def name_from_path(self, file: str | int, include_ext: bool = True) -> str:
7688
if isinstance(file, int):
7789
file = str(self.files[file])
7890
name = str(Path(file).relative_to(self.path))
79-
if not with_file_type:
91+
if not include_ext:
8092
name = name.replace(self.file_type, "")
8193
return name
8294

95+
def path_from_name(self, name: str | Path, include_ext=True):
96+
rel = Path(name)
97+
if include_ext and rel.suffix != self.file_type:
98+
rel = rel.with_suffix(self.file_type)
99+
return (self.path / rel).resolve()
100+
83101
def __getitem__(self, item: int):
84102
return self.files[item]
85103

src/vidata/io/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# isort: skip_file
1+
# isort: skip_file # order matters, first ones in list are the defaults
22
# ruff: noqa: I001, I002 # disable Ruff's import-sorting checks for this file
33
from .image_io import load_image, save_image
44
from .sitk_io import load_sitk, save_sitk
5-
from .nib_io import load_nib, save_nib
5+
from .nib_io import load_nib, save_nib, load_nibRO, save_nibRO
66
from .tif_io import load_tif, save_tif
77
from .blosc2_io import load_blosc2, load_blosc2pkl, save_blosc2, save_blosc2pkl
88
from .numpy_io import load_npy, load_npz, save_npy, save_npz
@@ -16,6 +16,8 @@
1616
"save_sitk",
1717
"load_nib",
1818
"save_nib",
19+
"load_nibRO",
20+
"save_nibRO",
1921
"load_blosc2",
2022
"save_blosc2",
2123
"load_blosc2pkl",

src/vidata/io/blosc2_io.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import math
88
from copy import deepcopy
9+
from pathlib import Path
910
from typing import Union
1011

1112
import blosc2
@@ -19,7 +20,7 @@
1920
@register_writer("mask", ".b2nd", backend="blosc2")
2021
def save_blosc2(
2122
data: np.ndarray,
22-
file: str,
23+
file: str | Path,
2324
patch_size: Union[tuple[int, int], tuple[int, int, int]] | None = None,
2425
clevel: int = 8,
2526
nthreads: int = 8,
@@ -42,11 +43,7 @@ def save_blosc2(
4243
_is_float = np.issubdtype(data.dtype.type, np.floating)
4344
_is_2d = data.ndim == 2
4445

45-
# if _is_2d:
4646
base_patch_size = (512 if _is_float else 1024) if _is_2d else (64 if _is_float else 96)
47-
# else:
48-
# base_patch_size = 64 if _is_float else 96
49-
5047
patch_size = tuple([min(s, base_patch_size) for s in data.shape])
5148

5249
blocks, chunks = comp_blosc2_params(data.shape, patch_size, data.itemsize)
@@ -60,12 +57,12 @@ def save_blosc2(
6057
mmap_mode="w+",
6158
meta=metadata,
6259
)
63-
return [file]
60+
return [str(file)]
6461

6562

6663
@register_loader("image", ".b2nd", backend="blosc2")
6764
@register_loader("mask", ".b2nd", backend="blosc2")
68-
def load_blosc2(file: str, nthreads: int = 1) -> tuple[blosc2.NDArray, dict]:
65+
def load_blosc2(file: str | Path, nthreads: int = 1) -> tuple[blosc2.NDArray, dict]:
6966
"""Reads a Blosc2 file and returns the data and metadata.
7067
7168
Args:
@@ -86,7 +83,7 @@ def load_blosc2(file: str, nthreads: int = 1) -> tuple[blosc2.NDArray, dict]:
8683
@register_writer("mask", ".b2nd", backend="blosc2pkl")
8784
def save_blosc2pkl(
8885
data: np.ndarray,
89-
file: str,
86+
file: str | Path,
9087
patch_size: Union[tuple[int, int], tuple[int, int, int]] | None = None,
9188
clevel: int = 8,
9289
nthreads: int = 8,
@@ -112,7 +109,7 @@ def save_blosc2pkl(
112109

113110
@register_loader("image", ".b2nd", backend="blosc2pkl")
114111
@register_loader("mask", ".b2nd", backend="blosc2pkl")
115-
def load_blosc2pkl(file: str, nthreads: int = 1) -> tuple[blosc2.NDArray, dict]:
112+
def load_blosc2pkl(file: str | Path, nthreads: int = 1) -> tuple[blosc2.NDArray, dict]:
116113
"""Reads a Blosc2 file and returns the data and metadata.
117114
118115
Args:

src/vidata/io/image_io.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from pathlib import Path
2+
13
import imageio.v3 as iio
24
import numpy as np
35

@@ -6,13 +8,13 @@
68

79
@register_loader("image", ".png", ".jpg", ".jpeg", ".bmp", backend="imageio")
810
@register_loader("mask", ".png", ".bmp", backend="imageio")
9-
def load_image(file: str):
11+
def load_image(file: str | Path):
1012
data = iio.imread(file) # automatically handles RGB, grayscale, masks
1113
return data, {}
1214

1315

1416
@register_writer("image", ".png", ".jpg", ".jpeg", ".bmp", backend="imageio")
1517
@register_writer("mask", ".png", ".bmp", backend="imageio")
16-
def save_image(data: np.ndarray, file: str) -> list[str]:
18+
def save_image(data: np.ndarray, file: str | Path) -> list[str]:
1719
iio.imwrite(file, data)
18-
return [file]
20+
return [str(file)]

src/vidata/io/json_io.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
2+
from pathlib import Path
23
from typing import Any
34

45

5-
def load_json(json_file: str) -> Any:
6+
def load_json(json_file: str | Path) -> Any:
67
"""Load data from a JSON file.
78
89
Args:
@@ -16,7 +17,7 @@ def load_json(json_file: str) -> Any:
1617
return data
1718

1819

19-
def save_json(data: Any, json_file: str, indent: int = 4) -> None:
20+
def save_json(data: Any, json_file: str | Path, indent: int = 4) -> None:
2021
"""Write data to a JSON file.
2122
2223
Args:

0 commit comments

Comments
 (0)