Neurostories: creating videos of the flash-lag effect

This page recollects some variations on the flash-lag effect... and the way to easily and programmmatically generate those movies of the illusion.



Let's first initialize the notebook:

InΒ [2]:
from __future__ import division, print_function
import numpy as np
np.set_printoptions(precision=6, suppress=True)
import os
%matplotlib inline
#%config InlineBackend.figure_format='retina'
%config InlineBackend.figure_format = 'svg'
import matplotlib.pyplot as plt
phi = (np.sqrt(5)+1)/2
fig_width = 10
figsize = (fig_width, fig_width/phi)
from IPython.display import display, HTML
def show_video(filename): 
    return HTML(data='<video src="{}" loop autoplay width="600" height="600"></video>'.format(filename))
%load_ext autoreload
%autoreload 2

the simplest approachΒΆ

InΒ [7]:
__author__ = "Laurent Perrinet INT - CNRS"
__licence__ = 'BSD licence'

print('😎 Welcome to the script generating the movies 😎')

import os
home = os.environ['HOME']
figpath = '../files/'
def path2(fname):
    return os.path.join(figpath, fname)

import numpy as np
import gizeh as gz
import moviepy.editor as mpy

W, H = 1000, 600
duration = 2.
fps = 100
def create_movie(figname, r = 16,
                duration = duration,
                start = duration*.2,
                stop = duration*.8, do_stop=False,
                duration_flash = .05,
                fps = fps,
                W=W, H=H):
    flash = gz.rectangle(lx=r, ly=r, xy=(W/2., H/2.-r), fill=(0,1,0))
    def make_frame(t):
        surface = gz.Surface(W, H, bg_color=(0, 0, 0))
        if np.abs(t-duration/2) < duration_flash: flash.draw(surface)
        if start < t < stop:
            if do_stop and t >= duration/2:
                flash.draw(surface)
                rect = gz.rectangle(lx=r, ly=r, xy=(W/2, H/2.+r), fill=(1,0,0))
            else:
                rect = gz.rectangle(lx=r, ly=r, xy=(W*t/duration, H/2.+r), fill=(1,0,0))
            rect.draw(surface)
        return surface.get_npimage()

    clip = mpy.VideoClip(make_frame, duration=duration)
    clip.write_videofile(path2(figname), fps=fps)
    return clip

figname = '2019-09-30_flash_lag.mp4'
if not os.path.isfile(path2(figname)): clip = create_movie(figname)
clip.ipython_display(fps=fps, width=W, autoplay=True, loop=True)
t:   1%|          | 2/200 [01:57<3:14:28, 58.93s/it, now=None]
t:   0%|          | 0/200 [00:00<?, ?it/s, now=None]
t:   2%|▏         | 3/200 [00:00<00:08, 22.69it/s, now=None]
😎 Welcome to the script generating the movies 😎
Moviepy - Building video __temp__.mp4.
Moviepy - Writing video __temp__.mp4

