Read Numpy array as itk.itkImagePython.itkImageF3

I just want to be able to use a numpy array as itk.itkImagePython.itkImageF3 to feed it into a registration (without SimpleITK). Thanks!

Description of the issue with MWE

I get an error when trying to run any of these

import itk
import numpy as np

series_length = 4
holder = np.zeros((series_length, 10, 10, 3), dtype=np.uint8)

PixelType = itk.ctype("float")
Dimension = 3
ImageType = itk.Image[PixelType, Dimension]

# Option 1
itk_image = itk.image_from_array(holder[0], ttype=(ImageType,))

# Option 2
itk_image = itk.PyBuffer[ImageType].GetImageFromArray(holder[0])

Expected behaviour

Take a Numpy array into an ITK object which I can use for registration.

Actual result

RuntimeError: Size mismatch of image and Buffer.

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<string>", line 17, in __PYTHON_EL_eval
  File "<string>", line 1, in <module>
  File "/usr/lib/python3.10/itk/support/extras.py", line 394, in GetImageFromArray
    return _GetImageFromArray(arr, "GetImageFromArray", is_vector, ttype)
  File "/usr/lib/python3.10/itk/support/extras.py", line 386, in _GetImageFromArray
    return templatedFunction(arr, is_vector)
  File "/usr/lib/python3.10/itk/itkPyBufferPython.py", line 2592, in GetImageFromArray
    imageView = itkPyBufferIF3.GetImageViewFromArray(ndarr, is_vector)
  File "/usr/lib/python3.10/itk/itkPyBufferPython.py", line 2562, in GetImageViewFromArray
    imgview = itkPyBufferIF3._GetImageViewFromArray(ndarr, ndarr.shape[::-1], 1)
SystemError: <built-in function itkPyBufferIF3__GetImageViewFromArray> returned a result with an exception set

Optional context

Current approach

The goal is to run a registration between two 3D objects as shown here. There, @dzenanz set the image type into itk.ImageRegistrationMethodv4. The original data was created with

  1. a numpy array

  2. saved to a file and

  3. read back with ~itk.imread

    holder = np.zeros((series_length, *obj3d.shape), dtype=np.uint8)
    fixed_image = itk.image_from_array(holder[0])
    moving_image = itk.image_from_array(holder[-1])

    itk.imwrite(itk.image_from_array(holder[0]),
    ā€œitk_hello_registration.in1.vtkā€)
    itk.imwrite(itk.image_from_array(holder[-1]),
    ā€œitk_hello_registration.in4.vtkā€)
    fixedImageFile = ā€œitk_hello_registration.in1.vtkā€
    movingImageFile = ā€œitk_hello_registration.in4.vtkā€

    PixelType = itk.ctype(ā€˜floatā€™)
    fixed_image = itk.imread(fixedImageFile, PixelType)
    moving_image = itk.imread(movingImageFile, PixelType)

Related posts

Here, I have seen that a PyBuffer can be used to specify the type of data for GetImageFromArray (which is the same as image_from_array).

ImageType = itk.Image[itk.RGBPixel[itk.UC], 3]
itk_image = itk.PyBuffer[ImageType].GetImageFromArray(np_image)

Also, here, I found that image types can be casted

PixelType = itk.ctype(ā€œunsigned charā€)
image = itk.imread("input_filename.png", PixelType)
dimension = image.GetImageDimension()
InputImageType = type(image)
OutputPixelType = itk.ctype(ā€œfloatā€)
OutputImageType = itk.Image[OutputPixelType, dimension]
cast = itk.CastImageFilter[InputImageType, OutputImageType].New(image)
itk.imwrite(cast, "output_filename.png")

Workaround

I have found that I can do this

fixed_image = itk.image_from_array(holder[0])
moving_image = itk.image_from_array(holder[-1])
# Set the data in the right format (casting)
fixed_castImageFilter = itk.CastImageFilter[
    type(fixed_image), ImageType].New()
fixed_castImageFilter.SetInput(fixed_image)
moving_castImageFilter = itk.CastImageFilter[
    type(moving_image), ImageType].New()
moving_castImageFilter.SetInput(moving_image)

followed by this

registration = itk.ImageRegistrationMethodv4[FixedImageType,MovingImageType].New(
    FixedImage=fixed_castImageFilter,
    MovingImage=moving_castImageFilter,
    Metric=metric,
    Optimizer=optimizer,
    InitialTransform=initialTransform)

System

Table 1: Python and ITK versions
Python 3.10.1
ITK 5.2.1
Numpy 1.21.5
Scipy 1.7.3
Parabola_(GNU/Linux) 5.15.12-gnu-1
gcc 11.1.0

Hi @edgar, the pixel type in numpy and itk has to match.
float in itk and C and C++ languages is np.float32, float in python is usually equivalent to double in C and C++.

# Option 1
itk_image = itk.image_from_array(holder[0].astype(np.float32), ttype=(ImageType,))

You donā€™t need to specify the ttype. The following is equivalent and easier to read:

itk_image = itk.image_from_array(holder[0].astype(np.float32))

You are going to get bite by the shape of your holder, it seems to me that you are creating as a (x,y,z), but in python the standard is (z,y,x). ITK assume the standard when converting from/to numpy.

Check itk_image.GetLargestPossibleRegion().GetSize(), it is itkSize3 ([3, 10, 10]). ITK follows c convention: X, Y, Z, you are getting a X=3 image, that I bet is not what you expect.
To get the shape in Z = 3, your numpy holder should be: np.zeros((series_length, 3, 10, 10))

Hope it helps

3 Likes

@edgar, it looks like you could benefit from reading an up-to-date getting started with ITK Python. Your code could be significantly shortened and simplified.

Many of the examples have not been updated to the latest and greatest syntax. A possible exercise for you is to update one of the examples from functional to procedural syntax, and then propose a PR with the changes.

1 Like

This is great Pablo @phcerdan. Thank you very much.

Indeed, I was aware of the indexing situation, but I appreciate that you point it out.

Oh, ! did consult the Getting started, but I could not find a way to convert my arrays. All of the examples dealt with reading files and thatā€™s how I managed to deal with 2D.

Would it be fine if I paste my diff or my code over here or somewhere else? I try to stay away from M$oft. (I can create a repo on Codeberg, notabug, savannah or similar services).

Thank you very much for your advice.

If you dislike GitHub that much, you could attach a git diff file to a post here. And of course, tell us how to best attribute that contribution to you.

Hi @dzenanz. I really appreciate the flexibility. Donā€™t worry about the attribution. My biggest interest is the growth of free software. Let me take the chance to express my gratitude to you, KitWare and all who contribute to that goal :smiley: . I will post my full solution shortly.

1 Like

0001-Simple-3D-registration-example-with-Python.patch (7.6 KB)

1 Like

I now see that you put GPL3 into the file. To incorporate it into ITK examples, it needs to be licensed under Apache 2.0 only. Do you mind me removing GPL3 text?

Here you go :slight_smile:
0001-Simple-3D-registration-example-with-Python.patch (7.0 KB)

By the way, I am really interested in learning how to use ITK, and you mentioned that there is a newer syntax. Where can I learn about it? I am at the stage of creating little scripts to learn and test the software in terms of 3D registration. Thanks!

The new syntax is called implicit or procedural in the getting started guide. The old syntax is called explicit or functional. You can see the differences on the example of median filter.

1 Like

Thanks! I may ask some questions about it on another post.