N. Virgo
N. Virgo

Reputation: 8449

Turning up the lights in matplotlib

I have the following Python code:

import numpy as np
from matplotlib import pyplot as plt
plt.rcParams['figure.figsize'] = [12, 7]

n = 100
m = 100

X = np.arange(-n/2,n/2,1)
Y = np.arange(-m/2,m/2,1)
X, Y = np.meshgrid(X, Y)
    
landscape = np.exp(-0.01 * (X*X + Y*Y) )

fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
ax.plot_surface(X, Y, landscape,
                linewidth=0,
                antialiased=False
                )

Running this in a notebook produces this image

enter image description here

If you look very closely you will see that the left-hand side of the Gaussian peak is very slightly lighter than the right-hand side. This lighting effect is barely visible, though, and I would like to increase it, so that the 3D shape becomes easily visible.

I'm aware of matplotlib.colors.LightSource, but no matter what I do I can't get that to produce the effect I want. Ideally, I'd just like to increase the intensity of the 'default' lighting, rather than fiddling around with this. But if someone can explain how to use LightSource for this image that would help too.

Note that I don't want to apply a height map to the image, and I also don't want to draw grid lines on it - I just want to increase the lighting effect while keeping the surface a uniform colour.

It's also worth mentioning that I'm somewhat stuck with MatPlotLib, because I'm using Jupyterlite to share the notebook with students who don't have Python installed, so I need a solution that works with that.

Upvotes: 4

Views: 3362

Answers (2)

Guy
Guy

Reputation: 50949

Using LightSource you can do something like

import numpy as np
from matplotlib import pyplot as plt
from matplotlib.colors import LightSource

plt.rcParams['figure.figsize'] = [12, 7]

n = 100
m = 100

X = np.arange(-n / 2, n / 2, 1)
Y = np.arange(-m / 2, m / 2, 1)
X, Y = np.meshgrid(X, Y)

landscape = np.exp(-0.01 * (X * X + Y * Y))

fig, ax = plt.subplots(subplot_kw={"projection": "3d"})

# this is used to set the graph color to blue
blue = np.array([0., 0., 1.])
rgb = np.tile(blue, (landscape.shape[0], landscape.shape[1], 1))

ls = LightSource()
illuminated_surface = ls.shade_rgb(rgb, landscape)

ax.plot_surface(X, Y, landscape,
                linewidth=0,
                antialiased=False,
                facecolors=illuminated_surface)

enter image description here

If you want the light from the right change the azdeg parameter at the LightSource creation

ls = LightSource(azdeg=80)

enter image description here

Parameters
    ----------
    azdeg : float, default: 315 degrees (from the northwest)
        The azimuth (0-360, degrees clockwise from North) of the light
        source.
    altdeg : float, default: 45 degrees
        The altitude (0-90, degrees up from horizontal) of the light
        source.

Upvotes: 7

Davide_sd
Davide_sd

Reputation: 13185

Keeping in mind that Matplotlib's 3D capabilities are rather limited, I would suggest to use a different library. Better libraries for 3D plots are:

  • Plotly: thought I wouldn't use it for solid color surfaces, as it has a strange lighting solution which creates hard to read plots.
  • Mayavi
  • PyVista
  • K3D-Jupyter (only works inside Jupyter Notebook).

Keep in mind that each one of those libraries come with their advantages and disadvantages.

I'm going to replicate your example with Mayavi, since I have it installed in the current environment:

import numpy as np
from mayavi import mlab

n = 100
m = 100

x, y = np.mgrid[-n/2:n/2:n*1j,-m/2:m/2:m*1j]
z = np.exp(-0.01 * (x**2 + y**2) )

surf = mlab.surf(x, y, z, warp_scale='auto', color=(0, 0.5, 1))
mlab.show()

enter image description here

Upvotes: 3

Related Questions