Skip to content
Draft
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
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ docs = [
"sphinxcontrib-autoprogram==0.1.7",
"sphinxcontrib-websupport==1.2.4",
]
itk = [
"itk>=5.4.0",
"SimpleITK>=2.5.0"
]

[project.urls]
homepage = "https://github.com/imagingdatacommons/highdicom"
Expand Down
146 changes: 146 additions & 0 deletions src/highdicom/volume.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Representations of multidimensional arrays with spatial metadata."""
from __future__ import annotations
from abc import ABC, abstractmethod
from enum import Enum
import itertools
Expand Down Expand Up @@ -44,6 +45,7 @@
keyword_for_tag,
)

from highdicom._dependency_utils import import_optional_dependency

_DCM_PYTHON_TYPE_MAP = {
'CS': str,
Expand Down Expand Up @@ -3583,6 +3585,150 @@ def pad_array(array: np.ndarray, cval: float) -> float:
channels=self._channels,
)

def to_sitk(self) -> SimpleITK.Image: # noqa
"""Convert the Volume to `SimpleITK.Image` format.

Returns
-------
SimpleITK.Image:
Image constructed from the Volume.

"""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add some more information to the docstring to help users:

  • SITK (itk) uses the same LPS convention as highdicom
  • The returned volume is transposed to hide the difference in row major / column major orderings between the two formats transparent from the user

func = self.to_sitk
sitk = import_optional_dependency(
module_name='SimpleITK',
feature=f'{func.__module__}.{func.__qualname__}'
)

array = self.array.transpose(2, 1, 0)

if self.dtype == np.bool_:
array = array.astype(int)

sitk_im = sitk.GetImageFromArray(array)
sitk_im.SetSpacing(self.spacing)
sitk_im.SetDirection(self.direction.flatten())
sitk_im.SetOrigin(self.position)

return sitk_im

@classmethod
def from_sitk(
cls,
sitk_im: SimpleITK.Image, # noqa
coordinate_system: CoordinateSystemNames | str = 'PATIENT',
frame_of_reference_uid: str | None = None,
) -> Self:
"""Construct a Volume from a `SimpleITK.Image`.

Parameters
----------
sitk_im: SimpleITK.Image
A `SimpleITK.Image` to convert to a volume.
coordinate_system: highdicom.CoordinateSystemNames | str
Coordinate system (``"PATIENT"`` or ``"SLIDE"``) in which the volume
is defined.
frame_of_reference_uid: Union[str, None], optional
Frame of reference UID for the frame of reference, if known.

Returns
-------
highdicom.Volume:
Volume constructed from the `SimpleITK.Image`.

"""
func = cls.from_sitk
sitk = import_optional_dependency(
module_name='SimpleITK',
feature=f'{func.__module__}.{func.__qualname__}'
)

array = sitk.GetArrayFromImage(sitk_im).transpose(2, 1, 0)

if array.dtype == np.bool_:
array = array.astype(int)

return cls.from_components(
array=array,
spacing=sitk_im.GetSpacing(),
coordinate_system=coordinate_system,
direction=np.reshape(sitk_im.GetDirection(), (3, 3)),
position=sitk_im.GetOrigin(),
frame_of_reference_uid=frame_of_reference_uid
)

def to_itk(self) -> itk.Image: # noqa
"""Convert the volume to `itk.Image` format.

Returns
-------
itk.Image:
Image constructed from the volume.

"""
func = self.to_itk
itk = import_optional_dependency(
module_name='itk',
feature=f'{func.__module__}.{func.__qualname__}'
)

array = self.array

if self.dtype == np.bool_:
array = array.astype(int)

itk_im = itk.GetImageFromArray(array)
itk_im.SetSpacing(self.spacing)
itk_im.SetDirection(self.direction)
itk_im.SetOrigin(self.position)

return itk_im

@classmethod
def from_itk(
cls,
itk_im: itk.Image, # noqa
coordinate_system: CoordinateSystemNames | str = 'PATIENT',
frame_of_reference_uid: str | None = None,
) -> Self:
"""Construct a Volume from an `itk.Image`.

Parameters
----------
itk_im: itk.Image
A `itk.Image` to convert to a volume.
coordinate_system: highdicom.CoordinateSystemNames | str
Coordinate system (``"PATIENT"`` or ``"SLIDE"``) in which the volume
is defined.
frame_of_reference_uid: Union[str, None], optional
Frame of reference UID for the frame of reference, if known.

Returns
-------
highdicom.Volume:
Volume constructed from the `itk.Image`.

"""
func = cls.from_itk
itk = import_optional_dependency(
module_name='itk',
feature=f'{func.__module__}.{func.__qualname__}'
)

array = itk.GetArrayFromImage(itk_im)

if array.dtype == np.bool_:
array = array.astype(int)

return cls.from_components(
array=array,
spacing=np.array(itk_im.GetSpacing()),
coordinate_system=coordinate_system,
direction=np.reshape(itk_im.GetDirection(), (3, 3)),
position=np.array(itk_im.GetOrigin()),
frame_of_reference_uid=frame_of_reference_uid
)


class VolumeToVolumeTransformer:

Expand Down
Loading