Skip to content
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

ENH: initialize ANTsImage with pointer only #634

Merged
merged 1 commit into from
May 16, 2024
Merged
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
97 changes: 60 additions & 37 deletions ants/core/ants_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,47 +44,74 @@

class ANTsImage(object):

def __init__(self, pixeltype='float', dimension=3, components=1, pointer=None, is_rgb=False):
def __init__(self, pointer):
"""
Initialize an ANTsImage
Initialize an ANTsImage.

Creating an ANTsImage requires a pointer to an underlying ITK image that
is stored via a nanobind class wrapping a AntsImage struct.

Arguments
---------
pixeltype : string
ITK pixeltype of image

dimension : integer
number of image dimension. Does NOT include components dimension

components : integer
number of pixel components in the image

pointer : py::capsule (optional)
pybind11 capsule holding the pointer to the underlying ITK image object
pointer : nb::class
nanobind class wrapping the struct holding the pointer to the underlying ITK image object

"""
## Attributes which cant change without creating a new ANTsImage object
self.pointer = pointer
self.pixeltype = pixeltype
self.dimension = dimension
self.components = components
self.has_components = self.components > 1
self.dtype = _itk_to_npy_map[self.pixeltype]
self.is_rgb = is_rgb

self._pixelclass = 'vector' if self.has_components else 'scalar'
self._shortpclass = 'V' if self._pixelclass == 'vector' else ''
if is_rgb:
self._pixelclass = 'rgb'
self._shortpclass = 'RGB'

self._libsuffix = '%s%s%i' % (self._shortpclass, utils.short_ptype(self.pixeltype), self.dimension)

self.shape = tuple(utils.get_lib_fn('getShape')(self.pointer))
self.physical_shape = tuple([round(sh*sp,3) for sh,sp in zip(self.shape, self.spacing)])

self._array = None

@property
def _libsuffix(self):
return str(type(self.pointer)).split('AntsImage')[-1].split("'")[0]

@property
def shape(self):
return tuple(utils.get_lib_fn('getShape')(self.pointer))

@property
def physical_shape(self):
return tuple([round(sh*sp,3) for sh,sp in zip(self.shape, self.spacing)])

@property
def is_rgb(self):
return 'RGB' in self._libsuffix

@property
def has_components(self):
suffix = self._libsuffix
return suffix.startswith('V') or suffix.startswith('RGB')

@property
def components(self):
if not self.has_components:
return 1

libfn = utils.get_lib_fn('getComponents')
return libfn(self.pointer)

@property
def pixeltype(self):
ptype = self._libsuffix[:-1]
if self.has_components:
if self.is_rgb:
ptype = ptype[3:]
else:
ptype = ptype[1:]

ptype_map = {'UC': 'unsigned char',
'UI': 'unsigned int',
'F': 'float',
'D': 'double'}
return ptype_map[ptype]

@property
def dtype(self):
return _itk_to_npy_map[self.pixeltype]

@property
def dimension(self):
return int(self._libsuffix[-1])

