Skip to content

Commit

Permalink
Merge pull request #634 from ANTsX/image-from-pointer
Browse files Browse the repository at this point in the history
ENH: initialize ANTsImage with pointer only
  • Loading branch information
ncullen93 committed May 16, 2024
2 parents 75f7392 + 264c0ef commit 447acce
Show file tree
Hide file tree
Showing 24 changed files with 132 additions and 138 deletions.
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

0 comments on commit 447acce

Please sign in to comment.