t:   4%|▍         | 8/200 [00:00<00:07, 26.84it/s, now=None]
t:   7%|β–‹         | 14/200 [00:00<00:05, 31.63it/s, now=None]
t:  10%|β–‰         | 19/200 [00:00<00:05, 34.93it/s, now=None]
t:  12%|β–ˆβ–        | 23/200 [00:00<00:05, 35.35it/s, now=None]
t:  14%|β–ˆβ–        | 29/200 [00:00<00:04, 39.06it/s, now=None]
t:  18%|β–ˆβ–Š        | 35/200 [00:00<00:03, 43.12it/s, now=None]
t:  20%|β–ˆβ–ˆ        | 41/200 [00:00<00:03, 45.15it/s, now=None]
t:  24%|β–ˆβ–ˆβ–Ž       | 47/200 [00:01<00:03, 47.30it/s, now=None]
t:  26%|β–ˆβ–ˆβ–‹       | 53/200 [00:01<00:03, 48.98it/s, now=None]
t:  30%|β–ˆβ–ˆβ–‰       | 59/200 [00:01<00:02, 50.11it/s, now=None]
t:  32%|β–ˆβ–ˆβ–ˆβ–Ž      | 65/200 [00:01<00:02, 51.94it/s, now=None]
t:  36%|β–ˆβ–ˆβ–ˆβ–Œ      | 71/200 [00:01<00:02, 53.20it/s, now=None]
t:  38%|β–ˆβ–ˆβ–ˆβ–Š      | 77/200 [00:01<00:02, 53.93it/s, now=None]
t:  42%|β–ˆβ–ˆβ–ˆβ–ˆβ–     | 83/200 [00:01<00:02, 47.30it/s, now=None]
t:  44%|β–ˆβ–ˆβ–ˆβ–ˆβ–     | 88/200 [00:01<00:02, 47.37it/s, now=None]
t:  47%|β–ˆβ–ˆβ–ˆβ–ˆβ–‹     | 94/200 [00:01<00:02, 49.70it/s, now=None]
t:  50%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ     | 100/200 [00:02<00:02, 49.31it/s, now=None]
t:  53%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž    | 106/200 [00:02<00:01, 50.82it/s, now=None]
t:  56%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ    | 112/200 [00:02<00:01, 50.66it/s, now=None]
t:  59%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰    | 118/200 [00:02<00:01, 48.55it/s, now=None]
t:  62%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | 124/200 [00:02<00:01, 50.11it/s, now=None]
t:  65%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ   | 130/200 [00:02<00:01, 51.09it/s, now=None]
t:  68%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š   | 136/200 [00:02<00:01, 52.38it/s, now=None]
t:  71%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   | 142/200 [00:02<00:01, 44.64it/s, now=None]
t:  74%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž  | 147/200 [00:03<00:01, 45.01it/s, now=None]
t:  76%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ  | 152/200 [00:03<00:01, 42.93it/s, now=None]
t:  78%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š  | 157/200 [00:03<00:00, 43.66it/s, now=None]
t:  81%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  | 162/200 [00:03<00:00, 40.97it/s, now=None]
t:  84%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž | 167/200 [00:03<00:00, 42.95it/s, now=None]
t:  86%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹ | 173/200 [00:03<00:00, 45.76it/s, now=None]
t:  90%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰ | 179/200 [00:03<00:00, 47.93it/s, now=None]
t:  92%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 184/200 [00:03<00:00, 47.31it/s, now=None]
t:  94%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 189/200 [00:03<00:00, 45.34it/s, now=None]
t:  97%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹| 194/200 [00:04<00:00, 43.51it/s, now=None]
t: 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰| 199/200 [00:04<00:00, 41.22it/s, now=None]
t:   1%|          | 2/200 [02:02<3:21:40, 61.11s/it, now=None]
Moviepy - Done !
Moviepy - video ready __temp__.mp4
Out[7]:
InΒ [8]:
figname = '2019-09-30_flash_lag_stop.mp4'
if not os.path.isfile(path2(figname)): clip = create_movie(figname, do_stop=True)
clip.ipython_display(fps=fps, width=W, autoplay=True, loop=True)
t:   1%|          | 2/200 [02:02<3:21:49, 61.16s/it, now=None]
t:   0%|          | 0/200 [00:00<?, ?it/s, now=None]
Moviepy - Building video __temp__.mp4.
Moviepy - Writing video __temp__.mp4

t:   3%|β–Ž         | 6/200 [00:00<00:03, 54.32it/s, now=None]
t:   6%|β–Œ         | 11/200 [00:00<00:03, 50.12it/s, now=None]
t:   8%|β–Š         | 16/200 [00:00<00:03, 49.47it/s, now=None]
t:  11%|β–ˆ         | 22/200 [00:00<00:03, 50.08it/s, now=None]
t:  14%|β–ˆβ–Ž        | 27/200 [00:00<00:03, 47.90it/s, now=None]
t:  16%|β–ˆβ–Œ        | 31/200 [00:00<00:03, 42.94it/s, now=None]
t:  18%|β–ˆβ–Š        | 36/200 [00:00<00:03, 44.81it/s, now=None]
t:  21%|β–ˆβ–ˆ        | 42/200 [00:00<00:03, 46.58it/s, now=None]
t:  24%|β–ˆβ–ˆβ–       | 48/200 [00:01<00:03, 48.18it/s, now=None]
t:  26%|β–ˆβ–ˆβ–‹       | 53/200 [00:01<00:03, 48.48it/s, now=None]
t:  30%|β–ˆβ–ˆβ–‰       | 59/200 [00:01<00:02, 49.61it/s, now=None]
t:  32%|β–ˆβ–ˆβ–ˆβ–      | 64/200 [00:01<00:02, 48.95it/s, now=None]
t:  34%|β–ˆβ–ˆβ–ˆβ–      | 69/200 [00:01<00:02, 48.81it/s, now=None]
t:  38%|β–ˆβ–ˆβ–ˆβ–Š      | 75/200 [00:01<00:02, 51.06it/s, now=None]
t:  40%|β–ˆβ–ˆβ–ˆβ–ˆ      | 81/200 [00:01<00:02, 52.37it/s, now=None]
t:  44%|β–ˆβ–ˆβ–ˆβ–ˆβ–Ž     | 87/200 [00:01<00:02, 52.94it/s, now=None]
t:  46%|β–ˆβ–ˆβ–ˆβ–ˆβ–‹     | 93/200 [00:01<00:01, 53.90it/s, now=None]
t:  50%|β–ˆβ–ˆβ–ˆβ–ˆβ–‰     | 99/200 [00:01<00:01, 54.12it/s, now=None]
t:  52%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž    | 105/200 [00:02<00:01, 54.39it/s, now=None]
t:  56%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ    | 111/200 [00:02<00:01, 49.86it/s, now=None]
t:  58%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š    | 117/200 [00:02<00:01, 48.46it/s, now=None]
t:  62%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | 123/200 [00:02<00:01, 50.53it/s, now=None]
t:  64%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–   | 129/200 [00:02<00:01, 52.45it/s, now=None]
t:  68%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š   | 135/200 [00:02<00:01, 53.66it/s, now=None]
t:  70%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   | 141/200 [00:02<00:01, 54.30it/s, now=None]
t:  74%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž  | 147/200 [00:02<00:00, 54.64it/s, now=None]
t:  76%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‹  | 153/200 [00:03<00:00, 54.69it/s, now=None]
t:  80%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‰  | 159/200 [00:03<00:00, 53.48it/s, now=None]
t:  82%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž | 165/200 [00:03<00:00, 53.24it/s, now=None]
t:  86%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Œ | 171/200 [00:03<00:00, 53.47it/s, now=None]
t:  88%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š | 177/200 [00:03<00:00, 53.54it/s, now=None]
t:  92%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 183/200 [00:03<00:00, 47.85it/s, now=None]
t:  94%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–| 189/200 [00:03<00:00, 49.30it/s, now=None]
t:  98%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Š| 195/200 [00:03<00:00, 50.26it/s, now=None]
t:   1%|          | 2/200 [02:06<3:28:27, 63.17s/it, now=None]
Moviepy - Done !
Moviepy - video ready __temp__.mp4
Out[8]:

a more complex approachΒΆ

InΒ [2]:
import gizeh as gz
W, H = 500, 300
r, gray, t = 25., .3, 1.

surface = gz.Surface(W,H, bg_color=(1, 1, 1)) # white background
gradient = gz.ColorGradient(type="radial", stops_colors=[(0,(gray, gray, gray)), (1, (1, 1, 1))],
                            xy1=[0, 0], xy2=[0, 0], xy3=[0, r])
rf = gz.circle(r=r, xy=(W/2., H/2.), fill=gradient)
#rect = gz.rectangle(lx=.3*H, ly=.02*H, xy=(W*t/duration, H/2.), fill=(0,1,0), angle=np.pi/2)

rf.draw(surface)
surface.ipython_display()
Out[2]:

The flash-lag clockΒΆ

And now the animation for the straight trajectory of a segment with perpendicular orientation:

InΒ [3]:
import numpy as np
import gizeh as gz
import moviepy.editor as mpy

W, H = 1000, 600
duration = 1

figpath = '../files/2019-09-30_'
fps = 60

fix = gz.circle(r=5, xy=(W/2., H/2.), fill=(1, 0, 0))

def make_frame(t):

    surface = gz.Surface(W, H, bg_color=(0, 0, 0))
    
    # rect = gz.rectangle(lx=.3*H, ly=.02*H, xy=(W*t/duration, H/2.), fill=(0,1,0), angle=np.pi/2)
    # modul = 1 - .2*np.sin(2*np.pi*t/duration)
    # ymodul = 1 + .2*(np.cos(2*np.pi*t/duration)+1)
    lx = .4*H
    for theta in np.linspace(0, 2*np.pi, 50, endpoint=False):
        gray = .1
        rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(gray, gray, gray), angle=theta)
        rect.draw(surface)

    pastille = gz.circle(r=25, xy=(W/2., H/2.), fill=(0, 0, 0))
        
    theta = 2*np.pi*t/duration + np.pi
    rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(0,1,0), angle=theta)
    rect.draw(surface)
    if np.abs(np.sin(.5*(theta+np.pi/2))) < np.sin(np.pi/64):
        # print (theta, np.abs(np.tan(theta)), np.tan(np.pi/32), np.abs(np.tan(theta)) < np.tan(np.pi/32))
        print ('flash for ', theta)
        flash = gz.circle(r=5, xy=(W/2., H/2. - .48*H), fill=(1, 0, 0))
        flash.draw(surface)
    pastille.draw(surface)
    fix.draw(surface)
    return surface.get_npimage()

clip = mpy.VideoClip(make_frame, duration=duration)
#clip.write_videofile(figpath + 'clock.mp4', fps=fps) # Many options...

clip.ipython_display(fps=fps, width=W, autoplay=True, loop=True)
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
t:   7%|β–‹         | 4/60 [00:00<00:01, 36.45it/s, now=None]
Moviepy - Building video __temp__.mp4.
Moviepy - Writing video __temp__.mp4

t:  33%|β–ˆβ–ˆβ–ˆβ–Ž      | 20/60 [00:00<00:01, 30.89it/s, now=None]
flash for  4.71238898038469
                                                            
Moviepy - Done !
Moviepy - video ready __temp__.mp4
Out[3]:

avec un flash traitΒΆ

InΒ [4]:
def make_frame(t):

    surface = gz.Surface(W, H, bg_color=(0, 0, 0))

    lx = .4*H
    for theta in np.linspace(0, 2*np.pi, 50, endpoint=False):
        gray = .1
        rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(gray, gray, gray), angle=theta)
        rect.draw(surface)

    pastille = gz.circle(r=25, xy=(W/2., H/2.), fill=(0, 0, 0))
        
    theta = 2*np.pi*t/duration + np.pi
    rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(0,1,0), angle=theta)
    rect.draw(surface)
    if np.abs(np.sin(.5*(theta+np.pi/2))) < np.sin(np.pi/64):
        print ('flash for theta=', theta, ', t=', t)
        flash = gz.rectangle(lx=.06*H, ly=2, xy=(W/2., H/2. - .47*H), fill=(1, 0, 0), angle=np.pi/2)
        flash.draw(surface)
    pastille.draw(surface)
    fix.draw(surface)
    return surface.get_npimage()

clip = mpy.VideoClip(make_frame, duration=duration)
#clip.write_videofile(figpath + 'clock.mp4', fps=fps) # Many options...

clip.ipython_display(fps=fps, width=W, autoplay=True, loop=True)
t:   0%|          | 0/60 [00:00<?, ?it/s, now=None]
Moviepy - Building video __temp__.mp4.
Moviepy - Writing video __temp__.mp4

t:  37%|β–ˆβ–ˆβ–ˆβ–‹      | 22/60 [00:01<00:01, 20.70it/s, now=None]
flash for theta= 4.71238898038469 , t= 0.25
                                                            
Moviepy - Done !
Moviepy - video ready __temp__.mp4

Out[4]:

avec plusieurs flash traitΒΆ

InΒ [5]:
duration = 3

def make_frame(t):

    surface = gz.Surface(W, H, bg_color=(0, 0, 0))
    lx = .4*H
    
    for theta in np.linspace(0, 2*np.pi, 50, endpoint=False):
        gray = .1
        rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(gray, gray, gray), angle=theta)
        rect.draw(surface)

    pastille = gz.circle(r=25, xy=(W/2., H/2.), fill=(0, 0, 0))
    N_spikes = 5
    for theta_ in np.linspace(0, 2*np.pi, N_spikes, endpoint=False):
        theta = 2*np.pi*t/duration + np.pi + theta_
        rect = gz.rectangle(lx=lx, ly=2, xy=(W/2.+lx/2*np.cos(theta), H/2.+lx/2*np.sin(theta)), fill=(0,1,0), angle=theta)
        rect.draw(surface)
        
        #if np.abs(np.cos(np.pi*(t/duration-.5)*N_spikes)) > np.cos(np.pi/32):
        if np.abs(np.sin(N_spikes*(theta_-theta)/2)) < np.sin(np.pi/64):
            print ('flash for theta=', theta, ', t=', t)
            flash = gz.rectangle(lx=.06*H, ly=5, xy=(W/2. + .47*H*np.cos(theta_), H/2. - .47*H*np.sin(theta_)), fill=(1, 0, 0), 
                                 angle=-theta_)
            flash.draw(surface)
        
    pastille.draw(surface)
    fix.draw(surface)
    return surface.get_npimage()

clip = mpy.VideoClip(make_frame, duration=duration)
#clip.write_videofile(figpath + 'clock.mp4', fps=fps)
clip.ipython_display(fps=fps, width=W, autoplay=True, loop=True)
t:   0%|          | 0/180 [00:00<?, ?it/s, now=None]
Moviepy - Building video __temp__.mp4.
Moviepy - Writing video __temp__.mp4

t:  12%|β–ˆβ–        | 22/180 [00:00<00:05, 28.69it/s, now=None]
flash for theta= 3.7699111843077517 , t= 0.3
flash for theta= 5.026548245743669 , t= 0.3
flash for theta= 6.283185307179586 , t= 0.3
flash for theta= 7.5398223686155035 , t= 0.3
flash for theta= 8.79645943005142 , t= 0.3
t:  32%|β–ˆβ–ˆβ–ˆβ–      | 58/180 [00:01<00:04, 29.47it/s, now=None]
flash for theta= 5.026548245743669 , t= 0.9
flash for theta= 6.283185307179586 , t= 0.9
flash for theta= 7.5398223686155035 , t= 0.9
flash for theta= 8.79645943005142 , t= 0.9
flash for theta= 10.053096491487338 , t= 0.9
t:  52%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–    | 94/180 [00:03<00:02, 32.18it/s, now=None]
flash for theta= 6.283185307179586 , t= 1.5
flash for theta= 7.5398223686155035 , t= 1.5
flash for theta= 8.79645943005142 , t= 1.5
flash for theta= 10.053096491487338 , t= 1.5
flash for theta= 11.309733552923255 , t= 1.5
t:  72%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–  | 130/180 [00:04<00:01, 31.22it/s, now=None]
flash for theta= 7.5398223686155035 , t= 2.1
flash for theta= 8.79645943005142 , t= 2.1
flash for theta= 10.053096491487338 , t= 2.1
flash for theta= 11.309733552923255 , t= 2.1
flash for theta= 12.566370614359172 , t= 2.1
t:  93%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–Ž| 168/180 [00:05<00:00, 30.73it/s, now=None]
flash for theta= 8.79645943005142 , t= 2.7
flash for theta= 10.053096491487338 , t= 2.7
flash for theta= 11.309733552923255 , t= 2.7
flash for theta= 12.566370614359172 , t= 2.7
flash for theta= 13.82300767579509 , t= 2.7
                                                              
Moviepy - Done !
Moviepy - video ready __temp__.mp4
Out[5]: