Skip to content

Support for biomedical image formats NIFTI #139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
zarr = ["ome-zarr>=0.9.0"]
openslide = ["openslide-python"]
tiff = ["tifffile", "imagecodecs"]
nifti = ["nibabel"]
cloud = ["tiledb-cloud"]

full = sorted({*zarr, *openslide, *tiff})
full = sorted({*zarr, *openslide, *tiff, *nifti})
setuptools.setup(
setup_requires=["setuptools_scm"],
use_scm_version={
Expand All @@ -25,6 +26,7 @@
"zarr": zarr,
"openslide": openslide,
"tiff": tiff,
"nifti": nifti,
"cloud": cloud,
"full": full,
},
Expand All @@ -34,17 +36,20 @@
"zarr_reader = tiledb.bioimg.converters.ome_zarr:OMEZarrReader",
"osd_reader = tiledb.bioimg.converters.openslide:OpenSlideReader",
"png_reader = tiledb.bioimg.converters.png.PNGReader",
"nifti_reader = tiledb.bioimg.converters.nifti:NiftiReader",
],
"bioimg.writers": [
"tiff_writer = tiledb.bioimg.converters.ome_tiff:OMETiffWriter",
"zarr_writer = tiledb.bioimg.converters.ome_tiff:OMEZarrWriter",
"png_writer = tiledb.bioimg.converters.png.PNGWriter",
"nifti_writer = tiledb.bioimg.converters.nifti:NiftiWriter",
],
"bioimg.converters": [
"tiff_converter = tiledb.bioimg.converters.ome_tiff:OMETiffConverter",
"zarr_converter = tiledb.bioimg.converters.ome_zarr:OMEZarrConverter",
"osd_converter = tiledb.bioimg.converters.openslide:OpenSlideConverter",
"png_converter = tiledb.bioimg.converters.png:PNGConverter",
"nifti_converter = tiledb.bioimg.converters.nifti:NiftiConverter",
],
},
)
Binary file added tests/data/nifti/anatomical.nii
Binary file not shown.
Binary file added tests/data/nifti/example4d.nii
Binary file not shown.
Binary file added tests/data/nifti/functional.nii
Binary file not shown.
Binary file added tests/data/nifti/standard.nii
Binary file not shown.
Binary file added tests/data/nifti/visiblehuman.nii
Binary file not shown.
114 changes: 114 additions & 0 deletions tests/integration/converters/test_nifti.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import json

import nibabel as nib
import numpy as np
import pytest

import tiledb
from tests import get_path
from tiledb.bioimg.converters import DATASET_TYPE, FMT_VERSION
from tiledb.bioimg.converters.nifti import NiftiConverter
from tiledb.bioimg.openslide import TileDBOpenSlide


def compare_nifti_images(file1, file2, scaled_test):
img1 = nib.load(file1)
img2 = nib.load(file2)

# Compare the affine matrices (spatial information)
assert np.array_equal(img1.affine, img2.affine)

# Compare the image data (voxel data)
data1 = np.array(img1.dataobj, dtype=img1.get_data_dtype())
data2 = np.array(img2.dataobj, dtype=img2.get_data_dtype())

assert np.array_equal(data1, data2)

# Compare the image data scaled (voxel data)
if scaled_test:
data_sc = img1.get_fdata()
data_sc_2 = img2.get_fdata()

assert np.array_equal(data_sc, data_sc_2)


@pytest.mark.parametrize(
"filename",
[
"nifti/example4d.nii",
"nifti/functional.nii",
"nifti/standard.nii",
"nifti/visiblehuman.nii",
"nifti/anatomical.nii",
],
)
@pytest.mark.parametrize("preserve_axes", [False, True])
@pytest.mark.parametrize("chunked", [False])
@pytest.mark.parametrize(
"compressor, lossless",
[
(tiledb.ZstdFilter(level=0), True),
# WEBP is not supported for these images
],
)
def test_nifti_converter_roundtrip(
tmp_path, preserve_axes, chunked, compressor, lossless, filename
):
# For lossy WEBP we cannot use random generated images as they have so much noise
input_path = str(get_path(filename))
tiledb_path = str(tmp_path / "to_tiledb")
output_path = str(tmp_path / "from_tiledb.nii")

NiftiConverter.to_tiledb(
input_path,
tiledb_path,
preserve_axes=preserve_axes,
chunked=chunked,
compressor=compressor,
log=False,
)
# Store it back to PNG
NiftiConverter.from_tiledb(tiledb_path, output_path)
# The dtype of this image is complex and nibabel breaks originally
compare_nifti_images(
input_path,
output_path,
scaled_test=False if filename == "nifti/visiblehuman.nii" else True,
)


@pytest.mark.parametrize(
"filename, axes, canonical",
[
("nifti/example4d.nii", "XYZT", "TZYX"),
("nifti/functional.nii", "XYZT", "TZYX"),
("nifti/standard.nii", "XYZ", "ZYX"),
("nifti/visiblehuman.nii", "XYZTC", "CZYX"),
("nifti/anatomical.nii", "XYZ", "ZYX"),
],
)
def test_nifti_converter_group_metadata(tmp_path, filename, axes, canonical):
input_path = get_path(filename)
tiledb_path = str(tmp_path / "to_tiledb")
NiftiConverter.to_tiledb(input_path, tiledb_path, preserve_axes=False)

with TileDBOpenSlide(tiledb_path) as t:
group_properties = t.properties
assert group_properties["dataset_type"] == DATASET_TYPE
assert group_properties["fmt_version"] == FMT_VERSION
assert isinstance(group_properties["pkg_version"], str)
assert group_properties["axes"] == axes

levels_group_meta = json.loads(group_properties["levels"])
assert t.level_count == len(levels_group_meta)
for level, level_meta in enumerate(levels_group_meta):
assert level_meta["level"] == level
assert level_meta["name"] == f"l_{level}.tdb"

level_axes = level_meta["axes"]
shape = level_meta["shape"]
level_width, level_height = t.level_dimensions[level]
assert level_axes == canonical
assert len(shape) == len(level_axes)
assert shape[level_axes.index("X")] == level_width
assert shape[level_axes.index("Y")] == level_height
Loading
Loading