Mirror clouds

An essential component of natural images is that they may contain order at large scales such as symmetries. Let's try to generate some textures with an axial (arbitrarily vertical) symmetry and take advantage of the different properties of random phase textures.

For that, we will first create a "vanilla" texture:

In [1]:
%matplotlib inline
import numpy as np
np.set_printoptions(precision=3, suppress=True)
import pylab
import matplotlib.pyplot as plt
In [2]:
import os
name = 'mirror'
DOWNSCALE = 1
V_X = .5
In [3]:
import MotionClouds as mc
N_X, N_Y, N_frame = mc.N_X/DOWNSCALE, mc.N_Y/DOWNSCALE, mc.N_frame/DOWNSCALE
mc.figpath = '../files/2016-01-27_mirror-clouds/'
if not(os.path.isdir(mc.figpath)): os.mkdir(mc.figpath)
    
fx, fy, ft = mc.get_grids(N_X, N_Y, N_frame)
z = mc.envelope_gabor(fx, fy, ft, V_X=V_X)

name_ = name + '_nomirror'
movie = mc.rectif(mc.random_cloud(z))
mc.anim_save(movie, os.path.join(mc.figpath, name_))
mc.in_show_video(name_, figpath=mc.figpath)

The first thing to try is to use the periodicity of MotionClouds: Since they are generated in the Fourier space, they have a period in space (width of the display) and in time (there is no gap perceived when you play these in loops as we do here). So we may take the above texture, mirror it vertically and concatenate it to its right:

In [4]:
name_ = name + '_horizontal'
mirrored_movie = np.vstack((movie, movie[::-1, :, :])) 
mc.anim_save(mirrored_movie, os.path.join(mc.figpath, name_))
mc.in_show_video(name_, figpath=mc.figpath)

Mathematically, taking the sum of these two parts $$ I_M(x, y, t) = \frac 1 2 \cdot (I(x, y, t) + I(x, -y, t)) $$

generates a symmetric pattern: $$ I_M(x, -y, t) = \frac 1 2 \cdot (I(x, -y, t) + I(x, y, t)) = I_M(x, y, t) $$

The vertical axis of symmetry is around $y=0$ and (from the periodicity $I(x, y+N_y, t)=I(x, y, t)$) around $y=N_y/2$: $$ I_M(x, -y-N_y/2, t) = \frac 1 2 \cdot (I(x, -y-N_y/2, t) + I(x, -(-y-N_y/2), t)) = \frac 1 2 \cdot (I(x, -N_y/2-y, t) + I(x, N_y/2+y, t)) = I_M(x, y-N_y/2, t) $$

Another advantage of these clouds is that they are random phase textures: the different features are not coherent. While adding these 2 textures (the original and the mirrored one) would produce an interference pattern if we would have used gratings, it is different here:

In [5]:
name_ = name + '_fuse'
mirrored_movie = np.vstack((movie, movie[::-1, :, :])) 
mirrored_movie = .5*(movie + movie[::-1, :, :])
mc.anim_save(mirrored_movie, os.path.join(mc.figpath, name_))
mc.in_show_video(name_, figpath=mc.figpath)

What happens now if we add more symmetries?

In general, it is possible to make this axis of symmetry at every coordinate $y=Y$. Let's call this operation $\mathcal{M}^Y$, such that : $$ \mathcal{M}^Y(I)(x, y-Y, t) = \frac 1 2 \cdot (I(x, y-Y, t) + I(x, -y-Y, t)) $$ It follows: $$ \mathcal{M}^Y(I)(x, y, t) = \frac 1 2 \cdot (I(x, y, t) + I(x, -y-2Y, t)) $$

In particular, in the example above, $I_M = \mathcal{M}^0(I)$.

Interestingly, the Fourier spectrum of such a transform is dual and in particular: $$ \mathcal{F}(\mathcal{M}^0(I))(f_x, f_y, f_t) = \mathcal{F}(\frac 1 2 \cdot (I(x, y, t) + I(x, -y, t))) $$ thus $$ \mathcal{F}(\mathcal{M}^0(I))(f_x, f_y, f_t) =\frac 1 2 \cdot (\mathcal{F}(I)(f_x, f_y, f_t) - \mathcal{F}(I)(f_x, -f_y, f_t))) $$ It means that this "mirrored motion clouds" are still random phase textures but where the phase is bound to have a mirror symmetry with respect to the central plane (here, $f_x, f_t$).

For instance, let's add it at $N_y/4$ by creating $\mathcal{M}^{N_y/4}(I)$:

In [6]:
def mirror(movie, Y=0):
    # translate
    movie = np.roll(movie, -Y, axis=0)
    # add the mirror image
    mirrored_movie = .5*(movie + movie[::-1, :, :])
    # translate in the other direction
    return np.roll(mirrored_movie, Y, axis=0)

name_ = name + '_fuse_quarter'
mirrored_movie = mirror(movie, Y=int(N_Y/4))
mc.anim_save(mirrored_movie, os.path.join(mc.figpath, name_))
mc.in_show_video(name_, figpath=mc.figpath)

Note that in the particular case of Motion Clouds (and everything created in the Fourier domain in general), the stimulus is periodic in time and space. Due to this, we have $I(x, y + N_Y, t)=I(x, y, t)$ and thus $$ \mathcal{M}^Y(I)(x, y-Y+N_Y/2, t) = \frac 1 2 \cdot (I(x, y-Y+N_Y/2, t) + I(x, -y-Y+N_Y/2- N_Y, t))= \mathcal{M}^Y(I)(x, -y-Y+N_Y/2, t) $$

That is, $Y$ is an axis of symmetry for $\mathcal{M}^Y(I)$, but $Y+N_Y/2$ is too. For $Y=0$, there is an axis of symmetry at $y=0$ and one at $y=N_y/2$.

Now we may pipe both operations: $$ I_MM = \mathcal{M}^{N_y/4}(\mathcal{M}^0(I)) $$

We check that: $$ I_MM(-y) = \mathcal{M}^{N_y/4}(\mathcal{M}^0(I(-y))) = \mathcal{M}^{N_y/4}(\mathcal{M}^0(I(y))) = I_MM(y) $$ and $$ I_MM(-y-N_y/4) = \mathcal{M}^{N_y/4}(.5 * (I(y-N_y/4)+I(-y+N_y/4)))) = .25 * (I(y-N_y/4)+I(-y-N_y/4)+I(y+N_y/4)+I(-y+N_y/4)) = I_MM(y-N_y/4) $$ The stimulus is thus symmetric at $y \in \{ 0, N_y/4, N_y/2, 3*N_y/4 \} $.

In [7]:
name_ = name + '_fuse_double'

mirrored_movie = mirror(mirror(movie, Y=0), Y=int(N_Y/4))
mc.anim_save(mirrored_movie, os.path.join(mc.figpath, name_))
mc.in_show_video(name_, figpath=mc.figpath)

You may have noticed that contrast has decreased: that's quite normal as we make sums and make the random phase texture more coherent. It can be easily proven that if we repeat this procedure again and again to be symmetric at every signle coordinate $y$, we end up with a constant stimulus (in the $y$ coordinate, not $x$ or $t$).

some book keeping for the notebook

In [8]:
%load_ext version_information
%version_information numpy, scipy, matplotlib, MotionClouds
Out[8]:
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:00:18 2018 CET