Caustics (wikipedia are luminous patterns which are resulting from the superposition of smoothly deviated light rays. It is the heart-shaped pattern in your cup of coffee which is formed as the rays of from the sun are reflected on the cup's surface. It is also the wiggly patterns of light that you will see on the floor of a pool as the sun's light is refracted at the surface of the water. Here we will simulate this particular physical phenomenon. Simply because they are mesmerizingly beautiful, but also as it is of interest in visual neuroscience. Indeed, it speaks to how images are formed (more on this later), hence how the brain may understand images.
In this post, I have developed a simple formalism to generate such patterns, with the paradoxical result that it is very simple to code yet generates patterns with great complexity, such as:
This is joint work with artist Etienne Rey, in which I especially follow the ideas put forward in the series Turbulence.
Upon further observation, one may discover that caustics exhibit some iridescence$^\ddagger$, that is, that the light pattern which forms the waggling lines of the caustics may decompose into different colors, forming evanescent rainbows. Here, we will simply use a modulation of the Snell-Descartes law that we used to compute different angle of refraction. This will be put in relation with the dependance of the refraction index with the wavelength of light and the transformation of a monochromatic light into RGB that we used in a previous post about colors of the sky. The results are close to subjective observations, with the surprising (to me) observation that colors appear more between nodes...
Note: $\ddagger$ I use the term iridescence which is improper in the physical sense as it rather concerns the property of an object to exhibit different colors depending on the angle of view. However, in the perspective of the work with Etienne Rey it resonates with our endeavour to show that percepetion, in particular visual perception, is an active process of the observer within its environment.
figpath = '2022-09-09_caustique'
from IPython.display import display
import matplotlib
import matplotlib.pyplot as plt
PRECISION = 4
PRECISION = 8
import os
import datetime
import numpy as np
def init(args=[], figpath=figpath, PRECISION=PRECISION):
import argparse
if figpath is None:
import datetime
date = datetime.datetime.now().date().isoformat()
figpath = f'{date}_caustique'
parser = argparse.ArgumentParser()
parser.add_argument("--tag", type=str, default='caustique', help="Tag")
parser.add_argument("--figpath", type=str, default=figpath, help="Folder to store images")
parser.add_argument("--vext", type=str, default='mp4', help="Folder to store images")
parser.add_argument("--nx", type=int, default=5*2**PRECISION, help="number of pixels (vertical)")
parser.add_argument("--ny", type=int, default=5*2**PRECISION, help="number of pixels (horizontal)")
parser.add_argument("--nframe", type=int, default=2**PRECISION, help="number of frames")
parser.add_argument("--bin_dens", type=int, default=2, help="relative bin density")
parser.add_argument("--bin_spectrum", type=int, default=3, help="bin spacing in spectrum")
parser.add_argument("--seed", type=int, default=8601, help="seed for RNG")
parser.add_argument("--H", type=float, default=5., help="depth of the pool")
parser.add_argument("--variation", type=float, default=.4, help="variation of diffraction index: http://www.philiplaven.com/p20.html 1.40 at 400 nm and 1.37 at 700nm makes a 2% variation")
parser.add_argument("--scale", type=float, default=150, help="sf")
parser.add_argument("--B_sf", type=float, default=0.25, help="bandwidth in sf")
parser.add_argument("--V_Y", type=float, default=0.3, help="horizontal speed")
parser.add_argument("--V_X", type=float, default=0.3, help="vertical speed")
parser.add_argument("--B_V", type=float, default=1.0, help="bandwidth in speed")
parser.add_argument("--theta", type=float, default=2*np.pi*(2-1.61803), help="angle with the horizontal")
parser.add_argument("--B_theta", type=float, default=np.pi/3, help="bandwidth in theta")
parser.add_argument("--min_lum", type=float, default=.2, help="diffusion level for the rendering")
parser.add_argument("--gamma", type=float, default=2.8, help="Gamma exponant to convert luminosity to luminance")
parser.add_argument("--fps", type=float, default=18, help="frames per second")
parser.add_argument("--multispectral", type=bool, default=True, help="Compute caustics on the full spectrogram.")
parser.add_argument("--cache", type=bool, default=True, help="Cache intermediate output.")
parser.add_argument("--verbose", type=bool, default=False, help="Displays more verbose output.")
opt = parser.parse_args(args=args)
if opt.verbose:
print(opt)
return opt
print(f'Saving our simulations in={figpath}')
Saving our simulations in=2022-09-09_caustique
opt = init()
opt.figpath = figpath
opt
Namespace(tag='caustique', figpath='2022-09-09_caustique', vext='mp4', nx=1280, ny=1280, nframe=256, bin_dens=2, bin_spectrum=3, seed=8601, H=5.0, variation=0.4, scale=150, B_sf=0.25, V_Y=0.3, V_X=0.3, B_V=1.0, theta=2.399988291783386, B_theta=1.0471975511965976, min_lum=0.2, gamma=2.8, fps=18, multispectral=True, cache=True, verbose=False)
Transfoming a sequence of PNG frames into gif or mp4:
def make_gif(gifname, fnames, fps, do_delete=True):
import imageio
with imageio.get_writer(gifname, mode='I', fps=fps) as writer:
for fname in fnames:
writer.append_data(imageio.imread(fname))
from pygifsicle import optimize
optimize(str(gifname))
if do_delete:
for fname in fnames: os.remove(fname)
return gifname
# https://moviepy.readthedocs.io/en/latest/getting_started/videoclips.html#imagesequenceclip
def make_mp4(mp4name, fnames, fps, do_delete=True):
import moviepy.editor as mpy
clip = mpy.ImageSequenceClip(fnames, fps=fps)
clip.write_videofile(mp4name, fps=fps, codec='libx264', verbose=False, logger=None)
if do_delete:
for fname in fnames: os.remove(fname)
return mp4name
Utilities to compute the spectrum of the blue sky and convert them later to RGB values (check out https://laurentperrinet.github.io/sciblog/posts/2020-07-04-colors-of-the-sky.html for details)
from lambda2color import Lambda2color, xyz_from_xy
# borrowed from https://github.com/gummiks/gummiks.github.io/blob/master/scripts/astro/planck.py
def planck(wav, T):
import scipy.constants as const
c = const.c # c = 3.0e+8
h = const.h # h = 6.626e-34
k = const.k # k = 1.38e-23
a = 2.0*h*c**2
b = h*c/(wav*k*T)
intensity = a / ( (wav**5) * (np.exp(b) - 1.0) )
return intensity
def scattering(wav, a=0.005, p=1.3, b=0.45):
"""
b is proportionate to the column density of aerosols
along the path of sunlight, from outside the atmosphere
to the point of observation
see https://laurentperrinet.github.io/sciblog/posts/2020-07-04-colors-of-the-sky.html for more details
"""
# converting wav in µm:
intensity = np.exp(-a/((wav/1e-6)**4)) # Rayleigh extinction by nitrogen
intensity *= (wav/1e-6)**-4
intensity *= np.exp(-b/((wav/1e-6)**p)) # Aerosols
return intensity
from tqdm.notebook import tqdm, trange
import MotionClouds as mc
class Caustique:
def __init__(self, opt):
"""
Image coordinates follow 'ij' indexing, that is,
* their origin at the top left,
* the X axis is vertical and goes "down",
* the Y axis is horizontal and goes "right".
"""
self.mc = mc
self.ratio = opt.ny/opt.nx # ratio between height and width (>1 for portrait, <1 for landscape)
X = np.linspace(0, 1, opt.nx, endpoint=False) # vertical
Y = np.linspace(0, self.ratio, opt.ny, endpoint=False) # horizontal
self.xv, self.yv = np.meshgrid(X, Y, indexing='ij')
self.opt = opt
# https://stackoverflow.com/questions/16878315/what-is-the-right-way-to-treat-python-argparse-namespace-as-a-dictionary
self.d = vars(opt)
print(self.opt.figpath)
os.makedirs(self.opt.figpath, exist_ok=True)
self.cachepath = os.path.join('/tmp', self.opt.figpath)
if opt.verbose: print(f'{self.cachepath=}')
os.makedirs(self.cachepath, exist_ok=True)
# a standard white:
illuminant_D65 = xyz_from_xy(0.3127, 0.3291),
illuminant_sun = xyz_from_xy(0.325998, 0.335354)
# color conversion class
self.cs_srgb = Lambda2color(red=xyz_from_xy(0.64, 0.33),
green=xyz_from_xy(0.30, 0.60),
blue=xyz_from_xy(0.15, 0.06),
white=illuminant_sun)
self.wavelengths = self.cs_srgb.cmf[:, 0]*1e-9
self.N_wavelengths = len(self.wavelengths)
# multiply by the spectrum of the sky
intensity5800 = planck(self.wavelengths, 5800.)
scatter = scattering(self.wavelengths)
self.spectrum_sky = intensity5800 * scatter
self.spectrum_sky /= self.spectrum_sky.max()
def wave(self):
filename = f'{self.cachepath}/{self.opt.tag}_wave.npy'
if os.path.isfile(filename):
z = np.load(filename)
else:
# A simplistic model of a wave using https://github.com/NeuralEnsemble/MotionClouds
fx, fy, ft = mc.get_grids(self.opt.nx, self.opt.ny, self.opt.nframe)
env = mc.envelope_gabor(fx, fy, ft, V_X=self.opt.V_Y, V_Y=self.opt.V_X, B_V=self.opt.B_V,
sf_0=1./self.opt.scale, B_sf=self.opt.B_sf/self.opt.scale,
theta=self.opt.theta, B_theta=self.opt.B_theta)
z = mc.rectif(mc.random_cloud(env, seed=self.opt.seed))
if self.opt.cache: np.save(filename, z)
return z
def transform(self, z_, modulation=1.):
xv, yv = self.xv.copy(), self.yv.copy()
dzdx = z_ - np.roll(z_, 1, axis=0)
dzdy = z_ - np.roll(z_, 1, axis=1)
xv = xv + modulation * self.opt.H * dzdx
yv = yv + modulation * self.opt.H * dzdy
xv = np.mod(xv, 1)
yv = np.mod(yv, self.ratio)
return xv, yv
def plot(self, z, do_color=True, videoname=None, dpi=50):
#hist = self.do_raytracing(z)
binsx, binsy = self.opt.nx//self.opt.bin_dens, self.opt.ny//self.opt.bin_dens
if videoname is None:
videoname=f'{self.opt.figpath}/{self.opt.tag}.{self.opt.vext}'
subplotpars = matplotlib.figure.SubplotParams(left=0., right=1., bottom=0., top=1., wspace=0., hspace=0.,)
if self.opt.multispectral:
#image_rgb = self.cs_srgb.spec_to_rgb(hist)
image_rgb = np.zeros((self.opt.nx//self.opt.bin_dens, self.opt.ny//self.opt.bin_dens, 3, self.opt.nframe))
for i_frame in tqdm(range(self.opt.nframe)):
for ii_wavelength, i_wavelength in enumerate(range(self.opt.bin_spectrum//2, self.N_wavelengths, self.opt.bin_spectrum)):
modulation = 1. + self.opt.variation/2 - self.opt.variation*i_wavelength/self.N_wavelengths
# print(i_wavelength, N_wavelengths, modulation)
xv, yv = self.transform(z[:, :, i_frame], modulation=modulation)
hist_, edge_x, edge_y = np.histogram2d(xv.ravel(), yv.ravel(),
bins=[binsx, binsy],
range=[[0, 1], [0, self.ratio]],
density=True)
spec = np.zeros((self.N_wavelengths))
spec[i_wavelength] = 1
rgb = self.cs_srgb.spec_to_rgb(spec)
rgb *= self.spectrum_sky[i_wavelength]
image_rgb[:, :, :, i_frame] += hist_[:, :, None] * rgb[None, None, :]
# image_rgb -= image_rgb.min()
image_rgb /= image_rgb.max()
else:
hist = np.zeros((binsx, binsy, self.opt.nframe))
for i_frame in trange(self.opt.nframe):
xv, yv = self.transform(z[:, :, i_frame])
hist_, edge_x, edge_y = np.histogram2d(xv.ravel(), yv.ravel(),
bins=[binsx, binsy],
range=[[0, 1], [0, self.ratio]],
density=True)
#hist /= hist.max()
fnames = []
for i_frame in range(self.opt.nframe):
fig, ax = plt.subplots(figsize=(self.opt.ny/self.opt.bin_dens/dpi, self.opt.nx/self.opt.bin_dens/dpi), subplotpars=subplotpars)
if self.opt.multispectral:
ax.imshow(image_rgb[:, :, :, i_frame] ** (1/self.opt.gamma), vmin=0, vmax=1)
else:
if do_color:
bluesky = np.array([0.268375, 0.283377]) # xyz
sun = np.array([0.325998, 0.335354]) # xyz
# ax.pcolormesh(edge_y, edge_x, hist[:, :, i_frame], vmin=0, vmax=1, cmap=plt.cm.Blues_r)
# https://en.wikipedia.org/wiki/CIE_1931_color_space#Mixing_colors_specified_with_the_CIE_xy_chromaticity_diagram
L1 = 1 - hist[:, :, i_frame]
L2 = hist[:, :, i_frame]
image_denom = L1 / bluesky[1] + L2 / sun[1]
image_x = (L1 * bluesky[0] / bluesky[1] + L2 * sun[0] / sun[1]) / image_denom
image_y = (L1 + L2) / image_denom
image_xyz = np.dstack((image_x, image_y, 1 - image_x - image_y))
image_rgb = self.cs_srgb.xyz_to_rgb(image_xyz)
image_L = self.opt.min_lum + (1-self.opt.min_lum)* L2 ** .61803
ax.imshow(image_L[:, :, None]*image_rgb, vmin=0, vmax=1)
else:
ax.imshow(1-image_L, vmin=0, vmax=1)
fname = f'{self.cachepath}/{self.opt.tag}_frame_{i_frame:04d}.png'
fig.savefig(fname, dpi=dpi)
fnames.append(fname)
plt.close('all')
if self.opt.vext == 'gif':
return make_gif(videoname, fnames, fps=self.opt.fps)
else:
return make_mp4(videoname, fnames, fps=self.opt.fps)
def show(self, videoname, width=1024):
if self.opt.vext == 'gif':
from IPython.display import Image, display
return display(Image(url=videoname, width=width))
else:
#import moviepy.editor as mpy
#return mpy.ipython_display(videoname, width=width)
# https://github.com/NeuralEnsemble/MotionClouds/blob/master/MotionClouds/MotionClouds.py#L858
from IPython.display import HTML, display
opts = ' loop="1" autoplay="1" controls '
html = HTML(f'<video {opts} src="{videoname}" type="video/{self.opt.vext}" width={width}\>')
html.reload()
return display(html)
c = Caustique(opt)
wave_videoname = f'{opt.figpath}/{opt.tag}_wave'
if not os.path.isfile(f'{wave_videoname}{c.mc.vext}'):
print(f'Doing {wave_videoname}{c.mc.vext}')
z = c.wave()
c.mc.anim_save(z.swapaxes(0, 1), wave_videoname)
c.show(f'{wave_videoname}{c.mc.vext}')
2022-09-09_caustique
c = Caustique(opt)
z = c.wave()
z.shape
2022-09-09_caustique
(1280, 1280, 256)
videoname = f'{opt.figpath}/{opt.tag}.{opt.vext}'
if not os.path.isfile(videoname):
c = Caustique(opt)
z = c.wave()
videoname = c.plot(z)
c.show(videoname)
N_scan = 9
base = 2
opt = init()
opt.figpath = figpath
c = Caustique(opt)
# compute just once
z = c.wave()
for H_ in c.opt.H*np.logspace(-1, 1, N_scan, base=base):
opt = init()
opt.figpath = figpath
c = Caustique(opt)
print(f'H = {H_:.3f}')
c.opt.H = H_
c.opt.tag = f'{opt.tag}_H_{H_:.3f}'
videoname = f'{opt.figpath}/{c.opt.tag}.{opt.vext}'
if not os.path.isfile(videoname):
url=c.plot(z, videoname=videoname)
c.show(videoname)
2022-09-09_caustique 2022-09-09_caustique H = 2.500
2022-09-09_caustique H = 2.973
2022-09-09_caustique H = 3.536
2022-09-09_caustique H = 4.204
2022-09-09_caustique H = 5.000
2022-09-09_caustique H = 5.946
2022-09-09_caustique H = 7.071
2022-09-09_caustique H = 8.409
2022-09-09_caustique H = 10.000
opt = init()
opt.figpath = figpath
c = Caustique(opt)
z = c.wave()
for variation_ in np.logspace(-2, 0, N_scan, base=10, endpoint=False):
opt = init()
opt.figpath = figpath
c = Caustique(opt)
print(f'variation = {variation_:.3f}')
c.opt.variation = variation_
c.opt.tag = f'{opt.tag}_variation_{variation_:.3f}'
videoname = f'{opt.figpath}/{c.opt.tag}.{opt.vext}'
if not os.path.isfile(videoname):
url=c.plot(z, videoname=videoname)
c.show(videoname)
2022-09-09_caustique 2022-09-09_caustique variation = 0.010
2022-09-09_caustique variation = 0.017
2022-09-09_caustique variation = 0.028
2022-09-09_caustique variation = 0.046
2022-09-09_caustique variation = 0.077
2022-09-09_caustique variation = 0.129
2022-09-09_caustique variation = 0.215
2022-09-09_caustique variation = 0.359
2022-09-09_caustique variation = 0.599
for variable in ['scale', 'B_sf', 'B_theta', 'V_X', 'B_V', 'gamma', ]: # 'theta', 'V_Y'
print(f'======{variable}======')
for modul in np.logspace(-1, 1, N_scan, base=base):
opt = init()
opt.figpath = figpath
c = Caustique(opt)
c.d[variable] *= modul
c.opt.tag = f'{opt.tag}_{variable}_modul_{modul:.3f}'
videoname = f'{opt.figpath}/{c.opt.tag}.{opt.vext}'
print(f'{variable}={variable}(default)*{modul:.3f}={c.d[variable]:.3E}')
if not os.path.isfile(videoname):
print('Doing ', videoname)
z = c.wave()
mcname = f'{opt.figpath}/{c.opt.tag}'
if False: #not os.path.isfile(f'{mcname}{c.mc.vext}'):
print('Doing ', f'{mcname}{c.mc.vext}')
c.mc.anim_save(z.swapaxes(0, 1), f'{mcname}')
url=c.plot(z, videoname=videoname)
c.show(videoname)
======scale====== 2022-09-09_caustique scale=scale(default)*0.500=7.500E+01
2022-09-09_caustique scale=scale(default)*0.595=8.919E+01
2022-09-09_caustique scale=scale(default)*0.707=1.061E+02
2022-09-09_caustique scale=scale(default)*0.841=1.261E+02
2022-09-09_caustique scale=scale(default)*1.000=1.500E+02
2022-09-09_caustique scale=scale(default)*1.189=1.784E+02
2022-09-09_caustique scale=scale(default)*1.414=2.121E+02
2022-09-09_caustique scale=scale(default)*1.682=2.523E+02
2022-09-09_caustique scale=scale(default)*2.000=3.000E+02
======B_sf====== 2022-09-09_caustique B_sf=B_sf(default)*0.500=1.250E-01
2022-09-09_caustique B_sf=B_sf(default)*0.595=1.487E-01
2022-09-09_caustique B_sf=B_sf(default)*0.707=1.768E-01
2022-09-09_caustique B_sf=B_sf(default)*0.841=2.102E-01
2022-09-09_caustique B_sf=B_sf(default)*1.000=2.500E-01
2022-09-09_caustique B_sf=B_sf(default)*1.189=2.973E-01
2022-09-09_caustique B_sf=B_sf(default)*1.414=3.536E-01
2022-09-09_caustique B_sf=B_sf(default)*1.682=4.204E-01
2022-09-09_caustique B_sf=B_sf(default)*2.000=5.000E-01
======B_theta====== 2022-09-09_caustique B_theta=B_theta(default)*0.500=5.236E-01
2022-09-09_caustique B_theta=B_theta(default)*0.595=6.227E-01
2022-09-09_caustique B_theta=B_theta(default)*0.707=7.405E-01
2022-09-09_caustique B_theta=B_theta(default)*0.841=8.806E-01
2022-09-09_caustique B_theta=B_theta(default)*1.000=1.047E+00
2022-09-09_caustique B_theta=B_theta(default)*1.189=1.245E+00
2022-09-09_caustique B_theta=B_theta(default)*1.414=1.481E+00
2022-09-09_caustique B_theta=B_theta(default)*1.682=1.761E+00
2022-09-09_caustique B_theta=B_theta(default)*2.000=2.094E+00
======V_X====== 2022-09-09_caustique V_X=V_X(default)*0.500=1.500E-01
2022-09-09_caustique V_X=V_X(default)*0.595=1.784E-01
2022-09-09_caustique V_X=V_X(default)*0.707=2.121E-01
2022-09-09_caustique V_X=V_X(default)*0.841=2.523E-01
2022-09-09_caustique V_X=V_X(default)*1.000=3.000E-01
2022-09-09_caustique V_X=V_X(default)*1.189=3.568E-01
2022-09-09_caustique V_X=V_X(default)*1.414=4.243E-01
2022-09-09_caustique V_X=V_X(default)*1.682=5.045E-01
2022-09-09_caustique V_X=V_X(default)*2.000=6.000E-01
======B_V====== 2022-09-09_caustique B_V=B_V(default)*0.500=5.000E-01
2022-09-09_caustique B_V=B_V(default)*0.595=5.946E-01
2022-09-09_caustique B_V=B_V(default)*0.707=7.071E-01
2022-09-09_caustique B_V=B_V(default)*0.841=8.409E-01
2022-09-09_caustique B_V=B_V(default)*1.000=1.000E+00
2022-09-09_caustique B_V=B_V(default)*1.189=1.189E+00
2022-09-09_caustique B_V=B_V(default)*1.414=1.414E+00
2022-09-09_caustique B_V=B_V(default)*1.682=1.682E+00
2022-09-09_caustique B_V=B_V(default)*2.000=2.000E+00
======gamma====== 2022-09-09_caustique gamma=gamma(default)*0.500=1.400E+00
2022-09-09_caustique gamma=gamma(default)*0.595=1.665E+00
2022-09-09_caustique gamma=gamma(default)*0.707=1.980E+00
2022-09-09_caustique gamma=gamma(default)*0.841=2.355E+00
2022-09-09_caustique gamma=gamma(default)*1.000=2.800E+00
2022-09-09_caustique gamma=gamma(default)*1.189=3.330E+00
2022-09-09_caustique gamma=gamma(default)*1.414=3.960E+00
2022-09-09_caustique gamma=gamma(default)*1.682=4.709E+00
2022-09-09_caustique gamma=gamma(default)*2.000=5.600E+00
%pip install --upgrade -r requirements.txt
Requirement already satisfied: pip in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 4)) (22.2.2) Requirement already satisfied: matplotlib in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 5)) (3.5.3) Collecting matplotlib Downloading matplotlib-3.6.0-cp310-cp310-macosx_10_12_x86_64.whl (7.3 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 7.3/7.3 MB 48.2 MB/s eta 0:00:00 Requirement already satisfied: numpy in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 6)) (1.23.3) Requirement already satisfied: scipy in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 7)) (1.9.1) Requirement already satisfied: MotionClouds in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 8)) (20200212) Requirement already satisfied: imageio in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 9)) (2.21.3) Requirement already satisfied: imageio-ffmpeg in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 10)) (0.4.7) Requirement already satisfied: pygifsicle in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 11)) (1.0.6) Requirement already satisfied: lambda2color in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 12)) (0.7) Requirement already satisfied: tqdm in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 13)) (4.64.1) Requirement already satisfied: jupyter in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 14)) (1.0.0) Requirement already satisfied: ipywidgets in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 15)) (8.0.2) Requirement already satisfied: nbconvert in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 16)) (7.0.0) Requirement already satisfied: moviepy in /usr/local/lib/python3.10/site-packages (from -r requirements.txt (line 17)) (1.0.3) Requirement already satisfied: pillow>=6.2.0 in /usr/local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (9.2.0) Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (2.8.2) Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (1.4.4) Requirement already satisfied: packaging>=20.0 in /usr/local/Cellar/sip/6.6.2_1/libexec/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (21.3) Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (0.11.0) Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (4.37.1) Collecting contourpy>=1.0.1 Downloading contourpy-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl (241 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 241.1/241.1 kB 10.4 MB/s eta 0:00:00 Requirement already satisfied: pyparsing>=2.2.1 in /usr/local/Cellar/sip/6.6.2_1/libexec/lib/python3.10/site-packages (from matplotlib->-r requirements.txt (line 5)) (3.0.9) Requirement already satisfied: support-developer>=1.0.2 in /usr/local/lib/python3.10/site-packages (from pygifsicle->-r requirements.txt (line 11)) (1.0.3) Requirement already satisfied: jupyter-console in /usr/local/lib/python3.10/site-packages (from jupyter->-r requirements.txt (line 14)) (6.4.4) Requirement already satisfied: qtconsole in /usr/local/lib/python3.10/site-packages (from jupyter->-r requirements.txt (line 14)) (5.3.2) Requirement already satisfied: notebook in /usr/local/lib/python3.10/site-packages (from jupyter->-r requirements.txt (line 14)) (6.4.12) Requirement already satisfied: ipykernel in /usr/local/lib/python3.10/site-packages (from jupyter->-r requirements.txt (line 14)) (6.15.2) Requirement already satisfied: traitlets>=4.3.1 in /usr/local/lib/python3.10/site-packages (from ipywidgets->-r requirements.txt (line 15)) (5.3.0) Requirement already satisfied: ipython>=6.1.0 in /usr/local/lib/python3.10/site-packages (from ipywidgets->-r requirements.txt (line 15)) (8.5.0) Requirement already satisfied: jupyterlab-widgets~=3.0 in /usr/local/lib/python3.10/site-packages (from ipywidgets->-r requirements.txt (line 15)) (3.0.3) Requirement already satisfied: widgetsnbextension~=4.0 in /usr/local/lib/python3.10/site-packages (from ipywidgets->-r requirements.txt (line 15)) (4.0.3) Requirement already satisfied: lxml in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (4.9.1) Requirement already satisfied: bleach in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (5.0.1) Requirement already satisfied: nbclient>=0.5.0 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (0.5.13) Requirement already satisfied: jinja2>=3.0 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (3.1.2) Requirement already satisfied: markupsafe>=2.0 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (2.1.1) Requirement already satisfied: jupyter-core>=4.7 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (4.11.1) Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (1.5.0) Requirement already satisfied: jupyterlab-pygments in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (0.2.2) Requirement already satisfied: tinycss2 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (1.1.1) Requirement already satisfied: nbformat>=5.1 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (5.4.0) Requirement already satisfied: pygments>=2.4.1 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (2.13.0) Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (4.11.1) Requirement already satisfied: defusedxml in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (0.7.1) Requirement already satisfied: mistune<3,>=2.0.3 in /usr/local/lib/python3.10/site-packages (from nbconvert->-r requirements.txt (line 16)) (2.0.4) Requirement already satisfied: requests<3.0,>=2.8.1 in /usr/local/lib/python3.10/site-packages (from moviepy->-r requirements.txt (line 17)) (2.28.1) Requirement already satisfied: decorator<5.0,>=4.0.2 in /usr/local/lib/python3.10/site-packages (from moviepy->-r requirements.txt (line 17)) (4.4.2) Requirement already satisfied: proglog<=1.0.0 in /usr/local/lib/python3.10/site-packages (from moviepy->-r requirements.txt (line 17)) (0.1.10) Requirement already satisfied: pyzmq>=17 in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (23.2.1) Requirement already satisfied: psutil in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (5.9.2) Requirement already satisfied: debugpy>=1.0 in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (1.6.3) Requirement already satisfied: jupyter-client>=6.1.12 in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (7.3.5) Requirement already satisfied: appnope in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (0.1.3) Requirement already satisfied: tornado>=6.1 in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (6.2) Requirement already satisfied: nest-asyncio in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (1.5.5) Requirement already satisfied: matplotlib-inline>=0.1 in /usr/local/lib/python3.10/site-packages (from ipykernel->jupyter->-r requirements.txt (line 14)) (0.1.6) Requirement already satisfied: pickleshare in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.7.5) Requirement already satisfied: pexpect>4.3 in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (4.8.0) Requirement already satisfied: prompt-toolkit<3.1.0,>3.0.1 in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (3.0.31) Requirement already satisfied: stack-data in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.5.0) Requirement already satisfied: backcall in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.2.0) Requirement already satisfied: jedi>=0.16 in /usr/local/lib/python3.10/site-packages (from ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.18.1) Requirement already satisfied: fastjsonschema in /usr/local/lib/python3.10/site-packages (from nbformat>=5.1->nbconvert->-r requirements.txt (line 16)) (2.16.1) Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.10/site-packages (from nbformat>=5.1->nbconvert->-r requirements.txt (line 16)) (4.15.0) Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib->-r requirements.txt (line 5)) (1.16.0) Requirement already satisfied: charset-normalizer<3,>=2 in /usr/local/lib/python3.10/site-packages (from requests<3.0,>=2.8.1->moviepy->-r requirements.txt (line 17)) (2.1.1) Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/site-packages (from requests<3.0,>=2.8.1->moviepy->-r requirements.txt (line 17)) (2022.6.15) Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/lib/python3.10/site-packages (from requests<3.0,>=2.8.1->moviepy->-r requirements.txt (line 17)) (1.26.12) Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/site-packages (from requests<3.0,>=2.8.1->moviepy->-r requirements.txt (line 17)) (3.3) Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/site-packages (from beautifulsoup4->nbconvert->-r requirements.txt (line 16)) (2.3.2.post1) Requirement already satisfied: webencodings in /usr/local/lib/python3.10/site-packages (from bleach->nbconvert->-r requirements.txt (line 16)) (0.5.1) Requirement already satisfied: Send2Trash>=1.8.0 in /usr/local/lib/python3.10/site-packages (from notebook->jupyter->-r requirements.txt (line 14)) (1.8.0) Requirement already satisfied: prometheus-client in /usr/local/lib/python3.10/site-packages (from notebook->jupyter->-r requirements.txt (line 14)) (0.14.1) Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.10/site-packages (from notebook->jupyter->-r requirements.txt (line 14)) (0.2.0) Requirement already satisfied: terminado>=0.8.3 in /usr/local/lib/python3.10/site-packages (from notebook->jupyter->-r requirements.txt (line 14)) (0.15.0) Requirement already satisfied: argon2-cffi in /usr/local/lib/python3.10/site-packages (from notebook->jupyter->-r requirements.txt (line 14)) (21.3.0) Requirement already satisfied: qtpy>=2.0.1 in /usr/local/lib/python3.10/site-packages (from qtconsole->jupyter->-r requirements.txt (line 14)) (2.2.0) Requirement already satisfied: parso<0.9.0,>=0.8.0 in /usr/local/lib/python3.10/site-packages (from jedi>=0.16->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.8.3) Requirement already satisfied: pyrsistent!=0.17.0,!=0.17.1,!=0.17.2,>=0.14.0 in /usr/local/lib/python3.10/site-packages (from jsonschema>=2.6->nbformat>=5.1->nbconvert->-r requirements.txt (line 16)) (0.18.1) Requirement already satisfied: attrs>=17.4.0 in /usr/local/lib/python3.10/site-packages (from jsonschema>=2.6->nbformat>=5.1->nbconvert->-r requirements.txt (line 16)) (22.1.0) Requirement already satisfied: entrypoints in /usr/local/lib/python3.10/site-packages (from jupyter-client>=6.1.12->ipykernel->jupyter->-r requirements.txt (line 14)) (0.4) Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.10/site-packages (from pexpect>4.3->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.7.0) Requirement already satisfied: wcwidth in /usr/local/lib/python3.10/site-packages (from prompt-toolkit<3.1.0,>3.0.1->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.2.5) Requirement already satisfied: argon2-cffi-bindings in /usr/local/lib/python3.10/site-packages (from argon2-cffi->notebook->jupyter->-r requirements.txt (line 14)) (21.2.0) Requirement already satisfied: executing in /usr/local/lib/python3.10/site-packages (from stack-data->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (1.0.0) Requirement already satisfied: asttokens in /usr/local/lib/python3.10/site-packages (from stack-data->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (2.0.8) Requirement already satisfied: pure-eval in /usr/local/lib/python3.10/site-packages (from stack-data->ipython>=6.1.0->ipywidgets->-r requirements.txt (line 15)) (0.2.2) Requirement already satisfied: cffi>=1.0.1 in /usr/local/lib/python3.10/site-packages (from argon2-cffi-bindings->argon2-cffi->notebook->jupyter->-r requirements.txt (line 14)) (1.15.1) Requirement already satisfied: pycparser in /usr/local/lib/python3.10/site-packages (from cffi>=1.0.1->argon2-cffi-bindings->argon2-cffi->notebook->jupyter->-r requirements.txt (line 14)) (2.21) Installing collected packages: contourpy, matplotlib Attempting uninstall: matplotlib Found existing installation: matplotlib 3.5.3 Uninstalling matplotlib-3.5.3: Successfully uninstalled matplotlib-3.5.3 Successfully installed contourpy-1.0.5 matplotlib-3.6.0 Note: you may need to restart the kernel to use updated packages.