Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0f2e17b

Browse files
committed
Add support for x,y,z arguments
1 parent b16f70a commit 0f2e17b

File tree

6 files changed

+200
-20
lines changed

6 files changed

+200
-20
lines changed

examples/mplot3d/voxels_numpy_logo.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'''
2+
===============================
3+
3D voxel plot of the numpy logo
4+
===============================
5+
6+
Demonstrates using ``ax.voxels`` with uneven coordinates
7+
'''
8+
import matplotlib.pyplot as plt
9+
import numpy as np
10+
from mpl_toolkits.mplot3d import Axes3D
11+
12+
13+
def explode(data):
14+
size = np.array(data.shape)*2
15+
data_e = np.zeros(size - 1, dtype=data.dtype)
16+
data_e[::2, ::2, ::2] = data
17+
return data_e
18+
19+
# build up the numpy logo
20+
n_voxels = np.zeros((4, 3, 4), dtype=bool)
21+
n_voxels[0, 0, :] = True
22+
n_voxels[-1, 0, :] = True
23+
n_voxels[1, 0, 2] = True
24+
n_voxels[2, 0, 1] = True
25+
facecolors = np.where(n_voxels, '#FFD65DC0', '#7A88CCC0')
26+
edgecolors = np.where(n_voxels, '#BFAB6E', '#7D84A6')
27+
filled = np.ones(n_voxels.shape)
28+
29+
# upscale the above voxel image, leaving gaps
30+
filled_2 = explode(filled)
31+
fcolors_2 = explode(facecolors)
32+
ecolors_2 = explode(edgecolors)
33+
34+
# Shrink the gaps
35+
x, y, z = np.indices(np.array(filled_2.shape) + 1).astype(float) // 2
36+
x[0::2, :, :] += 0.05
37+
y[:, 0::2, :] += 0.05
38+
z[:, :, 0::2] += 0.05
39+
x[1::2, :, :] += 0.95
40+
y[:, 1::2, :] += 0.95
41+
z[:, :, 1::2] += 0.95
42+
43+
fig = plt.figure()
44+
ax = fig.gca(projection='3d')
45+
ax.voxels(x, y, z, filled_2, facecolors=fcolors_2, edgecolors=ecolors_2)
46+
47+
plt.show()

examples/mplot3d/voxels_rgb.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,36 @@
1010
import numpy as np
1111
from mpl_toolkits.mplot3d import Axes3D
1212

13+
14+
def midpoints(x):
15+
sl = ()
16+
for i in range(x.ndim):
17+
x = (x[sl + np.index_exp[:-1]] + x[sl + np.index_exp[1:]]) / 2.0
18+
sl += np.index_exp[:]
19+
return x
20+
1321
# prepare some coordinates, and attach rgb values to each
14-
x, y, z = np.indices((16, 16, 16))
15-
r = (x + 0.5) / 16
16-
g = (y + 0.5) / 16
17-
b = (z + 0.5) / 16
22+
r, g, b = np.indices((17, 17, 17)) / 16.0
23+
rc = midpoints(r)
24+
gc = midpoints(g)
25+
bc = midpoints(b)
1826

1927
# define a sphere about [0.5, 0.5, 0.5]
20-
sphere = (r - 0.5)**2 + (g - 0.5)**2 + (b - 0.5)**2 < 0.5**2
28+
sphere = (rc - 0.5)**2 + (gc - 0.5)**2 + (bc - 0.5)**2 < 0.5**2
2129

2230
# combine the color components
2331
colors = np.zeros(sphere.shape + (3,))
24-
colors[..., 0] = r
25-
colors[..., 1] = g
26-
colors[..., 2] = b
32+
colors[..., 0] = rc
33+
colors[..., 1] = gc
34+
colors[..., 2] = bc
2735