@property
def spacing(self):
"""
Expand Down Expand Up @@ -284,11 +311,7 @@ def clone(self, pixeltype=None):
fn_suffix = '%s%i' % (p2_short,ndim)
libfn = utils.get_lib_fn('antsImageClone%s'%fn_suffix)
pointer_cloned = libfn(self.pointer)
return ANTsImage(pixeltype=pixeltype,
dimension=self.dimension,
components=self.components,
is_rgb=self.is_rgb,
pointer=pointer_cloned)
return iio2.from_pointer(pointer_cloned)

# pythonic alias for `clone` is `copy`
copy = clone
Expand Down
20 changes: 7 additions & 13 deletions ants/core/ants_image_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

__all__ = [
'from_pointer',
"image_header_info",
"image_clone",
"image_read",
Expand Down Expand Up @@ -66,6 +67,9 @@
_image_read_dict[itype][p][d] = "imageRead%s%s%i" % (ita, pa, d)


def from_pointer(pointer):
return iio.ANTsImage(pointer)

def from_numpy(
data, origin=None, spacing=None, direction=None, has_components=False, is_rgb=False
):
Expand Down Expand Up @@ -128,9 +132,7 @@ def _from_numpy(

if not has_components:
itk_image = libfn(data, data.shape[::-1])
ants_image = iio.ANTsImage(
pixeltype=ptype, dimension=ndim, components=1, pointer=itk_image
)
ants_image = from_pointer(itk_image)
ants_image.set_origin(origin)
ants_image.set_spacing(spacing)
ants_image.set_direction(direction)
Expand All @@ -141,9 +143,7 @@ def _from_numpy(
ants_images = []
for i in range(len(arrays)):
tmp_ptr = libfn(arrays[i], data_shape[::-1])
tmp_img = iio.ANTsImage(
pixeltype=ptype, dimension=ndim, components=1, pointer=tmp_ptr
)
tmp_img = from_pointer(tmp_ptr)
tmp_img.set_origin(origin)
tmp_img.set_spacing(spacing)
tmp_img.set_direction(direction)
Expand Down Expand Up @@ -547,13 +547,7 @@ def image_read(filename, dimension=None, pixeltype="float", reorient=False):
libfn = utils.get_lib_fn(_image_read_dict[pclass][ptype][ndim])
itk_pointer = libfn(filename)

ants_image = iio.ANTsImage(
pixeltype=ptype,
dimension=ndim,
components=ncomp,
pointer=itk_pointer,
is_rgb=is_rgb,
)
ants_image = from_pointer(itk_pointer)

if pixeltype is not None:
ants_image = ants_image.clone(pixeltype)
Expand Down
7 changes: 2 additions & 5 deletions ants/core/ants_transform.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
'transform_index_to_physical_point',
'transform_physical_point_to_index']

from . import ants_image as iio
from . import ants_image as iio, ants_image_io as iio2
from .. import utils


Expand Down Expand Up @@ -188,10 +188,7 @@ def apply_to_image(self, image, reference=None, interpolation='linear'):
reference = reference.clone(image.pixeltype)

img_ptr = tform_fn(self.pointer, image.pointer, reference.pointer, interpolation)
return iio.ANTsImage(pixeltype=image.pixeltype,
dimension=image.dimension,
components=image.components,
pointer=img_ptr)
return iio2.from_pointer(img_ptr)

def __repr__(self):
s = "ANTsTransform\n" +\
Expand Down
7 changes: 2 additions & 5 deletions ants/core/ants_transform_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import os
import numpy as np

from . import ants_image as iio
from . import ants_image as iio, ants_image_io as iio2
from . import ants_transform as tio
from .. import utils

Expand Down Expand Up @@ -297,10 +297,7 @@ def transform_to_displacement_field(xfrm, ref):
raise ValueError("Transform must be of DisplacementFieldTransform type")
libfn = utils.get_lib_fn("antsTransformToDisplacementField")
field_ptr = libfn(xfrm.pointer, ref.pointer)
return iio.ANTsImage( pixeltype=xfrm.precision,
dimension=xfrm.dimension,
components=xfrm.dimension,
pointer=field_ptr)
return iio2.from_pointer(field_ptr)

def read_transform(filename, precision="float"):
"""
Expand Down
5 changes: 2 additions & 3 deletions ants/registration/reorient_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from . import apply_transforms
from .. import utils
from ..core import ants_image as iio
from ..core import ants_image as iio, ants_image_io as iio2


