Sunday, July 4, 2010

Gabor Patch

With the upcoming release of PEBL 0.11, I have implemented a canvas, which allows you to draw on it and set arbitrary pixels, as well as composite objects together.  This moves PEBL toward greater capability in a number of visual perception domains, where noisy masks are created or sinusoidal gratings are used.  One of the most famous of these types of stimuli is known as the 'Gabor patch': a sine-wave grating in a gaussian window.


There are a number of places on the internet that tell you how to create Gabor patches, mostly in MATLAB, which makes it especially easy, and if you are clever, you can do apparently do it in about one line of code. In the example I'm making, it will just support greyscale values.  Note that standard RGB space supports 256 levels of grey, so the task will be to create a function that combines a 2D gaussian and a sine wave, and then scales these to grey level 0 to 255.


There are a few basic parameters that are important to control.  They include:

  1. Size of the image
    We are making a little image from scratch.  It will be a square.  The larger the square, the more time it will take--double the size of the square, and you multiply the number of pixels by 4.
  2. Size of the circle
   The circle controlled with a 2D gaussian. Thus, it 'fades out' smoothly.  The size is controlled with by the standard deviation of the gaussian.  Generally, the sd needs to be less than about 20% of the image size to prevent noticable clipping.

 3. Grating frequency.
   The grating is a cosine wave.    We will control how many times is the grating cycles within the image size.

4.  Angle of rotation.
     The grating can be rotated an arbitrary number of radians.

5. Phase.  Any sine wave can be phase-shifted.  This is important to produce motion drift, and also to make sure the gratings are centered in the way you want them to be.

6. Background level.  We'll specify a background grey level to fade into as the gaussian goes to 0.


I've created a small function that does all of this within a double loop.
 MakeGabor(size, freq, sd, rotation, phase, bgcol)

  An example use, to create a 100-pixel patch, with 8 cycles,a gaussian with a 15-pixel sd, a rotation of .5 radians, a phase shift of 0 radians, and a background color of 128 (midway between 0 and 255)
   canvas <- MakeGabor(100,8,15, .5, 0,127)


The basic formula for computing the level at a given XY position is:

      #compute rotation: where on the cosine grating should we be looking?
       stheta <- Sin(theta)
       ctheta <- Cos(theta)
        newx <- x*ctheta + y*stheta - center  #newx Is not really used.
        newy <- -x * stheta + y*ctheta - center

        ##Lookup the proper cosine value
        leva <-(Cos(newy*6.283185 +phase)+1)/2


        ##The gaussian window
        levb <- (NormalDensity((i-center)/sd) *  NormalDensity((j-center)/sd))/normmax
         ##trim the gaussian to 0 if it gets too low
        levb <- levb * (levb>.01)

        ##The grating is a weighted average between leva and the bglev:
        lev <- Round((leva*levb*255 +(1-levb)*bglev))

  


MakeGabor() is written in PEBL,  Basically, each pixel of the image is looped through, and a combined level of rotated sinusoidal grating and gaussian is computed; a PEBL color is created with the appropriate color (mapping 0 to 1 onto 0 to 255), and the pixel is set to that color.  All of this is pretty slow.  For a small 100x100 patch, expect it to take 1 to 3 seconds on reasonably fast computers.  This can be a little slow for real-time stimuli.  If I compiled this into a C++ function, it would probably take only tens or hundreds of milliseconds, and if we were to use accelerated graphics cards to composite, it could probably be changed very quickly in real time.  But for now, if you can't wait 1-3 seconds between trials, your best bet is to cache a bunch of them at program outset.  In fact, you may want to make a separate program that creates the gabor patch, saves it to a .png with  WritePNG, and then read them in later when you need to use them.

The demo gabor.pbl shows how a range of patches can be created, cached in a list, and then flipped through to create an animation.



In the demo, I created three sets of Gabor patches by varying a single parameter in each set: angle, frequency, and phase.   A actually created 20 of each, but only plotted the first 6 in the screenshot above.  Following plotting, I looped through each set, moving each frame to the same location.  Then, I looped through the  sets, showing/hiding each one with a Wait and Draw() in between.  The resulting animation is shown below:


 

No comments: