How to volume render 3D numpy array using VTK (MarchingCubes) in Python? - python

I have a 3D numpy array and I am trying to volume render it using VTK. However, I get a completely different volume rendering when I visualise it. I suspect it has something to do with my conversion of numpy array to the VTK image format but I can't seem to figure out where I am going wrong. I have uploaded the numpy array here.
Can someone help me figure out where I'm going wrong?
This is my code:
#!/usr/bin/env python
import os
import numpy as np
ArrayDicom = np.load('test3.npy')
data_matrix = ArrayDicom
w, d, h = ArrayDicom.shape
colors = vtkNamedColors()
iso_value = 200
reader = vtkImageImport()
data_string = data_matrix.tobytes()
reader.CopyImportVoidPointer(data_string, len(data_string))
reader.SetDataScalarTypeToUnsignedChar()
reader.SetNumberOfScalarComponents(1)
reader.SetDataExtent(0, w-1, 0, d-1, 0, h-1)
reader.SetWholeExtent(0, w-1, 0, d-1, 0, h-1)
reader.Update()
volume = vtkImageData()
volume.DeepCopy(reader.GetOutput())
surface = vtkMarchingCubes()
surface.SetInputData(volume)
surface.ComputeNormalsOn()
surface.SetValue(0, iso_value)
renderer = vtkRenderer()
renderer.SetBackground(colors.GetColor3d('DarkSlateGray'))
render_window = vtkRenderWindow()
render_window.AddRenderer(renderer)
render_window.SetWindowName('MarchingCubes')
interactor = vtkRenderWindowInteractor()
interactor.SetRenderWindow(render_window)
mapper = vtkPolyDataMapper()
mapper.SetInputConnection(surface.GetOutputPort())
mapper.ScalarVisibilityOff()
actor = vtkActor()
actor.SetMapper(mapper)
actor.GetProperty().SetColor(colors.GetColor3d('MistyRose'))
renderer.AddActor(actor)
render_window.Render()
interactor.Start()
This is my volume rendering:
This is my expected volume rendering:

Numpy uses a different array ordering than VTK. You should be able to re-order w, h and d to get the right thing.
This is how you want it:
h, d, w = ArrayDicom.shape
OK, here's a conversion script that I used to convert to a VTK file:
import numpy as np
import SimpleITK as sitk
x = np.load("test3.npy")
y = sitk.GetImageFromArray(x)
sitk.WriteImage(y, "test3.vtk")
It's not as nice as correctly getting the VTK image import to work, but, well, I'm a SimpleITK guy, and I know that converting numpy works in SimpleITK.

Related

itk python: use filters e.g. to extract contour from blob in image

I'm trying to work with ITK in Python (instead of openCV as I'm mostly using 3D image data) but can't get the filters working.
I'll skip the exact error messages as they depend on what I'm trying. You can reproduce them with the example below based on the ITK documentation. I create a blob using a 2D Gaussian and then try to extract its contours.
The approximate_signed_distance_map_image_filter acts as expected but the contour_extractor2_d_image_filter crashes on me in various ways no matter what I do.
Any ideas on how to solve this?
Minimal (2D) example
import itk
import matplotlib.pyplot as plt
import numpy as np
fig, axs = plt.subplots(1,3)
print('creating blob from 2d gaussian histogram')
arr = np.random.multivariate_normal([0,0], [[1,0],[0,1]], 100000)
h = np.histogram2d(arr[:,0],arr[:,1], bins=[30,30])
axs[0].set_title('Blob')
axs[0].imshow(h[0], cmap='gray')
print('applying itk approximate_signed_distance_map_image_filter')
arr_image = itk.image_view_from_array(h[0])
asdm = itk.approximate_signed_distance_map_image_filter(arr_image, inside_value=1000, outside_value=0)
asdm_arr = itk.array_from_image(asdm)
axs[1].set_title('signed distance')
axs[1].imshow(asdm_arr)
print('applying itk contour_extractor2_d_image_filter')
ce2d = itk.contour_extractor2_d_image_filter(itk.output(asdm), contour_value=1000)
ce2d_arr = itk.array_from_image(ce2d)
# also not working
# ce2d = itk.ContourExtractor2DImageFilter.New()
# ce2d.SetInput(asdm);
# ce2d.SetContourValue(0);
# ce2d.Update()
# ce2d_arr = itk.array_from_image(ce2d.GetOutput())
axs[2].set_title('contour')
axs[2].imshow(ce2d_arr)
plt.show()

From numpy array to DICOM

My code reads a DICOM file, takes the pixel information to a numpy array then it modifies the numpy array. It uses lists because im trying to operate with multiple DICOM files at the same time.
I havent found any information on how to take my modified numpy array and make it a DICOM file again so i can use it outside Python.
#IMPORT
import cv2
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.pyplot as plt
import SimpleITK as sitk
from glob import glob
import pydicom as dicom
data_path = "C:\\Users\\oliva\\Desktop\\Py tesis\\dicom\\"
output_path = working_path = "C:\\Users\\oliva\\Desktop\\Py tesis\\dicom1\\"
path = glob(data_path + '/*.dcm')
#Checks if we are in the correct path
print ("Total of %d DICOM images.\nFirst 5 filenames:" % len(path))
print ('\n'.join(path[:14]))
data_set = []
for element in path:
imagen=sitk.ReadImage(element)
#imagen = cv2.imread(element)
array_imagen = sitk.GetArrayViewFromImage(imagen)
array2_imagen=array_imagen[0]
imagen_array_norm = np.uint8(cv2.normalize(array2_imagen, None, 0, 255, cv2.NORM_MINMAX))
data_set.append(imagen_array_norm)
#Check
print(len(data_set))
print(type(data_set[1]))
plt.imshow(data_set[4], cmap=plt.cm.gray)
#Equalization
data_set_eq = equal(data_set)
print(len(data_set_eq))
print(type(data_set_eq[6]))
plt.imshow(data_set_eq[7], cmap=plt.cm.gray)
#Filtering
data_set_m = median(data_set)
print(len(data_set_m))
print(type(data_set_m[6]))
plt.imshow(data_set_m[8], cmap=plt.cm.gray)
#Functions
def equal(data):
data_set_eq = []
for element in data_set:
imagen_array_eq = cv2.equalizeHist(element)
data_set_eq.append(imagen_array_eq)
return data_set_eq
def median(data):
data_set_m = []
for element in data_set:
imagen_array_m =cv2.medianBlur(element,5)
data_set_m.append(imagen_array_m)
return data_set_m
I would like some enlightenment on how to produce a DICOM file from my modified numpy array.
You can convert the numpy array back to a SimpleITK image, and then write it out as Dicom. The code would look something like this:
for x in data_set:
img = sitk.GetImageFromArray(x)
sitk.WriteImage(img, "your_image_name_here.dcm")
From the file name suffix, SimpleITK knows to write Dicom.
Note that the filtering you are doing can be accomplished within SimpleITK. You don't really need to use OpenCV. Check out the following filters in SimpleITK: IntensityWindowingImageFilter, AdaptiveHistogramEqualizationFilter, and MedianImageFilter.
https://itk.org/SimpleITKDoxygen/html/classitk_1_1simple_1_1IntensityWindowingImageFilter.html
https://itk.org/SimpleITKDoxygen/html/classitk_1_1simple_1_1AdaptiveHistogramEqualizationImageFilter.html
https://itk.org/SimpleITKDoxygen/html/classitk_1_1simple_1_1MedianImageFilter.html

How to create a TFRecord from a NumPy array

I'd like to 1. iterate through a directory of images and turn each image into a NumPy array. I think I have accomplished this with the following code:
import tensorflow as tf
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
from PIL import Image
import os
myimages = []
path_to_images = 'images_animation'
filenum = len([name for name in os.listdir(path_to_images) if os.path.isfile(os.path.join(path_to_images, name))])
#loops through available pngs
for p in range(1, filenum):
## Read in picture
fname = "images_animation/image%03d.png" % p
img = mpimg.imread(fname)
imgplot = plt.imshow(img)
# append AxesImage object to the list
myimages.append([imgplot])
for n, im in enumerate(myimages):
img = Image.open(fname).convert("L")
arr = np.array(img
print(arr)
If I can make this code better or more efficient, please feel free to tell me how.
Now, I'd like to 2. turn these NumPy arrays into TFRecords. What is the best way to do so? I'm near clueless as how to do this, so I have not done much to solve it myself, so I'm looking for a solution.

How to convert a vtkimage into a numpy array

I tried to deal with MHD image files with python and python-vtk. The file is placed in google drive : mhd . I want to convert it into numpy array and then split them according to a given value' 500 for instance '. then calculate the summary information. I followed the instruction of this [post] How to convert a 3D vtkDataSet into a numpy array? but it does not work for my case.
import vtk
imageReader = vtk.vtkMetaImageReader()
imageReader.SetFileName(testfile1)
imageReader.Update()
# from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy does not work for the data type issue
image = imageReader.GetOutput()
# List the dimensions of the image, for example
print image.GetDimensions()
pixelspace = imageReader.GetPixelSpacing()
an error comes here:
AttributeError: GetPixelSpacing
How could I achieve the conversion?
When I finish the data splitting, how could I save them back to mhd (or a raw data would be better ?)
Adapting from this thread... you can do the following:
import numpy as np
import vtk
from vtk.util.numpy_support import vtk_to_numpy
imr = vtk.vtkMetaImageReader()
imr.SetFileName('t10-Subvolume-resample_scale-1.mhd')
imr.Update()
im = imr.GetOutput()
rows, cols, _ = im.GetDimensions()
sc = im.GetPointData().GetScalars()
a = vtk_to_numpy(sc)
a = a.reshape(rows, cols, -1)
assert a.shape==im.GetDimensions()
where a will be a NumPy array containing the image data.
To get the channels right for later use as OpenCV image:
import vtk
import numpy as np
from vtk.util import numpy_support
def vtkImgToNumpyArray(vtkImageData):
rows, cols, _ = vtkImageData.GetDimensions()
scalars = vtkImageData.GetPointData().GetScalars()
resultingNumpyArray = numpy_support.vtk_to_numpy(scalars)
resultingNumpyArray = resultingNumpyArray.reshape(cols, rows, -1)
red, green, blue, alpha = np.dsplit(resultingNumpyArray, resultingNumpyArray.shape[-1])
resultingNumpyArray = np.stack([blue, green, red, alpha], 2).squeeze()
resultingNumpyArray = np.flip(resultingNumpyArray, 0)
return resultingNumpyArray

Displaying true-colour 2D RGB textures in a 3D plot?

I'm trying to make a 3D plot that consists of a series of 2D planes through an RGB stack, like this:
I know that it's possible to do this using mpl_toolkits.mplot3d by passing the x, y, z coordinates and the RGB(A) colours of each pixel to plot_surface:
import numpy as np
from matplotlib import pyplot as pp
from mpl_toolkits.mplot3d.axes3d import Axes3D
def plot_stack_slices(rgbstack, scale=(1., 1., 1.), z_interval=10.):
fig, ax = pp.subplots(1,1,subplot_kw={'projection':'3d'})
ax.invert_zaxis()
ax.hold(True)
sx, sy, sz = scale
nz, ny, nx, nc = rgbstack.shape
stack_xyz = np.mgrid[:nx*sx:nx*1j, :ny*sy:ny*1j, :nz*sz:nz*1j]
slices = rgbstack[::-z_interval]
slice_xyz = np.rollaxis(stack_xyz, 3, 0)[::-z_interval]
surflist = []
for (img,xyz) in zip(slices, slice_xyz):
x, y, z = xyz
s = ax.plot_surface(x, y, z, facecolors=img**0.75,
rstride=50, cstride=50)
surflist.append(s)
return fig, ax, surflist
Unfortunately this becomes extremely slow if I set rstride=1, cstride=1 in order to display the textures at full resolution.
I'm also aware that Mayavi can easily handle displaying multiple 2D textures at full resolution:
from mayavi import mlab
def plot_stack_slices2(stack, scale=(1., 1., 20.), z_interval=10.):
mfig = mlab.figure(bgcolor=(1,)*3)
sx, sy, sz = scale
nz, ny, nx = stack.shape
slices = stack[::-z_interval]
slice_z = np.linspace(0,nz*sz,nz)[::z_interval]
surflist = []
for (img,z) in zip(slices, slice_z):
im = mlab.imshow(img.T, colormap='gray', figure=mfig)
im.actor.scale = [sx,sy,sz]
im.actor.position = [0, 0, z]
surflist.append(z)
return fig, surflist
However, the problem now is that there does not seem to be any way of displaying true-colour RGB textures using Mayavi - according to the docs I can only specify either a single (R, G, B) tuple, or a pre-defined colourmap.
Does anyone know of a better way to display true-colour 2D RGB textures in a 3D plot?
Given enough time I could probably figure out how do do this in Vtk or even pure OpenGL if necessary, but I'm really hoping that there are existing libraries that will do the job.
Big thanks to aestrivex for providing working solutions using Mayavi/VTK - it's useful info that I may need for doing more complicated things in the future.
In the end I actually chose to go with cgohlke's suggestion of using visvis, which turned out to be a lot simpler to implement:
import visvis as vv
vv.use('wx')
import numpy as np
from matplotlib.image import imread
from matplotlib.cbook import get_sample_data
imgdata = imread(get_sample_data('lena.png'))
nr, nc = imgdata.shape[:2]
x,y = np.mgrid[:nr, :nc]
z = np.ones((nr, nc))
for ii in xrange(5):
vv.functions.surf(x, y, z*ii*100, imgdata, aa=3)
I don't know about other libraries -- volshow looks neat but I havent tested it -- but you can do this in vtk.
I have been working on doing this generally in mayavi (see How to directly set RGB/RGBA colors in mayavi) but for certain image sources mayavi structures the vtk pipeline in a way that was not designed to deal with this at all. My efforts to convert a 2D vtk.ImageData to true color starting with mlab.imshow were met with resistance at every step, but I managed it.
First, here is how I have managed to do it in mayavi using mlab. This is far too hacky and "magic"-reliant even for my standards:
from mayavi import mlab
import numpy as np
from tvtk.api import tvtk
k=mlab.imshow(np.random.random((10,10)),colormap='bone')
colors=tvtk.UnsignedCharArray()
colors.from_array(np.random.randint(256,size=(100,3)))
k.mlab_source.dataset.point_data.scalars=colors
k.actor.input.point_data.scalars=colors
#the latter set of scalars is what is actually used in the VTK pipeline in this
#case, but if they don't play nice with the mayavi source then tvtk will
#complain because we are circumventing the structure it expects
k.actor.input.scalar_type='unsigned_char'
k.actor.input.number_of_scalar_components=3
k.image_map_to_color.lookup_table=None
k.actor.input.modified()
mlab.draw()
#this draw fails. As it fails, there is an interaction here, somewhere deep in
#tvtk, causing the ImageData to partially reset.. I have not been able to track
#it down yet. ignore the error output
k.actor.input.scalar_type='unsigned_char'
k.actor.input.number_of_scalar_components=3
#now after we reset these back to what they should be, it works
mlab.draw()
mlab.show()
But in pure tvtk it's not nearly so bad:
import numpy as np
from tvtk.api import tvtk
colors=np.random.randint(256,size=(100,3))
an_image=tvtk.ImageData()
an_image.number_of_scalar_components=3
an_image.scalar_type='unsigned_char'
an_image.point_data.scalars=tvtk.UnsignedCharArray()
an_image.point_data.scalars.from_array(colors)
an_image.dimensions=np.array((10,10,1))
an_actor=tvtk.ImageActor()
an_actor.input=an_image
an_actor.interpolate=False
ren=tvtk.Renderer()
renWin=tvtk.RenderWindow()
renWin.add_renderer(ren)
ren.add_actor2d(an_actor)
iren=tvtk.RenderWindowInteractor()
iren.render_window=renWin
iren.interactor_style=tvtk.InteractorStyleTrackballCamera()
renWin.render()
iren.start()
Of course, doing it in vtk is more work. You might even be able to wrap this nicely so that it's pretty reasonable.
I want to fix mayavi to handle this properly, but as you can see from my snippet it is not straightforward and could take a while.

Categories