2836
# and plot everything
2937
fig = plt.figure()
3038
ax = fig.gca(projection='3d')
31-
ax.voxels(sphere,
39+
ax.voxels(r, g, b, sphere,
3240
facecolors=colors,
3341
edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter
3442
linewidth=0.5)
43+
ax.set(xlabel='r', ylabel='g', zlabel='b')
3544

3645
plt.show()

examples/mplot3d/voxels_torus.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
'''
2+
=======================================================
3+
3D voxel / volumetric plot with cylindrical coordinates
4+
=======================================================
5+
6+
Demonstrates using the ``x, y, z`` arguments of ``ax.voxels``.
7+
'''
8+
9+
import matplotlib.pyplot as plt
10+
import matplotlib.colors
11+
import numpy as np
12+
from mpl_toolkits.mplot3d import Axes3D
13+
14+
15+
def midpoints(x):
16+
sl = ()
17+
for i in range(x.ndim):
18+
x = (x[sl + np.index_exp[:-1]] + x[sl + np.index_exp[1:]]) / 2.0
19+
sl += np.index_exp[:]
20+
return x
21+
22+
# prepare some coordinates, and attach rgb values to each
23+
r, theta, z = np.mgrid[0:1:11j, 0:np.pi*2:25j, -0.5:0.5:11j]
24+
x = r*np.cos(theta)
25+
y = r*np.sin(theta)
26+
27+
rc, thetac, zc = midpoints(r), midpoints(theta), midpoints(z)
28+
29+
# define a wobbly torus about [0.7, *, 0]
30+
sphere = (rc - 0.7)**2 + (zc + 0.2*np.cos(thetac*2))**2 < 0.2**2
31+
32+
# combine the color components
33+
hsv = np.zeros(sphere.shape + (3,))
34+
hsv[..., 0] = thetac / (np.pi*2)
35+
hsv[..., 1] = rc
36+
hsv[..., 2] = zc + 0.5
37+
colors = matplotlib.colors.hsv_to_rgb(hsv)
38+
39+
# and plot everything
40+
fig = plt.figure()
41+
ax = fig.gca(projection='3d')
42+
ax.voxels(x, y, z, sphere,
43+
facecolors=colors,
44+
edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter
45+
linewidth=0.5)
46+
47+
plt.show()

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,8 +2746,10 @@ def calc_arrow(uvw, angle=15):
27462746

27472747
quiver3D = quiver
27482748

2749-
def voxels(self, filled, **kwargs):
2749+
def voxels(self, *args, **kwargs):
27502750
"""
2751+
ax.voxels([x, y, z,] /, filled, **kwargs)
2752+
27512753
Plot a set of filled voxels
27522754
27532755
All voxels are plotted as 1x1x1 cubes on the axis, with filled[0,0,0]
@@ -2758,10 +2760,16 @@ def voxels(self, filled, **kwargs):
27582760
27592761
Parameters
27602762
----------
2761-
filled : np.array
2763+
filled : 3D np.array of bool
27622764
A 3d array of values, with truthy values indicating which voxels
27632765
to fill
27642766
2767+
x, y, z : 3D np.array, optional
2768+
The coordinates of the corners of the voxels. This should broadcast
2769+
to a shape one larger in every dimension than the shape of `filled`.
2770+
These arguments can be used to plot non-cubic voxels.
2771+
If not specified, defaults to increasing integers along each axis.
2772+
27652773
facecolors, edgecolors : array_like
27662774
The color to draw the faces and edges of the voxels. This parameter
27672775
can be:
@@ -2791,10 +2799,33 @@ def voxels(self, filled, **kwargs):
27912799
27922800
.. plot:: gallery/mplot3d/voxels.py
27932801
.. plot:: gallery/mplot3d/voxels_rgb.py
2802+
.. plot:: gallery/mplot3d/voxels_torus.py
2803+
.. plot:: gallery/mplot3d/voxels_numpy_logo.py
27942804
"""
2805+
2806+
# parse optional x y z arguments
2807+
if len(args) >= 3:
2808+
xyz, args = args[:3], args[3:]
2809+
else:
2810+
xyz = None
2811+
2812+
# clever trick to produce the right error message and handle argument
2813+
# names. `str` is needed to circumvent unicode_literals on py2
2814+
_get_args = (lambda filled, **kwargs: (filled, kwargs))
2815+
_get_args.__name__ = str('voxels')
2816+
filled, kwargs = _get_args(*args, **kwargs)
2817+
27952818
# check dimensions
27962819
if filled.ndim != 3:
27972820
raise ValueError("Argument filled must be 3-dimensional")
2821+
size = np.array(filled.shape, dtype=np.intp)
2822+
2823+
# check xyz coordinates, which are one larger than the filled shape
2824+
coord_shape = tuple(size + 1)
2825+
if xyz is None:
2826+
x, y, z = np.indices(coord_shape)
2827+
else:
2828+
x, y, z = (broadcast_to(c, coord_shape) for c in xyz)
27982829

27992830
def _broadcast_color_arg(color, name):
28002831
if np.ndim(color) in (0, 1):
@@ -2821,25 +2852,21 @@ def _broadcast_color_arg(color, name):
28212852
edgecolors = _broadcast_color_arg(edgecolors, 'edgecolors')
28222853

28232854
# always scale to the full array, even if the data is only in the center
2824-
self.auto_scale_xyz(
2825-
[0, filled.shape[0]],
2826-
[0, filled.shape[1]],
2827-
[0, filled.shape[2]]
2828-
)
2855+
self.auto_scale_xyz(x, y, z)
28292856

28302857
# points lying on corners of a square
28312858
square = np.array([
28322859
[0, 0, 0],
28332860
[0, 1, 0],
28342861
[1, 1, 0],
28352862
[1, 0, 0]
2836-
])
2863+
], dtype=np.intp)
28372864

28382865
voxel_faces = defaultdict(list)
28392866

28402867
def permutation_matrices(n):
28412868
""" Generator of cyclic permutation matices """
2842-
mat = np.eye(n, dtype=int)
2869+
mat = np.eye(n, dtype=np.intp)
28432870
for i in range(n):
28442871
yield mat
28452872
mat = np.roll(mat, 1, axis=0)
@@ -2848,7 +2875,7 @@ def permutation_matrices(n):
28482875
# render
28492876
for permute in permutation_matrices(3):
28502877
# find the set of ranges to iterate over
2851-
pc, qc, rc = permute.T.dot(filled.shape[:3])
2878+
pc, qc, rc = permute.T.dot(size)
28522879
pinds = np.arange(pc)
28532880
qinds = np.arange(qc)
28542881
rinds = np.arange(rc)
@@ -2890,7 +2917,20 @@ def permutation_matrices(n):
28902917

28912918
# iterate over the faces, and generate a Poly3DCollection for each voxel
28922919
polygons = {}
2893-
for coord, faces in voxel_faces.items():
2920+
for coord, faces_inds in voxel_faces.items():
2921+
# convert indices into 3D positions
2922+
if xyz is None:
2923+
faces = faces_inds
2924+
else:
2925+
faces = []
2926+
for face_inds in faces_inds:
2927+
ind = face_inds[:, 0], face_inds[:, 1], face_inds[:, 2]
2928+
face = np.empty(face_inds.shape)
2929+
face[:, 0] = x[ind]
2930+
face[:, 1] = y[ind]
2931+
face[:, 2] = z[ind]
2932+
faces.append(face)
2933+
28942934
poly = art3d.Poly3DCollection(faces,
28952935
facecolors=facecolors[coord],
28962936
edgecolors=edgecolors[coord],

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,43 @@ def test_alpha(self):
655655
assert voxels[coord], "faces returned for absent voxel"
656656
assert isinstance(poly, art3d.Poly3DCollection)
657657

658+
@image_comparison(
659+
baseline_images=['voxels-xyz'],
660+
extensions=['png'],
661+
tol=0.01
662+
)
663+
def test_xyz(self):
664+
fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
665+
666+
def midpoints(x):
667+
sl = ()
668+
for i in range(x.ndim):
669+
x = (x[sl + np.index_exp[:-1]] +
670+
x[sl + np.index_exp[1:]]) / 2.0
671+
sl += np.index_exp[:]
672+
return x
673+
674+
# prepare some coordinates, and attach rgb values to each
675+
r, g, b = np.indices((17, 17, 17)) / 16.0
676+
rc = midpoints(r)
677+
gc = midpoints(g)
678+
bc = midpoints(b)
679+
680+
# define a sphere about [0.5, 0.5, 0.5]
681+
sphere = (rc - 0.5)**2 + (gc - 0.5)**2 + (bc - 0.5)**2 < 0.5**2
682+
683+
# combine the color components
684+
colors = np.zeros(sphere.shape + (3,))
685+
colors[..., 0] = rc
686+
colors[..., 1] = gc
687+
colors[..., 2] = bc
688+
689+
# and plot everything
690+
ax.voxels(r, g, b, sphere,
691+
facecolors=colors,
692+
edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter
693+
linewidth=0.5)
694+
658695

659696
def test_inverted_cla():
660697
# Github PR #5450. Setting autoscale should reset

0 commit comments

Comments
 (0)