Skip to content

Commit

Permalink
WIP: ENH: Use cuCIM in CLI conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
thewtex committed Mar 5, 2024
1 parent ae53013 commit 136ddb5
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 16 deletions.
27 changes: 27 additions & 0 deletions ngff_zarr/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

from .cli_input_to_ngff_image import cli_input_to_ngff_image
from .config import config
from .cucim_image_to_multiscales import cucim_image_to_multiscales
from .cucim_image_to_ngff_image import cucim_image_to_ngff_image
from .detect_cli_io_backend import (
ConversionBackend,
conversion_backends_values,
Expand Down Expand Up @@ -336,6 +338,31 @@ def shutdown_client(sig_id, frame): # noqa: ARG001
_multiscales_to_ngff_zarr(
live, args, output_store, rich_dask_progress, multiscales
)
elif input_backend is ConversionBackend.CUCIM:
try:
import cucim

cuimage = cucim.CuImage(str(args.input[0]))
if args.chunks is None:
# Present the existing chunks and resolution levels
multiscales = cucim_image_to_multiscales(cuimage)
else:
ngff_image = cucim_image_to_ngff_image(cuimage)
multiscales = _ngff_image_to_multiscales(
live,
ngff_image,
args,
progress,
rich_dask_progress,
subtitle,
method,
)
_multiscales_to_ngff_zarr(
live, args, output_store, rich_dask_progress, multiscales
)
except ImportError:
sys.stdout.write("[red]Please install the [i]cucim[/i] package.\n")
sys.exit(1)
elif input_backend is ConversionBackend.TIFFFILE:
try:
import tifffile
Expand Down
18 changes: 2 additions & 16 deletions ngff_zarr/cli_input_to_ngff_image.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import sys

import numpy as np
import zarr
from dask.array.image import imread as daimread
from rich import print

from .cucim_image_to_ngff_image import cucim_image_to_ngff_image
from .detect_cli_io_backend import ConversionBackend
from .from_ngff_zarr import from_ngff_zarr
from .itk_image_to_ngff_image import itk_image_to_ngff_image
from .methods._support import _spatial_dims
from .ngff_image import NgffImage
from .to_ngff_image import to_ngff_image

Expand Down Expand Up @@ -55,20 +54,7 @@ def imread(filename):
import cucim

cuimage = cucim.CuImage(str(input[0]))
data = np.array(cuimage)
dims = tuple(d for d in cuimage.dims.lower())
spatial_dims = set(dims).intersection(_spatial_dims)
spatial_dims = [d for d in dims if d in spatial_dims]
spatial_dims_str = "".join(spatial_dims).upper()
translation = {d: 0.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
# cucim: Should origin have a dim_order argument like spacing?
translation[dim] = cuimage.origin[idx]
spacing = cuimage.spacing(spatial_dims_str)
scale = {d: 1.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
scale[dim] = spacing[idx]
return to_ngff_image(data, dims=dims, translation=translation, scale=scale)
return cucim_image_to_ngff_image(cuimage)
except ImportError:
print("[red]Please install the [i]cucim[/i] package.")
sys.exit(1)
Expand Down
99 changes: 99 additions & 0 deletions ngff_zarr/cucim_image_to_multiscales.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import dask
import dask.array as da
import numpy as np

from .methods._support import _spatial_dims
from .multiscales import Multiscales
from .to_ngff_image import to_ngff_image
from .zarr_metadata import Axis, Dataset, Metadata, Scale, Translation


def _read_region_data(cuimage, location, size, level):
return np.array(cuimage.read_region(location, size, level))


def cucim_image_to_multiscales(cuimage) -> Multiscales:
dims = tuple(d for d in cuimage.dims.lower())
spatial_dims = set(dims).intersection(_spatial_dims)
spatial_dims = [d for d in dims if d in spatial_dims]
spatial_dims_str = "".join(spatial_dims).upper()

images = []

axes = []
for dim in dims:
unit = None
if dim in {"x", "y", "z"}:
axis = Axis(name=dim, type="space", unit=unit)
elif dim == "c":
axis = Axis(name=dim, type="channel", unit=unit)
elif dim == "t":
axis = Axis(name=dim, type="time", unit=unit)
else:
msg = f"Dimension identifier is not valid: {dim}"
raise KeyError(msg)
axes.append(axis)

for level in range(cuimage.resolutions["level_count"]):
scale_dimensions = cuimage.resolutions["level_dimensions"][level]
scale_downsamples = cuimage.resolutions["level_downsamples"][level]
scale_tile_size = cuimage.resolutions["level_tile_sizes"][level]
# hard coded for 2d
blocks = []
for ii in range(scale_dimensions[0] // scale_tile_size[0]):
block_row = []
for jj in range(scale_dimensions[1] // scale_tile_size[1]):
location = (ii * scale_tile_size[0], jj * scale_tile_size[1])
size = scale_tile_size
block_row.append(
dask.delayed(_read_region_data)(cuimage, location, size, level)
)
blocks.append(block_row)
data = da.block(blocks)

spacing = cuimage.spacing(spatial_dims_str)
scale = {d: 1.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
scale[dim] = spacing[idx] * scale_downsamples

translation = {d: 0.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
# cucim: Should origin have a dim_order argument like spacing?
translation[dim] = (
cuimage.origin[idx] + spacing[idx] * scale_downsamples / 2
)

image = to_ngff_image(data, dims=dims, translation=translation, scale=scale)
images.append(image)

datasets = []
for index, image in enumerate(images):
path = f"scale{index}/{image.name}"
scale = []
for dim in image.dims:
if dim in image.scale:
scale.append(image.scale[dim])
else:
scale.append(1.0)
translation = []
for dim in image.dims:
if dim in image.translation:
translation.append(image.translation[dim])
else:
translation.append(1.0)
coordinateTransformations = [Scale(scale), Translation(translation)]
dataset = Dataset(
path=path, coordinateTransformations=coordinateTransformations
)
datasets.append(dataset)
metadata = Metadata(
axes=axes,
datasets=datasets,
name=image.name,
coordinateTransformations=None,
)
return Multiscales(
images=images,
metadata=metadata,
scale_factors=cuimage.resolutions["level_downsamples"],
)
22 changes: 22 additions & 0 deletions ngff_zarr/cucim_image_to_ngff_image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import numpy as np

from .methods._support import _spatial_dims
from .ngff_image import NgffImage
from .to_ngff_image import to_ngff_image


def cucim_image_to_ngff_image(cuimage) -> NgffImage:
data = np.array(cuimage)
dims = tuple(d for d in cuimage.dims.lower())
spatial_dims = set(dims).intersection(_spatial_dims)
spatial_dims = [d for d in dims if d in spatial_dims]
spatial_dims_str = "".join(spatial_dims).upper()
translation = {d: 0.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
# cucim: Should origin have a dim_order argument like spacing?
translation[dim] = cuimage.origin[idx]
spacing = cuimage.spacing(spatial_dims_str)
scale = {d: 1.0 for d in spatial_dims}
for idx, dim in enumerate(spatial_dims):
scale[dim] = spacing[idx]
return to_ngff_image(data, dims=dims, translation=translation, scale=scale)
2 changes: 2 additions & 0 deletions ngff_zarr/detect_cli_io_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def detect_cli_io_backend(input: List[str]) -> ConversionBackend:
if extension in itk_supported_extensions:
return ConversionBackend.ITK

extension = Path(input[0]).suffixes[-1].lower()

if importlib.util.find_spec("cucim") is not None:
cucim_supported_extensions = (".svs", ".tif", ".tiff")

Expand Down

0 comments on commit 136ddb5

Please sign in to comment.