Eschercube

Eschercube

Motion Clouds are a set of stimuli designed to explore in a systematic way the functional response of a sensory system to a natural-like motion stimulus.

These are optimized for translating, full-field motion and are by construction textures synthesized from randomly placed similar motion patches with characteristics spatial parameters. The object of such an endeavor is to systematically test a system to varying the parameters by testing the response to a series of such textures.

We show here for a fixed set of central parameters (mean speed, direction, orientation and spatial frequency) a cube constituted by a family of such Motion Clouds when varying the bandwidth parameters for speed (left panel), frequency (right panel) and orientation (top panel).

Each elementary cube in the larger cube denoting the parameter space represents a Motion Cloud and is shown as a cube to represent the corresponding movie, with time flowing from lower left to upper right in the right and top facets. Overlaid hue gives a measure of a typical response for a sensory response (here a motion energy model) which gives a complete characterization of the sensory system at hand.

In [1]:
from IPython.display import HTML
HTML(data='''<video alt="MCartwork" controls autoplay="1" loop="1" width="100%">
                <source src={url} type="video/mp4"/>
             </video>'''.format(url='http://motionclouds.invibe.net/files/MCartwork.mp4'))
Out[1]:

the making of the parameter cube : 1) making a cristal

In [2]:
infile = \
"""ATOM 001 N   ASN A  4 0 0 0
ATOM 002 CA  ASN A  4 0 0 1
ATOM 003 C   ASN A  4 0 0 2
ATOM 004 O   ASN A  4 0 0 3
ATOM 005 CB  ASN A  4 0 0 4
ATOM 006 CG  ASN A  4 0 1 0
ATOM 007 OD1 ASN A  4 0 1 1
ATOM 008 ND2 ASN A  4 0 1 2
ATOM 009 N   CYS A  5 0 1 3
ATOM 010 CA  CYS A  5 0 1 4
ATOM 011 C   CYS A  5 0 2 0
ATOM 012 O   CYS A  5 0 2 1
ATOM 013 CB  CYS A  5 0 2 2
ATOM 014 SG  CYS A  5 0 2 3
ATOM 015 N   GLU A  6 0 2 4
ATOM 016 CA  GLU A  6 0 3 0
ATOM 017 C   GLU A  6 0 3 1
ATOM 018 O   GLU A  6 0 3 2
ATOM 019 CB  GLU A  6 0 3 3
ATOM 020 CG  GLU A  6 0 3 4
ATOM 021 CD  GLU A  6 0 4 0
ATOM 022 OE1 GLU A  6 0 4 1
ATOM 023 OE2 GLU A  6 0 4 2
ATOM 024 N   ARG A  7 0 4 3
ATOM 025 CA  ARG A  7 0 4 4
ATOM 026 C   ARG A  7 1 0 0
ATOM 027 O   ARG A  7 1 0 1
ATOM 028 CB  ARG A  7 1 0 2
ATOM 029 CG  ARG A  7 1 0 3
ATOM 030 CD  ARG A  7 1 0 4
ATOM 031 NE  ARG A  7 1 1 0
ATOM 032 CZ  ARG A  7 1 1 1
ATOM 033 NH1 ARG A  7 1 1 2
ATOM 034 NH2 ARG A  7 1 1 3
ATOM 035 N   VAL A  8 1 1 4
ATOM 036 CA  VAL A  8 1 2 0
ATOM 037 C   VAL A  8 1 2 1
ATOM 038 O   VAL A  8 1 2 2
ATOM 039 CB  VAL A  8 1 2 3
ATOM 040 CG1 VAL A  8 1 2 4
ATOM 041 CG2 VAL A  8 1 3 0
ATOM 042 N   TRP A  9 1 3 1
ATOM 043 CA  TRP A  9 1 3 2
ATOM 044 C   TRP A  9 1 3 3
ATOM 045 O   TRP A  9 1 3 4
ATOM 046 CB  TRP A  9 1 4 0
ATOM 047 CG  TRP A  9 1 4 1
ATOM 048 CD1 TRP A  9 1 4 2
ATOM 049 CD2 TRP A  9 1 4 3
ATOM 050 NE1 TRP A  9 1 4 4
ATOM 051 CE2 TRP A  9 2 0 0
ATOM 052 CE3 TRP A  9 2 0 1
ATOM 053 CZ2 TRP A  9 2 0 2
ATOM 054 CZ3 TRP A  9 2 0 3
ATOM 055 CH2 TRP A  9 2 0 4
ATOM 056 N   LEU A 10 2 1 0
ATOM 057 CA  LEU A 10 2 1 1
ATOM 058 C   LEU A 10 2 1 2
ATOM 059 O   LEU A 10 2 1 3
ATOM 060 CB  LEU A 10 2 1 4
ATOM 061 CG  LEU A 10 2 2 0
ATOM 062 CD1 LEU A 10 2 2 1
ATOM 063 CD2 LEU A 10 2 2 2
ATOM 064 N   ASN A 11 2 2 3
ATOM 065 CA  ASN A 11 2 2 4
ATOM 066 C   ASN A 11 2 3 0
ATOM 067 O   ASN A 11 2 3 1
ATOM 068 CB  ASN A 11 2 3 2
ATOM 069 CG  ASN A 11 2 3 3
ATOM 070 OD1 ASN A 11 2 3 4
ATOM 071 ND2 ASN A 11 2 4 0
ATOM 072 N   VAL A 12 2 4 1
ATOM 073 CA  VAL A 12 2 4 2
ATOM 074 C   VAL A 12 2 4 3
ATOM 075 O   VAL A 12 2 4 4
ATOM 076 CB  VAL A 12 3 0 0
ATOM 077 CG1 VAL A 12 3 0 1
ATOM 078 CG2 VAL A 12 3 0 2
ATOM 079 N   THR A 13 3 0 3
ATOM 080 CA  THR A 13 3 0 4
ATOM 081 C   THR A 13 3 1 0
ATOM 082 O   THR A 13 3 1 1
ATOM 083 CB  THR A 13 3 1 2
ATOM 084 OG1 THR A 13 3 1 3
ATOM 085 CG2 THR A 13 3 1 4
ATOM 086 N   PRO A 14 3 2 0
ATOM 087 CA  PRO A 14 3 2 1
ATOM 088 C   PRO A 14 3 2 2
ATOM 089 O   PRO A 14 3 2 3
ATOM 090 CB  PRO A 14 3 2 4
ATOM 091 CG  PRO A 14 3 3 0
ATOM 092 CD  PRO A 14 3 3 1
ATOM 093 N   ALA A 15 3 3 2
ATOM 094 CA  ALA A 15 3 3 3
ATOM 095 C   ALA A 15 3 3 4
ATOM 096 O   ALA A 15 3 4 0
ATOM 097 CB  ALA A 15 3 4 1
ATOM 098 N   THR A 16 3 4 2
ATOM 099 CA  THR A 16 3 4 3
ATOM 100 C   THR A 16 3 4 4
ATOM 101 O   THR A 16 4 0 0
ATOM 102 CB  THR A 16 4 0 1
ATOM 103 OG1 THR A 16 4 0 2
ATOM 104 CG2 THR A 16 4 0 3
ATOM 105 N   LEU A 17 4 0 4
ATOM 106 CA  LEU A 17 4 1 0
ATOM 107 C   LEU A 17 4 1 1
ATOM 108 O   LEU A 17 4 1 2
ATOM 109 CB  LEU A 17 4 1 3
ATOM 110 CG  LEU A 17 4 1 4
ATOM 111 CD1 LEU A 17 4 2 0
ATOM 112 CD2 LEU A 17 4 2 1
ATOM 113 N   ARG A 18 4 2 2
ATOM 114 CA  ARG A 18 4 2 3
ATOM 115 C   ARG A 18 4 2 4
ATOM 116 O   ARG A 18 4 3 0
ATOM 117 CB  ARG A 18 4 3 1
ATOM 118 CG  ARG A 18 4 3 2
ATOM 119 CD  ARG A 18 4 3 3
ATOM 120 NE  ARG A 18 4 3 4
ATOM 121 CZ  ARG A 18 4 4 0
ATOM 122 NH1 ARG A 18 4 4 1
ATOM 123 NH2 ARG A 18 4 4 2
ATOM 124 N   SER A 19 4 4 3
ATOM 125 CA  SER A 19 4 4 4
CONECT 001 002 003 004 005
CONECT 006 007 008 009 010
CONECT 011 012 013 014 015
CONECT 016 017 018 019 020
CONECT 021 022 023 024 025
CONECT 026 027 028 029 030
CONECT 031 032 033 034 035
CONECT 036 037 038 039 040
CONECT 041 042 043 044 045
CONECT 046 047 048 049 050
CONECT 051 052 053 054 055
CONECT 056 057 058 059 060
CONECT 061 062 063 064 065
CONECT 066 067 068 069 070
CONECT 071 072 073 074 075
CONECT 076 077 078 079 080
CONECT 081 082 083 084 085
CONECT 086 087 088 089 090
CONECT 091 092 093 094 095
CONECT 096 097 098 099 100
CONECT 101 102 103 104 105
CONECT 106 107 108 109 110
CONECT 111 112 113 114 115
CONECT 116 117 118 119 120
CONECT 121 122 123 124 125
CONECT 001 006 011 016 021
CONECT 026 031 036 041 046
CONECT 051 056 061 066 071
CONECT 076 081 086 091 096
CONECT 101 106 111 116 121
CONECT 002 007 012 017 022
CONECT 027 032 037 042 047
CONECT 052 057 062 067 072
CONECT 077 082 087 092 097
CONECT 102 107 112 117 122
CONECT 003 008 013 018 023
CONECT 028 033 038 043 048
CONECT 053 058 063 068 073
CONECT 078 083 088 093 098
CONECT 103 108 113 118 123
CONECT 004 009 014 019 024
CONECT 029 034 039 044 049
CONECT 054 059 064 069 074
CONECT 079 084 089 094 099
CONECT 104 109 114 119 124
CONECT 005 010 015 020 025
CONECT 030 035 040 045 050
CONECT 055 060 065 070 075
CONECT 080 085 090 095 100
CONECT 105 110 115 120 125
CONECT 001 026 051 076 101
CONECT 002 027 052 077 102
CONECT 003 028 053 078 103
CONECT 004 029 054 079 104
CONECT 005 030 055 080 105
CONECT 006 031 056 081 106
CONECT 007 032 057 082 107
CONECT 008 033 058 083 108
CONECT 009 034 059 084 109
CONECT 010 035 060 085 110
CONECT 011 036 061 086 111
CONECT 012 037 062 087 112
CONECT 013 038 063 088 113
CONECT 014 039 064 089 114
CONECT 015 040 065 090 115
CONECT 016 041 066 091 116
CONECT 017 042 067 092 117
CONECT 018 043 068 093 118
CONECT 019 044 069 094 119
CONECT 020 045 070 095 120
CONECT 021 046 071 096 121
CONECT 022 047 072 097 122
CONECT 023 048 073 098 123
CONECT 024 049 074 099 124
CONECT 025 050 075 100 125
"""

the making of the parameter cube : 2) filling the cristal

In [3]:
import os
if not os.path.exists('../galleries/Artwork/MCartwork.mp4'):
    import numpy as np
    from tvtk.api import tvtk
    from mayavi.sources.vtk_data_source import VTKDataSource
    from mayavi import mlab
    import MotionClouds as mc
    import itertools
    import os

    def source(dim, bwd):
        """
        Create motion cloud source
        """
        fx, fy, ft = dim
        z = mc.envelope_gabor(fx, fy, ft, B_sf=bwd[0], B_V=bwd[1], B_theta=bwd[2])
        data = mc.rectif(mc.random_cloud(z))
        return data

    def image_data(dim, sp, orig, bwd):
        """
        Create Image Data for the surface from a data source.
        """
        data = source(dim, bwd)
        i = tvtk.ImageData(spacing=sp, origin=orig)
        i.point_data.scalars = data.ravel()
        i.point_data.scalars.name = 'scalars'
        i.dimensions = data.shape
        return i

    def view(dataset):
        """
        Open up a mayavi scene and display the cubeset in it.
        """
        engine = mlab.get_engine()
        src = VTKDataSource(data=dataset)
        engine.add_source(src)
        mlab.pipeline.surface(src, colormap='gray')
        return

    def main(dim, sp=(1, 1, 1), orig=(0, 0, 0), B=(.01, .1, .4)):
        view(image_data(dim=dim, sp=sp, orig=orig, bwd=B))
        return

    def get_nodes_and_edges():
        nodes = dict()
        edges = list()
        atoms = set()
        # Build the graph from the PDB information
        for line in infile.splitlines():
            line = line.split()
            if line[0] == 'ATOM':
                nodes[line[1]] = (line[2], line[6], line[7], line[8])
                atoms.add(line[2])
            elif line[0] == 'CONECT':
                for start, stop in zip(line[1:-1], line[2:]):
                    edges.append((start, stop))
        atoms = list(atoms)
        atoms.sort()
        atoms = dict(zip(atoms, range(len(atoms))))
        # Turn the graph into 3D positions, and a connection list.
        labels = dict()
        x       = list()
        y       = list()
        z       = list()
        scalars = list()
        for index, label in enumerate(nodes):
            labels[label] = index
            this_scalar, this_x, this_y, this_z= nodes[label]
            scalars.append(atoms[this_scalar])
            x.append(float(this_x))
            y.append(float(this_y))
            z.append(float(this_z))
        connections = list()
        for start, stop in edges:
            connections.append((labels[start], labels[stop]))
        x       = np.array(x)
        y       = np.array(y)
        z       = np.array(z)
        scalars = np.array(scalars)
        return x, y, z, connections, scalars

    size = 2**4 ##
    space = 1.5 * size # space between 2 cubes
    N = 5
    idx = np.arange(N)
    pos = idx * space
    Bf = np.logspace(-2., 0.1, N)
    Bv = [0.01, 0.1, 0.5, 1.0, 10.0]
    sigma_div = [2, 3, 5, 8, 13]
    Bo = np.pi/np.array(sigma_div)
    fx, fy, ft = mc.get_grids(size, size, size)
    downscale = 4
    mlab.figure(1, bgcolor=(.6, .6, .6), fgcolor=(0, 0, 0), size=(1600/downscale,1200/downscale))
    mlab.clf()
    do_grid, do_splatter, do_MCs = True, True, True

    """ Generate the cubical graph structure to connect each individual cube """
    if do_grid:
        x, y, z, connections, scalars = get_nodes_and_edges()
        x = x * space + size / 2.
        y = y * space + size / 2.
        z = z * space + size / 2.
        scalars = 0.8*np.ones(x.shape)
        pts = mlab.points3d(x, y, z, scalars, colormap='Blues', scale_factor=5.0, resolution=10)
        pts.mlab_source.dataset.lines = np.array(connections)
        # Use a tube filter to plot tubes on the link, varying the radius with the scalar value
        tube = mlab.pipeline.tube(pts, tube_sides=15, tube_radius=1.5)
        mlab.pipeline.surface(tube, color=(0.4, 0.4, 0.4) )

    """ Gaussian Splatter """
    if do_splatter:
        # Visualize the local atomic density
        x_, y_, z_ = np.mgrid[0:(N*space), 0:(N*space), 0:(N*space)]
        response = np.exp(-((x_-4*space)**2 +(y_- 1*space)**2 +((z_-3.4*space)/2.)**2)/2/(1.3*space)**2)
        mlab.contour3d(response, opacity=0.5)

    """ Generate the Motion Clouds cubes """
    if do_MCs:
        for i, j, k in list(itertools.product(idx, idx, idx)):
            main(dim=(fx, fy, ft), orig=(pos[i], pos[j], pos[k]), B=(Bf[i], Bv[k], Bo[j]))
    elevation = 90. - 18.
    view = mlab.view(azimuth=290., elevation=elevation, distance='auto', focalpoint='auto')
    distance = view[2]
    mlab.savefig('MCartwork.png')
    if True:
        N_frame = 64 ##
        for i_az, azimuth in enumerate(np.linspace(0, 360, N_frame, endpoint=False)):
            mlab.view(*view)
            mlab.view(azimuth=azimuth)
            mlab.view(distance=distance*(.7 + 0.3*np.cos(azimuth*2*np.pi/360.)))
            mlab.savefig('_MCartwork%03d.png' % i_az, size=(1600/downscale, 1200/downscale))
        os.system('ffmpeg -y -i _MCartwork%03d.png  MCartwork.mpg')
        os.system('ffmpeg -y -i _MCartwork%03d.png  MCartwork.mp4')
        os.system('convert -delay 8 -loop 0 -colors 256  -scale 25% -layers Optimize  _MCartwork*.png  MCartwork.gif')
        os.system('rm _MCartwork*')

the making of the parameter cube : 3) post-production

In [4]:
from IPython.display import display, Image
#display(Image(filename='../galleries/Artwork/MCartwork.png', embed=True))

Image(url='http://motionclouds.invibe.net/galleries/Artwork/MCartwork.png', embed=False)
Out[4]: