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
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()
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?
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.
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
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.