# 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_'


### 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'


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