Reverse-phi and Asymmetry of ON and OFF responses

We test Reverse-phi motion and the Asymmetry of ON and OFF responses using MotionClouds.

In [1]:
import os
import MotionClouds as mc

mc.figpath = '../files/2014-11-10_reverse-phi'
if not(os.path.isdir(mc.figpath)): os.mkdir(mc.figpath)

import scipy.ndimage as nd
import matplotlib.cm as cm
import numpy as np
%pylab inline
Populating the interactive namespace from numpy and matplotlib
In [2]:
import holoviews as hv
#hv.notebook_extension('bokeh')
hv.notebook_extension()
nb_name = 'reverse_'
No description has been provided for this imageNo description has been provided for this image

reverse phi motion generator

In [3]:
def toss():
    return (np.random.rand()>.5)*2-1

def mcg(img,  wait=12, term=6, shift_range=4, reverse=True, nb_test=1, random=False, verbose=False):
    """
    From a (square, gray level) image, we write a funtion that returns a (3D ndarray) movie with a revese phi motion.
    
    This movie consists of nb_test*(wait + 2*term) gray scale frames where:
    
    nb_test: is the number of repetition (in case of random tosses of the shift)
    wait: the number of frames we are waiting
    term: the number of frames we are presenting one frame and the then the other
    reverse: if we reverse the contrast
    
    """
    if (nb_test <= 0):
        return (movie[0, 0, 0])
    period = wait + 2*term
            
    movie = np.zeros([img.shape[0], img.shape[1], (nb_test*(wait + 2*term))])
    for i_test in range(nb_test):
        if random:
            contrast = toss()
            shift = toss() * np.random.randint(shift_range)
        else:
            contrast = 2*(reverse==False) - 1
            shift = shift_range
        if verbose: print('contrast=', contrast)
        start = i_test*period + wait
        movie[:, :, start:(start + term)] = img[:, :, np.newaxis]
        img_shifted = np.roll(img, shift, 1) * contrast
        movie[:, :, (start + term):(start + 2*term)] = img_shifted[:, :, np.newaxis]
    return movie

Random Image

Let's first try with a binary random image:

In [4]:
%%opts Image style(cmap='gray')
pictogram = 2. * (np.random.rand(64, 64) > .5) - 1.
hv.Image(pictogram).hist()
Out[4]:

The simplest movement is to have no reversal ($\phi$-motion) the image goes up:

In [5]:
movie = mcg(img=pictogram, reverse=False)
name = nb_name + 'pictogram-phi'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

The simplest movement is to have no reversal ($\phi$-motion) the image goes down:

In [6]:
movie = mcg(img=pictogram, reverse=False, shift_range =-4)
name = nb_name + 'pictogram-phidown'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

Flipping the contrast induces a reversal (reverse-$\phi$ motion):

In [7]:
movie = mcg(img=pictogram, reverse=True)
name = nb_name + 'pictogram-reversephi'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

Showing a sequence of such motions with random reversals and shifts:

In [8]:
movie = mcg(img=pictogram, nb_test=10, random=True)
name = nb_name + 'pictogram'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

trying out with a (natural) image

In [9]:
from scipy.misc import face
color = face()
print('Size of image @ loading ', color.shape)
image = color[:, (1024-768):, :].mean(axis=-1)
image = 2* (image - image.min()) / (image.max() - image.min()) - 1.
print('Size of image after square reshaping + gray levels ', image.shape)
Size of image @ loading  (768, 1024, 3)
Size of image after square reshaping + gray levels  (768, 768)
In [10]:
%%opts Image style(cmap='gray')
hv.RGB(color) + hv.Image(image).hist()
Out[10]:
In [11]:
movie = mcg(img=np.rot90(image, 3))
name = nb_name + 'natural'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

Showing a sequence of such motions with random reversals and shifts demonstrates that there is a "surprising" effect related to the reversal:

In [12]:
movie = mcg(img=np.rot90(image, 3), nb_test=10, random=True)
name = nb_name + 'natural_random'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

static MotionClouds

trying with just one frame of a motion cloud:

In [13]:
%%opts Image style(cmap='gray')
fx, fy, ft = mc.get_grids(mc.N_X, mc.N_Y, 1)
name = nb_name + 'MC1'
image = mc.rectif(mc.random_cloud(mc.envelope_gabor(fx, fy, ft)))
image = 2. * image.reshape((mc.N_X, mc.N_Y)) - 1.
image = np.rot90(image, 1)
hv.Image(image).hist()
Out[13]:

The simplest movement is to have no reversal ($\phi$-motion) the image goes up:

In [14]:
movie = mcg(img=image, reverse=False)
name = nb_name + 'MC1-phi'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

Flipping the contrast induces a reversal (reverse-$\phi$ motion):

In [15]:
movie = mcg(img=image, reverse=True)
name = nb_name + 'MC1-reversephi'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

Showing a sequence of such motions with random reversals and shifts shows that the difference between a reversal or not is harder to catch than on a natural image:

In [16]:
movie = mcg(img=image, nb_test=10, random=True)
name = nb_name + 'MC1re-random'
mc.anim_save(mc.rectif(movie), os.path.join(mc.figpath, name))
mc.in_show_video(name, figpath=mc.figpath)

MotionClouds

trying now with a full motion cloud:

In [17]:
fx, fy, ft = mc.get_grids(mc.N_X, mc.N_Y, mc.N_frame)
name = nb_name + 'MC'
env = mc.envelope_gabor(fx, fy, ft)
env = rot90(env)
mc.figures(env, name, seed=12234565, figpath=mc.figpath)
mc.in_show_video(name, figpath=mc.figpath)
/usr/local/lib/python3.7/site-packages/vispy/visuals/isocurve.py:22: UserWarning: VisPy is not yet compatible with matplotlib 2.2+
  warnings.warn("VisPy is not yet compatible with matplotlib 2.2+")
INFO:OpenGL.acceleratesupport:No OpenGL_accelerate module loaded: No module named 'OpenGL_accelerate'
No description has been provided for this image
No description has been provided for this image

Now generate a reverse phi stimulus by showing just 2 frames the next one being filpped over:

In [18]:
i_frame = 4
mov = mc.random_cloud(env)
mov_ =  np.zeros_like(mov)
print ('Size of movie @ loading ', mov_[:, :, (mc.N_frame//2-i_frame):(mc.N_frame//2)].shape, '\nSize of frame = ', mov[:, :, mc.N_frame//2-1].shape)
mov_[:, :, (mc.N_frame//2-i_frame):(mc.N_frame//2)] = mov[:, :, mc.N_frame//2-1][:, :, np.newaxis]
mov_[:, :, (mc.N_frame//2):(mc.N_frame//2+i_frame)] = mov[:, :, mc.N_frame//2][:, :, np.newaxis]
mov_ = mc.rectif(mov_, contrast=1.)
mc.anim_save(mov_, os.path.join(mc.figpath, name + '_just_Phi'))
mc.in_show_video(name + '_just_Phi', figpath=mc.figpath)
Size of movie @ loading  (256, 256, 4) 
Size of frame =  (256, 256)
In [19]:
%%opts Image style(cmap='gray')
key_dims = [hv.Dimension('x',range=(-.5,.5)), hv.Dimension('y',range=(-.5,.5))]
one = hv.Image(mov_[:, :, mc.N_frame//2-i_frame].T, group='one frame', key_dimensions=key_dims)
two = hv.Image(mov_[:, :, mc.N_frame//2].T, group='second frame', key_dimensions=key_dims).hist()
one + two
WARNING:root:Image03447: Setting non-parameter attribute key_dimensions=[Dimension('x'), Dimension('y')] using a mechanism intended only for parameters
WARNING:root:Image03448: Setting non-parameter attribute key_dimensions=[Dimension('x'), Dimension('y')] using a mechanism intended only for parameters
Out[19]:
In [20]:
i_frame = 4
mov = mc.random_cloud(env)
mov_ =  np.zeros_like(mov)
print ('Size of movie @ loading ', mov_[:, :, (mc.N_frame//2-i_frame):(mc.N_frame//2)].shape, '\nSize of frame = ', mov[:, :, mc.N_frame//2-1].shape)
mov_[:, :, (mc.N_frame//2-i_frame):(mc.N_frame//2)] = mov[:, :, mc.N_frame//2-1][:, :, np.newaxis]
mov_[:, :, (mc.N_frame//2):(mc.N_frame//2+i_frame)] = -mov[:, :, mc.N_frame//2][:, :, np.newaxis]
mov_ = mc.rectif(mov_, contrast=1.)
mc.anim_save(mov_, os.path.join(mc.figpath, name + '_reverse_Phi'))
mc.in_show_video(name + '_reverse_Phi', figpath=mc.figpath)
Size of movie @ loading  (256, 256, 4) 
Size of frame =  (256, 256)
In [21]:
%%opts Image style(cmap='gray')
resone = hv.Image(mov_[:, :, mc.N_frame//2-i_frame].T, group='one frame', key_dimensions=key_dims)
two = hv.Image(mov_[:, :, mc.N_frame//2].T, group='second frame', key_dimensions=key_dims).hist()
one + two
WARNING:root:Image04103: Setting non-parameter attribute key_dimensions=[Dimension('x'), Dimension('y')] using a mechanism intended only for parameters
WARNING:root:Image04104: Setting non-parameter attribute key_dimensions=[Dimension('x'), Dimension('y')] using a mechanism intended only for parameters
Out[21]:

some book keeping for the notebook

In [22]:
%load_ext version_information
%version_information numpy, scipy, matplotlib, MotionClouds
Out[22]:
Software Version
Python 3.7.1 64bit [Clang 10.0.0 (clang-1000.11.45.5)]
IPython 7.1.1
OS Darwin 17.7.0 x86_64 i386 64bit
numpy 1.15.4
scipy 1.1.0
matplotlib 3.0.1
MotionClouds 20180606
Wed Nov 14 11:19:24 2018 CET