The main focus of PyCA is to provide data types (for images and vector fields) and corresponding C++/CUDA operations. We import the Core module as follows:
import PyCA.Core as ca
The main data types in PyCA are Image3D's and Field3D's, objects that represent a block of memory containing floats. Each Image3D/Field3D is described by two attributes: its grid
and memType
.
For its grid, we declare a GridInfo object that describes a grid with the grid's size, spacing and origin. By default the spacing is (1,1,1) and the origin is (0,0,0). We declare these objects using other data types: Vec3Di and Vec3Df (integer and float 3D vectors).
For an object's memory type, there are two options: MEM_HOST
and MEM_DEVICE
for CPU and GPU memory.
grid = ca.GridInfo(ca.Vec3Di(20, 20, 20), ca.Vec3Df(1.0, 1.0, 1.0), ca.Vec3Df(0.0, 0.0, 0.0))
grid = ca.GridInfo(ca.Vec3Di(20, 20, 20)) #equivalent
memType = ca.MEM_DEVICE #for GPU Memory
Im1 = ca.Image3D(grid, memType)
Im2 = ca.Image3D(grid, memType)
print Im1
VF1 = ca.Field3D(Im1.grid(), Im1.memType())
VF2 = ca.Field3D(Im1.grid(), Im1.memType())
print VF1
Image3D: size (20, 20, 20) origin (0 0 0) spacing (1 1 1) MemType: MEM_DEVICE Field3D: size (20, 20, 20) origin (0 0 0) spacing (1 1 1) MemType: MEM_DEVICE
We can also change the grid and memType of current Image3D's/Field3D's, provided that we don't make the grid larger than the current pre-allocated memory.
grid2 = ca.GridInfo(ca.Vec3Di(20, 20, 20), ca.Vec3Df(.5, .5, .5), ca.Vec3Df(-19.0, -19.0, -19.0))
memTypeCPU = ca.MEM_HOST
Im1.setGrid(grid2)
Im1.toType(memTypeCPU)
print Im1
Im1.setGrid(grid) #Change back
Im1.toType(ca.MEM_DEVICE)
Image3D: size (20, 20, 20) origin (-19 -19 -19) spacing (0.5 0.5 0.5) MemType: MEM_HOST
Although our data types are called Image3D's and Field3D's, it is trivial to work with 2D Images. In PyCA, a 2D image is an Image3D with size (M x N x 1), and likewise for a 2D Field - and everything will work as expected.
Currently, our Image3D and Field3D are just allocated memory and don't contain any meaningful data. We can set their values as follows
ca.SetMem(Im1, 1.0) #for an Image of all ones
ca.SetMem(VF1, ca.Vec3Df(1.0, 2.0, 3.0)) #For a Field that is (1, 2, 3) everywhere
ca.SetToZero(VF1) # V Field where each element is (0, 0, 0)
ca.SetToIdentity(VF1) # H Field where each element (x, y, z)
Another common way to set the data is to copy it from a previously defined Image3D or Field3D, and in fact, we can copy between an Image3D and a Field3D provided we give the correct index (x=0, y=1, z=2)
ca.SetMem(Im1, 4.0)
ca.Copy(Im2, Im1) # element-wise copy, Im2 = Im1
ca.Copy(VF1, Im2, 1) #copy into the y field: VF1[:,:,:,1] = Im2
ca.Copy(Im2, VF1, 2) #copy from the y field:
We can transfer memory contents between an Image3D/Field3D and a Numpy array:
The functions ImFromNPArr and FieldFromNPArr each take a Numpy array and copy it into a Image3D/Field3D. This is the easiest way to interact with Numpy arrays, but it is also the slowest.
import numpy as np
import PyCA.Common as common
np_arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) #create sample 3x3 numpy array
Im2 = common.ImFromNPArr(np_arr, ca.MEM_DEVICE) #create Image3D from the numpy array
print Im2.grid()
Grid: size (3, 3, 1) origin (0 0 0) spacing (1 1 1)
Notice that here a 2D array was numpy array was given and was turned into an Image3D with size (3, 3, 1). ImFromNPArr expects either a MxN numpy array or an MxNxK numpy array. Also notice that the spacing and origin of the grid were set by default to (1, 1, 1) and (0, 0, 0). We can do a similar thing with a Field3D. FieldFromNpArr expects either a MxNx2 numpy array or a MxNxKx3 numpy array.
np_arr_2D = np.zeros((20, 20, 2))
VF_2D = common.FieldFromNPArr(np_arr_2D, ca.MEM_DEVICE)
print VF_2D.grid()
np_arr_3D = np.zeros((20, 20, 20, 3))
VF_3D = common.FieldFromNPArr(np_arr_3D, ca.MEM_DEVICE)
print VF_3D.grid()
Grid: size (20, 20, 1) origin (0 0 0) spacing (1 1 1) Grid: size (20, 20, 20) origin (0 0 0) spacing (1 1 1)
The *FromNpArr functions are inherently slow. However, since Image3D's and Field3D's are blocks of memory (much like Numpy arrays), we can interact with Image3D's as numpy arrays using the .asnp() member function. However, since Numpy arrays only exist on host memory, the Image3D/Field3D is required to have memType of MEM_HOST.
Im = ca.Image3D(ca.GridInfo(ca.Vec3Di(4, 4, 1)), ca.MEM_HOST)
ca.SetMem(Im, 0.0)
Imnp = Im.asnp()
# now we can treat Imnp like we would any numpy array
Imnp[1:3, 1:3, :] = 5.0
print Im
print np.squeeze(Imnp)
print type(Imnp)
Image3D: size (4, 4, 1) origin (0 0 0) spacing (1 1 1) MemType: MEM_HOST [[ 0. 0. 0. 0.] [ 0. 5. 5. 0.] [ 0. 5. 5. 0.] [ 0. 0. 0. 0.]] <type 'numpy.ndarray'>
In this code, Im
and Imnp
represent the same block of memory, so changing the contents of Imnp
also changes the contents of Im
. However, we can treat Imnp
in the same way as any Numpy array, and supports Numpy array operatins (such as indexing, etc.). We can also copy the memory over using Numpy's copy function
Imnp2 = Im.asnp().copy()
This method is still much quicker than using the *FromNpArr functions