A bit of fun with gravity waves

A bit of fun with gravity waves

Motion Clouds were defined in the origin to provide a simple parameterization for textures. Thus we used a simple unimodal, normal distribution (on the log-radial frequency space to be more precise). But the larger set of Random Phase Textures may provide some interesting examples, some of them can even be fun! This is the case of this simulation of the waves you may observe on the surface on the ocean.

In [1]:
from IPython.display import HTML
HTML('<center><video controls autoplay loop src="../files/waves.mp4" width=61.8%/></center>')
Out[1]:

Main features of gravitational waves are:

  1. longer waves travel faster (tsunami are fast and global, ripples are slow and local) - speed is linearly proportional to wavelength
  2. phase speed (following a wave's crest) is twice as fast as group speed (following a group of waves).
In [2]:
name = 'waves'
import os
import numpy as np
import MotionClouds as mc
fx, fy, ft = mc.get_grids(mc.N_X, mc.N_Y, mc.N_frame)
In [3]:
theta, B_theta, B_wave = 0., np.pi/16., .1
alpha, sf_0, B_sf, B_V = 2., .25, .3, 2.
seed = 1234565
V_X, V_Y, g = .5, 0., .1
loggabor=True
In [4]:
def envelope_gravity(fx, fy, ft, B_wave, g=.1):
    """
     Gravitational envelope:
     selects the plane corresponding to the speed (V_X, V_Y) with some thickness B_V

    """
    k = fx*V_X+fy*V_Y
    env = np.exp(-.5*(((ft/.5)**2-g*np.sqrt(((k/.5)**2)))**2/(B_wave*mc.frequency_radius(fx, fy, ft, clean_division=True))**2))
    env *= (ft*k) < 0
    return env

def envelope_gabor_wave(fx, fy, ft, B_wave, V_X=mc.V_X, V_Y=mc.V_Y,
                        B_V=mc.B_V, B_v=1., sf_0=mc.sf_0, B_sf=mc.B_sf, loggabor=mc.loggabor,
                        theta=mc.theta, B_theta=mc.B_theta, alpha=mc.alpha):
    """
    Returns the Motion Cloud kernel

    """
    envelope = mc.envelope_gabor(fx, fy, ft, V_X=V_X, V_Y=V_Y,
                                 B_V=B_V, sf_0=sf_0, B_sf=B_sf, loggabor=loggabor,
                                 theta=theta, B_theta=B_theta, alpha=alpha)
    envelope *= envelope_gravity(fx, fy, ft, B_wave=B_wave)
    return envelope
In [5]:
B_v_low, B_v_high = .025, .1
In [6]:
name_ = name + '_low'
mc1 = envelope_gabor_wave(fx, fy, ft, V_X=1., V_Y=0., B_wave=B_v_low, B_V=B_V, theta=theta, B_theta=B_theta, sf_0=sf_0, B_sf=B_sf, alpha=alpha)
mc.figures(mc1, name_, seed=seed)
mc.in_show_video(name_)
In [7]:
name_ = name + '_high'
mc1 = envelope_gabor_wave(fx, fy, ft, V_X=1., V_Y=0., B_wave=B_v_high, B_V=B_V, theta=theta, B_theta=B_theta, sf_0=sf_0, B_sf=B_sf, alpha=alpha)
mc.figures(mc1, name_, seed=seed)
mc.in_show_video(name_)

This figure shows how one can create stimuli similar to MotionCloud stimuli that have a dstribution according to the laws of gravitational waves (that is with temporal frequency proportional to the square root of spatial frequency for deep water conditions -- in the shallow water conditions, normal MCs are a good approximation). Note that phase speed (following a maximum) is twice faster than group speed (following a dot). The two lines correspond to different bandwitdhs for the spread around the physical law (width of the manifold).
Columns represent isometric projections of a cube. The left column displays iso-surfaces of the spectral envelope by displaying enclosing volumes at 5 different energy values with respect to the peak amplitude of the Fourier spectrum. The middle column shows an isometric view of the faces of the movie cube. The first frame of the movie lies on the x-y plane, the x-t plane lies on the top face and motion direction is seen as diagonal lines on this face (vertical motion is similarly see in the y-t face). The third column displays the actual movie as an animation.

In [8]:
z = mc.rectif(mc.random_cloud(envelope_gabor_wave(fx, fy, ft, V_X=1., V_Y=0., B_wave=B_v_low, B_V=B_V, theta=theta, B_theta=B_theta, sf_0=sf_0, B_sf=B_sf, alpha=alpha)))
In [9]:
from moviepy.editor import VideoClip
import numpy as np
from vispy import app, scene
from vispy.gloo.util import _screenshot

canvas = scene.SceneCanvas(bgcolor=(.95, .95, .95, 1))#keys='interactive')
view = canvas.central_widget.add_view()
cam = scene.TurntableCamera(elevation=20, azimuth=205, up='z')
cam.fov = 42
cam.scale_factor = mc.N_X * .7
cam.set_range((0, mc.N_X), (0, mc.N_Y), (-mc.N_frame/4, mc.N_frame/4))
view.camera = cam

duration = 16.
Z = lambda t : z[ int(t/duration * mc.N_frame) ] * 42.
surface = scene.visuals.SurfacePlot(z=Z(0), shading='smooth', color=(0.2, 0.2, 1, 1))
view.add(surface)
canvas.show()

# ANIMATE WITH MOVIEPY
def make_frame(t):
    surface.set_data(z = Z(t)) # Update the surface
    canvas.on_draw(None) # Update the image on Vispy's canvas
    return _screenshot((0,0,canvas.size[0],canvas.size[1]))[:,:,:3]

import moviepy.editor as mpy

fname = os.path.join(mc.figpath, name + '_small.gif')
if not os.path.isfile(fname):
    animation = VideoClip(make_frame, duration=duration/2).resize(width=240)
    animation.speedx(0.5).write_gif(fname, fps=8, opt='nq')
mpy.ipython_display(fname, loop=1, autoplay=1)
INFO: Could not import backend "PyQt4":
No module named 'PyQt4'
INFO: Could not import backend "PyQt5":
No module named 'PyQt5'
INFO: Could not import backend "PySide":
No module named 'PySide'
Out[9]:
In [10]:
fname = os.path.join(mc.figpath, name + '.mp4')
if not os.path.isfile(fname):
    animation = VideoClip(make_frame, duration=duration).resize(width=920)
    animation.speedx(0.5).write_videofile(fname, fps=16)
mpy.ipython_display(fname, loop=1, autoplay=1)
Out[10]: