How to perform 3D volumetric plotting using 3D arrays on plotly? - matplotlib

I would to perform a 3D volumetric plot using 3D numpy arrays on plotly (something similar to using the isosurface function on MATLAB). The arrays contain 10 slices of images of size 512 by 512 - shape = (10, 512, 512). I followed one of the examples on the plotly site (https://plot.ly/python/3d-volume-plots/) but it returned me an empty plot instead. Why is this the case?
My code is as shown below:
import cv2
import skimage.io as skio
import glob
import os
import numpy as np
import pyvista as pv
import plotly.graph_objects as go
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
def plot3D(img_dir):
#Read images into array
img_list = []
index = 0
for img in os.listdir(img_dir):
img_individual = cv2.imread(os.path.join(img_dir,img), cv2.IMREAD_GRAYSCALE)
img_list.append([img_individual])
index += 1 #Count the number of images appended into the list
print(np.shape(img_list)) #shape = (10,1,512,512)
img_listtoarray = np.asarray(img_list) #Convert list to numpy array
img_array = np.ones((index,512,512))
print(np.shape(img_array))
i = 0
j = 0
k = 0
#Reduce 4D array into 3D array of size (10,512,512)
for i in range(index):
while(j < 512):
while(k < 512):
img_array[i,j,k] = img_listtoarray[i,0,j,k]
k += 1
j += 1
k = 0
j = 0
print(np.shape(img_array)) #shape = (10,512,512)
#Create meshgrid
Z, X, Y = np.mgrid[1:10:5j,1:512:5j,1:512:5j] #Check dimensions
fig = go.Figure(data = go.Volume(
x = Z.flatten(),
y = X.flatten(),
z = Y.flatten(),
value = img_array,
isomin = 0.1,
isomax = 0.8,
opacity = 0.3,
surface_count = 30
))
fig.show()
plot3D("train/result_processed/")
This will used be for the 3D image construction of a MDCK cell spheroid by using the segmented image slices as shown in the link:
All of the images to be used are of uint8 type.
Thank you.

Related

How to plot a 3D function with colors given spacing 2D input

Let's assume I have 3 arrays defined as:
v1=np.linspace(1,100)
v2=np.linspace(1,100)
v3=np.linspace(1,100)   
Then I have a function that takes those 3 values and gives me the desired output, let's assume it is like:
f = (v1 + v2*10)/v3
I want to plot that function on a 3D plot with axis v1,v2,v3 and color it's surface depending on its value.
More than the best way to plot it, I was also interested in how to scroll all the values in the in vectors and build the function point by point.
I have been trying with for loops inside other for loops but I am always getting one error.
MANY THANKS
I tried this but i'm always getting a line instead of a surface
import mpl_toolkits.mplot3d.axes3d as axes3d
import sympy
from sympy import symbols, Function
# Parameters I use in the function
L = 132
alpha = 45*math.pi/180
beta = 0
s,t = symbols('s,t')
z = Function('z')(s,t)
figure = plt.figure(figsize=(8,8))
ax = figure.add_subplot(1, 1, 1, projection='3d')
# experiment with various range of data in x and y
x1 = np.linspace(-40,-40,100)
y1 = np.linspace(-40,40,100)
x,y = np.meshgrid(x1,y1)
# My function Z
c1=math.cos(beta)**2
c2=math.cos(alpha)**2
s1=math.sin(alpha)**2
den = math.sqrt((c1*c2)+s1)
z=L*((math.cos(beta)/den)-1)+(s*(math.sin(alpha)))+(t*(1-math.cos(alpha)))
ax.plot_surface(x,y,z,cmap='rainbow')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
plt.show()
In this example I'm going to show you how to achieve your goal. Specifically, I use Numpy because it supports vectorized operations, hence I avoid for loops.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
import matplotlib.cm as cm
# Parameters I use in the function
L = 132
alpha = 45*np.pi/180
beta = 0
figure = plt.figure()
ax = figure.add_subplot(1, 1, 1, projection='3d')
# experiment with various range of data in x and y
x1 = np.linspace(-40,40,100)
y1 = np.linspace(-40,40,100)
x,y = np.meshgrid(x1,y1)
# My function Z
c1=np.cos(beta)**2
c2=np.cos(alpha)**2
s1=np.sin(alpha)**2
den = np.sqrt((c1*c2)+s1)
z=L*((np.cos(beta)/den)-1)+(x*(np.sin(alpha)))+(y*(1-np.cos(alpha)))
# compute the color values according to some other function
color_values = np.sqrt(x**2 + y**2 + z**2)
# normalize color values between 0 and 1
norm = Normalize(vmin=color_values.min(), vmax=color_values.max())
norm_color_values = norm(color_values)
# chose a colormap and create colors starting from the normalized values
cmap = cm.rainbow
colors = cmap(norm_color_values)
surf = ax.plot_surface(x,y,z,facecolors=colors)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# add a colorbar
figure.colorbar(cm.ScalarMappable(norm=norm, cmap=cmap), label="radius")
plt.show()

How to tell `photutils` to plot only apertures which satisfy a condition?

I'm following an example in the photutils documentation to detect sources in an image:
from astropy.stats import sigma_clipped_stats
from photutils.datasets import load_star_image
import numpy as np
import matplotlib.pyplot as plt
from astropy.visualization import SqrtStretch
from astropy.visualization.mpl_normalize import ImageNormalize
from photutils.detection import DAOStarFinder
from photutils.aperture import CircularAperture
# Load image
hdu = load_star_image() # load a star image from the dataset
data = hdu.data[0:101, 0:101]
mean, median, std = sigma_clipped_stats(data, sigma = 3.0) # estimate noise
# Find stars in the image that have FWHMs of 3 pixels and peaks ~ 5 sigma > bg
daofind = DAOStarFinder(fwhm = 3.0, threshold = 5.*std)
sources = daofind(data - median)
# Print position and photometric data for each star in the image
for col in sources.colnames:
sources[col].info.format = '%.8g' # for consistent table output
positions = np.transpose((sources['xcentroid'], sources['ycentroid']))
apertures = CircularAperture(positions, r = 4.)
norm = ImageNormalize(stretch = SqrtStretch())
plt.imshow(data, cmap = 'Greys', origin = 'lower', norm = norm,
interpolation = 'nearest')
for i in range(len(sources)):
if sources[i][-1] < -2:
print(sources[i][-1])
apertures.plot(color = 'r', lw = 1.5, alpha = 0.5
Which produces
I've added the last four lines, with the intention to plot apertures around only the brightest stars. However, the for loop doesn't change the image. I understand why (it's plotting all apertures multiple times, once for each of the 4 stars with mag < -2), but how do I change it to plot them for only those stars?

How to convert 2D DICOM slices to 3D image in Python

I am currently sitting on an task in which I need to plot DICOM slices into one 3D model using NumPy, Matplotlib, (Marchingcubes, Triangulation or Volumemodel)
I have tried the method from this website :
https://www.raddq.com/dicom-processing-segmentation-visualization-in-python/
but unfortunately it didn't worked out for me
import pydicom
import numpy as np
import os
import matplotlib.pyplot as plt
import ipywidgets as widgets
from ipywidgets import interact, fixed
filesNew = []
datenSatz = []
output_path = './Head/'
print()
def load_scan(path):
slices = [pydicom.read_file(path + '/' + s) for s in os.listdir(path)]
slices.sort(key = lambda x: int(x.InstanceNumber))
try:
slice_thickness = np.abs(slices[0].ImagePositionPatient[2] - slices[1].ImagePositionPatient[2])
except:
slice_thickness = np.abs(slices[0].SliceLocation - slices[1].SliceLocation)
for s in slices:
s.SliceThickness = slice_thickness
return slices
for s in load_scan('./Head/'):
h = s.pixel_array
datenSatz.append(s) #dataSet from the patient
filesNew.append(h) #pixel_array
def show_image(image_stack, sliceNumber):
pxl_ar = image_stack[sliceNumber]
#print(np.array_equal(pxl_ar,filesNew[sliceNumber]))
plt.imshow(pxl_ar, cmap= plt.cm.gray)
plt.show()
slider = widgets.IntSlider(min=0,max=len(filesNew)-1,step=1,value = 0, continuous_update=False)
interact(show_image, image_stack = fixed(filesNew), sliceNumber = slider);
DICOM slices visualized
There is an example of loading a set of 2D CT slices and building a 3D array.
https://github.com/pydicom/pydicom/blob/master/examples/image_processing/reslice.py
It does not go on to construct the surface, but it should solve the first half of your problem.

Orientation of matplotlib 2d patches transformed to 3d with arbitrary normals do not match orientation given by quiver

I have followed
How can matplotlib 2D patches be transformed to 3D with arbitrary normals?
to transform a matplotlib 2d patch (circle) into a 3d patch with an arbitrary normal vector. However, when I plot this normal vector using quiver, it turns out that the patch and the vector are not perpendicular.
Here is my code (where I load the functions given as 2nd Answer in link above):
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import art3d
from mpl_toolkits.mplot3d import proj3d
def rotation_matrix(v1,v2):
"""
Calculates the rotation matrix that changes v1 into v2.
"""
v1/=np.linalg.norm(v1)
v2/=np.linalg.norm(v2)
cos_angle=np.dot(v1,v2)
d=np.cross(v1,v2)
sin_angle=np.linalg.norm(d)
if sin_angle == 0:
M = np.identity(3) if cos_angle>0. else -np.identity(3)
else:
d/=sin_angle
eye = np.eye(3)
ddt = np.outer(d, d)
skew = np.array([[ 0, d[2], -d[1]],
[-d[2], 0, d[0]],
[d[1], -d[0], 0]], dtype=np.float64)
M = ddt + cos_angle * (eye - ddt) + sin_angle * skew
return M
def pathpatch_2d_to_3d(pathpatch, z = 0, normal = 'z'):
"""
Transforms a 2D Patch to a 3D patch using the given normal vector.
The patch is projected into they XY plane, rotated about the origin
and finally translated by z.
"""
if type(normal) is str: #Translate strings to normal vectors
index = "xyz".index(normal)
normal = np.roll((1,0,0), index)
path = pathpatch.get_path() #Get the path and the associated transform
trans = pathpatch.get_patch_transform()
path = trans.transform_path(path) #Apply the transform
pathpatch.__class__ = art3d.PathPatch3D #Change the class
pathpatch._code3d = path.codes #Copy the codes
pathpatch._facecolor3d = pathpatch.get_facecolor #Get the face color
verts = path.vertices #Get the vertices in 2D
M = rotation_matrix(normal,(0, 0, 1)) #Get the rotation matrix
pathpatch._segment3d = np.array([np.dot(M, (x, y, 0)) + (0, 0, z) for x, y in verts])
def pathpatch_translate(pathpatch, delta):
"""
Translates the 3D pathpatch by the amount delta.
"""
pathpatch._segment3d += delta
fig = plt.figure()
ax = fig.gca(projection='3d')
from matplotlib.patches import Circle, PathPatch
dirvec =(-0.420, -0.757, -0.500)
normal=dirvec
p = Circle((0,0), 18., facecolor = 'g', alpha = .6)
ax.add_patch(p)
pathpatch_2d_to_3d(p, z = 0, normal = normal)
pathpatch_translate(p, (0.,0.,0.) )
ax.quiver( 0.,0.,0., -0.420, -0.757, -0.500, length=50, color='g', lw=2, pivot='tail')
xlim( -50., 50. );ylim( -50., 50. );ax.set_zlim(-50.,50)
The result is not far from perpendicular but is clearly not exactly 90 degrees as should be. I appreciate any help to clarify what is going on. Thank you

PolyCollection doesn't work

I have a problem with PolyCollection matplotlib when I work with python 2.5. In random mode, it shows me following error: array dimensions must agree except for d_0 (file:collection.py - xy = np.concatenate([xy, np.zeros((1,2))])). This is my code:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.collections import PolyCollection
from matplotlib.colors import colorConverter
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.font_manager as fm
from matplotlib.patches import Rectangle
import matplotlib.cm as cm
colors = ['#be1e2d',
'#666699',
'#92d5ea',
'#ee8310',
'#8d10ee',
'#5a3b16',
'#26a4ed',
'#f45a90',
'#e9e744']
row_names = ['2005','2006','2007']
data = [[1,1,1,1,1,1],[2,2,2,2,2,2],[4,4,4,4,4,4],[5,5,5,5,5,5],[7,7,7,7,7,7],[8,8,8,8,8,8]]
column_names = ['Ri','Pe']
#0 to start and end list
i=0
for i in range(len(data)):
data[i].append(0)
for i in range(len(data)):
data[i].insert(0,0)
dpi = 50.0
width = 460
height = 440
fig = plt.figure(1, figsize=(width/dpi,height/dpi),facecolor='w')
ax = fig.gca(projection='3d')#,azim=40, elev=0)
#Build axes
size = len(row_names) * len(data[0])
zs = np.arange(len(data))
# Setto le properties dei font
fp = fm.FontProperties()
fp.set_size('xx-small')
#Build Graph
verts = []
step = 1.0/len(data[0])
vertsColor = []
#Verify Single series or not
if len(column_names) > 1:
idx = 0
xs = np.arange(0, size, step)
change_color = len(column_names) - 1
for z in zs:
verts.append(zip(xs, data[z]))
vertsColor.append(colors[idx])
if idx == change_color:
idx = 0
else:
idx = idx + 1
################################################
# I THINK THE PROBLEM IS HERE
poly = PolyCollection(verts,facecolors=vertsColor)
ax.add_collection3d(poly, zs=zs, zdir='y')
################################################
ax.set_ylim3d(0, len(row_names)*len(column_names))
zs = np.arange(0,len(row_names) * len(column_names), len(column_names))
ax.set_yticks(zs)
lim = ((size*step)-step) - (len(row_names) - 1)
ax.set_xlim3d(0, lim)
rect = []
serie = []
#Build legend
for i in range(len(column_names)):
rect.insert(i,Rectangle((0,0), 1,1, facecolor=colors[i]))
serie.insert(i,column_names[i])
ax.legend((rect), (serie), loc=3, ncol=3, prop=fp)
else:
xs = np.arange(0, size, step)
for z in zs:
verts.append(zip(xs, data[z]))
poly = PolyCollection(verts,facecolors=colors) #[:len(data)])
poly.set_alpha(0.6)
ax.add_collection3d(poly, zs=zs, zdir='y')
ax.set_xlabel('Rec')
lim = ((size*step)-step) - (len(row_names) - 1)
ax.set_xlim3d(0, lim)
ax.set_yticks(zs)
ax.set_ylim3d(0, len(row_names))
#Find Max Value
max_value = 0
i=0
for i in data:
mass = max(i)
if mass > max_value:
max_value = mass
#Font Label X,Y,Z
for label in ax.get_xticklabels():
label.set_fontproperties(fp)
for label in ax.get_yticklabels():
label.set_fontproperties(fp)
for label in ax.get_zticklabels():
label.set_fontproperties(fp)
ax.set_xticklabels('')
ax.set_ylabel('Years')
ax.set_yticklabels(row_names, fontproperties = fp)
ax.set_zlabel('Values')
ax.set_zlim3d(0, max_value)
ax.set_title('Test',x=0.5, y=1)
plt.show()
THANKS.