_possible_orientations = ['RIP','LIP', 'RSP', 'LSP', 'RIA', 'LIA',
Expand Down Expand Up @@ -72,8 +72,7 @@ def reorient_image2(image, orientation='RAS'):
libfn = utils.get_lib_fn('reorientImage2')
itkimage = libfn(image.pointer, orientation)

new_img = iio.ANTsImage(pixeltype='float', dimension=ndim,
components=image.components, pointer=itkimage)#.clone(inpixeltype)
new_img = iio2.from_pointer(itkimage)
if inpixeltype != 'float':
new_img = new_img.clone(inpixeltype)
return new_img
Expand Down
3 changes: 1 addition & 2 deletions ants/segmentation/anti_alias.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,4 @@ def anti_alias(image):

libfn = utils.get_lib_fn('antiAlias%s' % image._libsuffix)
new_ptr = libfn(image.pointer)
return iio.ANTsImage(pixeltype='float', dimension=image.dimension,
components=image.components, pointer=new_ptr)
return iio2.from_pointer(new_ptr)
18 changes: 5 additions & 13 deletions ants/utils/add_noise_to_image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
__all__ = ["add_noise_to_image"]

from .. import utils
from ..core import ants_image as iio
from ..core import ants_image as iio, ants_image_io as iio2

def add_noise_to_image(image,
noise_model,
Expand Down Expand Up @@ -47,39 +47,31 @@ def add_noise_to_image(image,

libfn = utils.get_lib_fn("additiveGaussianNoise")
noise = libfn(image.pointer, noise_parameters[0], noise_parameters[1])
output_image = iio.ANTsImage(pixeltype='float',
dimension=image_dimension, components=1,
pointer=noise).clone('float')
output_image = iio2.from_pointer(noise).clone('float')
return output_image
elif noise_model == 'saltandpepper':
if len(noise_parameters) != 3:
raise ValueError("Incorrect number of parameters.")

libfn = utils.get_lib_fn("saltAndPepperNoise")
noise = libfn(image.pointer, noise_parameters[0], noise_parameters[1], noise_parameters[2])
output_image = iio.ANTsImage(pixeltype='float',
dimension=image_dimension, components=1,
pointer=noise).clone('float')
output_image = iio2.from_pointer(noise).clone('float')
return output_image
elif noise_model == 'shot':
if not isinstance(noise_parameters, (int, float)):
raise ValueError("Incorrect parameter specification.")

libfn = utils.get_lib_fn("shotNoise")
noise = libfn(image.pointer, noise_parameters)
output_image = iio.ANTsImage(pixeltype='float',
dimension=image_dimension, components=1,
pointer=noise).clone('float')
output_image = iio2.from_pointer(noise).clone('float')
return output_image
elif noise_model == 'speckle':
if not isinstance(noise_parameters, (int, float)):
raise ValueError("Incorrect parameter specification.")

libfn = utils.get_lib_fn("speckleNoise")
noise = libfn(image.pointer, noise_parameters)
output_image = iio.ANTsImage(pixeltype='float',
dimension=image_dimension, components=1,
pointer=noise).clone('float')
output_image = iio2.from_pointer(noise).clone('float')
return output_image
else:
raise ValueError("Unknown noise model.")
10 changes: 3 additions & 7 deletions ants/utils/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__all__ = ['merge_channels',
'split_channels']

from ..core import ants_image as iio
from ..core import ants_image as iio, ants_image_io as iio2
from .. import utils


Expand Down Expand Up @@ -45,10 +45,7 @@ def merge_channels(image_list):
libfn = utils.get_lib_fn('mergeChannels')
image_ptr = libfn([image.pointer for image in image_list])

return iio.ANTsImage(pixeltype=inpixeltype,
dimension=dimension,
components=components,
pointer=image_ptr)
return iio2.from_pointer(image_ptr)


def split_channels(image):
Expand Down Expand Up @@ -82,8 +79,7 @@ def split_channels(image):

libfn = utils.get_lib_fn('splitChannels')
itkimages = libfn(image.pointer)
antsimages = [iio.ANTsImage(pixeltype=inpixeltype, dimension=dimension,
components=components, pointer=itkimage) for itkimage in itkimages]
antsimages = [iio2.from_pointer(itkimage) for itkimage in itkimages]
return antsimages


Expand Down
5 changes: 2 additions & 3 deletions ants/utils/compose_displacement_fields.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

__all__ = ['compose_displacement_fields']

from ..core import ants_image as iio
from ..core import ants_image as iio, ants_image_io as iio2
from .. import utils


Expand All @@ -27,8 +27,7 @@ def compose_displacement_fields(displacement_field,
libfn = utils.get_lib_fn('composeDisplacementFieldsD%i' % displacement_field.dimension)
comp_field = libfn(displacement_field.pointer, warping_field.pointer)

new_image = iio.ANTsImage(pixeltype='float', dimension=displacement_field.dimension,
components=displacement_field.dimension, pointer=comp_field).clone('float')
new_image = iio2.from_pointer(comp_field).clone('float')
return new_image


11 changes: 4 additions & 7 deletions ants/utils/crop_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


from .get_mask import get_mask
from ..core import ants_image as iio
from ..core import ants_image as iio, ants_image_io as iio2
from .. import utils


Expand Down Expand Up @@ -52,8 +52,7 @@ def crop_image(image, label_image=None, label=1):

libfn = utils.get_lib_fn('cropImage')
itkimage = libfn(image.pointer, label_image.pointer, label, 0, [], [])
return iio.ANTsImage(pixeltype='float', dimension=ndim,
components=image.components, pointer=itkimage).clone(inpixeltype)
return iio2.from_pointer(itkimage).clone(inpixeltype)


def crop_indices(image, lowerind, upperind):
Expand Down Expand Up @@ -98,8 +97,7 @@ def crop_indices(image, lowerind, upperind):

libfn = utils.get_lib_fn('cropImage')
itkimage = libfn(image.pointer, image.pointer, 1, 2, lowerind, upperind)
ants_image = iio.ANTsImage(pixeltype='float', dimension=image.dimension,
components=image.components, pointer=itkimage)
ants_image = iio2.from_pointer(itkimage)
if inpixeltype != 'float':
ants_image = ants_image.clone(inpixeltype)
return ants_image
Expand Down Expand Up @@ -141,8 +139,7 @@ def decrop_image(cropped_image, full_image):

libfn = utils.get_lib_fn('cropImage')
itkimage = libfn(cropped_image.pointer, full_image.pointer, 1, 1, [], [])
ants_image = iio.ANTsImage(pixeltype='float', dimension=cropped_image.dimension,
components=cropped_image.components, pointer=itkimage)
ants_image = iio2.from_pointer(itkimage)
if inpixeltype != 'float':
ants_image = ants_image.clone(inpixeltype)

Expand Down
Loading