Array passing without copying
In [ ]:
import shapeworks as sw
In [ ]:
import numpy as np
shapeworks Image from numpy array¶
In [ ]:
dims = (1,3,2) # NOTE: numpy dims are specified in z, y, x order
farr = np.ndarray(dims, dtype=np.float32)
ival = 10; jval = 50; kval = 1.75
for i in range(0, farr.shape[2]):
for j in range(0, farr.shape[1]):
for k in range(0, farr.shape[0]):
farr[k][j][i] = ival*(i/farr.shape[2]) + jval*(j/farr.shape[1]) + kval/farr.shape[0]
In [ ]:
farr.mean()
In [ ]:
farr.dtype
In [ ]:
farr.flags['OWNDATA']
In [ ]:
farrimg = sw.Image(farr)
farrimg # NOTE: sw.Image dims are specified in x, y, z order
In [ ]:
farrimg.mean()
While the numpy can still look at the memory, it no longer has ownership:¶
In [ ]:
farr.flags['OWNDATA']
In [ ]:
farrimg += 100
In [ ]:
farrimg.mean()
In [ ]:
farr.mean()
...so the safest thing to do now is let the array go out of scope:¶
- having used a temporary during Image construction:
img = sw.Image(np.array(arr))
- variable replacement after Image construction:
arr = np.zeros(1)
- explicit deletion after Image construction:
del arr
In [ ]:
del farr
Only dtype.float32 arrays can be used to initialize an image:¶
In [ ]:
dims = (12,3,21)
darr = np.ndarray(dims, dtype=np.float64)
ival = 10; jval = 50; kval = 1.75
for k in range(0, dims[0]):
for j in range(0, dims[1]):
for i in range(0, dims[2]):
darr[k][j][i] = ival*(i/darr.shape[2]) + jval*(j/darr.shape[1]) + kval/darr.shape[0]
In [ ]:
darr.dtype
In [ ]:
darr.flags['OWNDATA']
In [ ]:
# note: this try/catch is only used so the notebook runs to completion; not typically necessary
try:
darrimg = sw.Image(darr) # Throws an exception because dtype must be same as Image's pixel type
except Exception as ex:
print(ex)
In [ ]:
darrimg = sw.Image(np.array(darr, dtype=np.float32)) # Makes a copy of the array when passsed
darrimg
No unnecessary copies and no memory leaks!¶
In [ ]:
darr.flags['OWNDATA']
In [ ]:
darrimg.mean()
In [ ]:
darr.mean()
In [ ]:
darrimg += 50
In [ ]:
darrimg.mean()
In [ ]:
darr.mean()
In [ ]:
darr *= 10
In [ ]:
darrimg.mean()
In [ ]:
darr.mean()
In [ ]:
ellipsoid_path = "../../../Testing/data/1x2x2.nrrd"
femur_path = "../../../Testing/data/femur.nrrd"
In [ ]:
img = sw.Image(ellipsoid_path)
img
In [ ]:
arr = img.toArray()
arr.dtype
In [ ]:
arr.mean()
In [ ]:
img.mean()
In [ ]:
arr.shape # remember, numpy dims are zyx and Image dims are xyz
In [ ]:
img.dims()
The numpy array references the memory of the current Image and can change it:¶
In [ ]:
arr += 100
In [ ]:
img.mean()
In [ ]:
arr.mean()
In [ ]:
arr.flags['OWNDATA']
In [ ]:
del arr
If a copy is needed, pass copy=True
to toArray()
¶
In [ ]:
arr = img.toArray(copy=True)
arr.flags['OWNDATA']
This can be useful when the array is created from a temporary Image:¶
In [ ]:
arr = sw.Image(ellipsoid_path).toArray(copy=True)
arr.mean()
In [ ]:
def use_arr(arr):
return arr.mean()
In [ ]:
use_arr(sw.Image(ellipsoid_path).toArray(copy=True))
In [ ]:
import pyvista as pv
In [ ]:
pv.set_jupyter_backend(backend="ipyvtklink")
In [ ]:
#help(pv.Plotter)
In [ ]:
plotter = pv.Plotter(shape = (1, 1),
notebook = True,
border = True)
plotter.add_axes()
plotter.add_bounding_box()
#plotter.show_bounds() # for some reason extremely slow on osx
#plotter.show_grid() # for some reason extremely slow on osx
In [ ]:
# NOTE: pyvisya-wrapped vtk images require 'F' ordering to prevent copying
arr = img.toArray(for_viewing = True) # 'F' is `for_viewing`
arr.flags
In [ ]:
arr.flags
In [ ]:
# sw2vtkImage takes care of this for us
vtkimg = sw.sw2vtkImage(img, verbose=True)
In [ ]:
vol = plotter.add_volume(vtkimg, shade=True, show_scalar_bar=True)
In [ ]:
plotter.show()
In [ ]:
plotter = pv.Plotter(shape = (1, 1),
notebook = True,
border = True)
plotter.add_axes()
In [ ]:
img1 = sw.Image(femur_path)
In [ ]:
img1.setSpacing((1.5, 0.75, 1)) # set spacing to show that it's preserved on both copy and assign
In [ ]:
img2 = sw.Image(img1) # make a copy to be processed by a scipy Python filter (spacing preserved)
Let's use a scipy operation on the image:¶
In [ ]:
from scipy import ndimage
In [ ]:
ck = ndimage.gaussian_filter(img2.toArray(), 12.0)
The return from this filter is the right size and type, but it's a copy:¶
In [ ]:
ck.shape
In [ ]:
ck.dtype
In [ ]:
ck.flags['OWNDATA']
Let's assign it back to Image so we can retain Image's origin, scale, and coordsys:¶
In [ ]:
img2.assign(ck)
In [ ]:
# notice numpy array ownership has been transferred to Image
ck.flags['OWNDATA']
Now we can look at it again in the plotter:¶
In [ ]:
plotter.add_volume(sw.sw2vtkImage(img2), shade=True, show_scalar_bar=True)
plotter.add_volume(sw.sw2vtkImage(img1), shade=True, show_scalar_bar=True)
In [ ]:
plotter.show()