Rainbow effect
When I see a rainbow, I perceive the luminance inside the arc to be brighter than outside the arc. Is this effect percpetual (inside our head) or physical (inside each droplet in the sky). So, this is a simple notebook to show off how to synthesize the image of a rainbow on a realistic sky. TL;DR: there must be a physical reason for it.
Outline: The rainbow is a set of colors over a gradient of hues, masked for certain ones. The sky will be a gradient over blueish colors.
Let's first initialize the notebook:
%matplotlib inline
%config InlineBackend.figure_format='retina'
import numpy as np
import matplotlib.pyplot as plt
FIG_WIDTH = 15
FIGSIZE=(FIG_WIDTH, .618 * FIG_WIDTH)
Size of the images that we will generate, definition of the meshgrid:
N_pix = 1024
ruler = np.linspace(-1, 1, N_pix, endpoint=True)
X, Y = np.meshgrid(ruler, ruler)
print('X.shape =', X.shape)
Creation of a band:
width, X0, Y0 = .05, .0, 0.
blue2red = np.exp( -.5 * ((Y-Y0)**2)/width**2)
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(blue2red, vmin=0, vmax=1);
Creation of a softmax band from zero to one:
blue2red = 1 / (1 + np.exp( (Y-Y0)/width) )
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(blue2red, vmin=0, vmax=1);
Bending the band around a parabola:
width, X0, Y0, radius = .02, .0, -.25, 1.5
blue2red = 1 / (1 + np.exp( (- (X-X0)**2/radius**2 + Y-Y0)/width) )
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
cmap = ax.imshow(blue2red, vmin=0, vmax=1)
fig.colorbar(mappable=cmap);
Converting to a RGB image:
from matplotlib.colors import hsv_to_rgb
help(hsv_to_rgb)
Empirically defining the starting and ending hues in a rainbow:
h_start, h_stop = .8, .0
hue = h_start+(h_stop-h_start)*blue2red
print('hue.min()', hue.min(), ', hue.max()', hue.max())
hsv = np.dstack((hue, np.ones_like(hue), np.ones_like(hue)))
print('hsv.shape =', hsv.shape)
rainbow = hsv_to_rgb(hsv)
print('rainbow.min()', rainbow.min(), ', rainbow.max()', rainbow.max())
print('rainbow.shape =', rainbow.shape)
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(rainbow, vmin=0, vmax=1)
Creating a simple mask:
mask = blue2red * (1-blue2red)
mask /= mask.max()
hsv = np.dstack((hue, np.ones_like(hue), mask))
print('hsv.shape =', hsv.shape)
from matplotlib.colors import hsv_to_rgb
rainbow = hsv_to_rgb(hsv)
print('rainbow.shape =', rainbow.shape)
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(rainbow, vmin=0, vmax=1);
Now assembling this image to that of a background sky:
L_mean = .7
sky_contrast = .6
blue = np.array([0.1+L_mean, 0.05+L_mean, 1.])
image = (.5 + .5 * sky_contrast * Y)[:, :, None] * blue[None, None, :]
print('image.shape =', image.shape)
print('image.min()', image.min(), ', image.max()', image.max())
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(image, vmin=0, vmax=1);
print(image.min(), image.max())
rainbow_contrast = .5
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow((1.-rainbow_contrast)*image+rainbow_contrast*rainbow, vmin=0, vmax=1);
Quite realistic, no? Still, there is little effect at a high contrast for the rainbow. I do not perceive a "rainbow effect", so most likely I would predict there must be a physical reason for it.
Yet...
rainbow_contrast = .1
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow((1.-rainbow_contrast)*image+rainbow_contrast*rainbow, vmin=0, vmax=1);
There is a little effect at low contrast...
Take a picture found on the net (Hi, https://www.laniadvokat.com/blog/2018/7/24/lake-city-to-molas-pass !), with this nice double rainbow:
import imageio
image = imageio.imread('https://static1.squarespace.com/static/58efb414f7e0abff4dbfc0c4/t/5b5ff2f3aa4a99799747f7eb/1533014789574/IMG_3775.JPG')
print('image.shape =', image.shape)
print('image.min()', image.min(), ', image.max()', image.max())
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(image, vmin=0, vmax=255);
Let's zoom below and above, somewhere around the first arc:
zoom = image[250:750, 0:400]
fig, ax = plt.subplots(figsize=(FIG_WIDTH, FIG_WIDTH))
ax.imshow(zoom, vmin=0, vmax=255);
And let's plot the mean grayscale luminance:
fig, ax = plt.subplots(figsize=FIGSIZE)
ax.plot(zoom.mean(axis=1))
ax.plot(zoom.mean(axis=(1, 2)), 'k--');
One clearly sees the gradient of the background cloud (mean luminance as a dashed black line), the oscillation of hue in the rainbow (one line per channel), but also an indication that below the rainbow, luminance is quite higher than that predicted by a linear regression don on the upper part of the image.
It is certainly a mixed effect...
some book keeping for the notebook¶
%load_ext watermark
%watermark
%load_ext version_information
%version_information numpy, matplotlib, imageio