Odd behavior with Numpy Array Views

Hello, I was just experimenting with SimpleITK in a Jupyter Notebook and was surprised by the following behavior.

> junk = np.zeros((3,3), dtype=np.uint8)
> junk
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]], dtype=uint8)

> sitk.GetArrayFromImage(sitk.GetImageFromArray(junk))
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]], dtype=uint8)

> sitk.GetArrayViewFromImage(sitk.GetImageFromArray(junk))
array([[ 96, 117, 250],
       [ 79,   5,  86],
       [  0,   0,  48]], dtype=uint8)

> tmp = sitk.GetImageFromArray(junk)
> sitk.GetArrayViewFromImage(tmp)
array([[0, 0, 0],
       [0, 0, 0],
       [0, 0, 0]], dtype=uint8)

The odd part is the third result where I am using GetArrayViewFromImage on the same line as GetImageFromArray. The values it returns change each time, leading me to believe it is just getting garbage from memory.

Interestingly, it seems to stop when I print the result:

> print(sitk.GetArrayViewFromImage(sitk.GetImageFromArray(junk)))
[[0 0 0]
 [0 0 0]
 [0 0 0]]

But sometimes it will report bogus units for the first run of the cell before printing the correct values for each run after.

Based on a search of the forum, I think it might be related to this post.

The issue only seems to arise when I have multiple function calls on one line which I can easily avoid doing in practice. This behavior seems like a bug but I wanted to mention it here before submitting an issue in case I was missing something. If this is (somehow) expected behavior, please let me know. Thanks!

Hello @brandonfosso,

This is a feature not a bug :wink: . Well, it might be a bug in your code, depending on how you view this.

What is happening:

  1. A SimpleITK image is created from the given numpy array.
  2. A numpy array view is created on the SimpleITK image.
  3. The SimpleITK image goes out of scope and is deleted, the view is now looking at junk memory.
  4. We look at the junk memory.

Because the GetArrayView is a view onto the SimpleITK data, it is only valid while we have a reference to the SimpleITK image. Once that reference is gone and the SimpleITK image is deleted, the view is invalid. This is a memory management feature.

In SimpleITK we want to have full knowledge and control of our data at all times, so we are defensive. If you want a writable numpy array SimpleITK can give you a copy of the image. If you want a readable numpy array you can get a copy or use a view, with the latter only valid while the SimpleITK image is valid.

Hope this clarifies things.

3 Likes

That makes sense, thank you for explaining!