Ejemplo n.º 1
0
class DenseNoise(TopographicaBasedVisualStimulus):
    """
    Dense Noise 


    Produces a matrix with the values 0, 0.5 and 1 allocated at random
    and then scaled and translated by scale and offset with the next
    transformation rule:  result*scale + offset
    """

    experiment_seed = SNumber(dimensionless,
                              doc="The seed of a given experiment")
    duration = SNumber(ms, doc='Total duration of the frames')
    time_per_image = SNumber(ms, doc='Duration of one image')
    grid_size = SNumber(dimensionless, doc="Grid Size ")

    def __init__(self, **params):
        TopographicaBasedVisualStimulus.__init__(self, **params)
        assert (self.time_per_image / self.frame_duration) % 1.0 == 0.0

    def frames(self):
        aux = imagen.random.DenseNoise(
            grid_density=self.grid_size * 1.0 / self.size_x,
            offset=0,
            scale=2 * self.background_luminance,
            bounds=BoundingBox(radius=self.size_x / 2),
            xdensity=self.density,
            ydensity=self.density,
            random_generator=numpy.random.RandomState(
                seed=self.experiment_seed))

        while True:
            aux2 = aux()
            for i in range(self.time_per_image / self.frame_duration):
                yield (aux2, [0])
Ejemplo n.º 2
0
class FlatDisk(TopographicaBasedVisualStimulus):
    """
    A flat luminance aperture of specified radius.

    This stimulus corresponds to a disk of constant luminance of 
    pre-specified *radius* flashed for the *duration* of milliseconds
    on a constant background of *background_luminance* luminance.
    The luminance of the disk is specified by the *contrast* parameter,
    and is thus *background_luminance* + *background_luminance* \* (*self.contrast*/100.0).

    Notes
    -----
    size_x/2 is interpreted as the bounding box.
    """
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")
    radius = SNumber(degrees,
                     doc="The radius of the disk - in degrees of visual field")

    def frames(self):
        self.current_phase = 0
        while True:
            d = imagen.Disk(smoothing=0.0,
                            size=self.radius * 2,
                            offset=self.background_luminance,
                            scale=self.background_luminance *
                            (self.contrast / 100.0),
                            bounds=BoundingBox(radius=self.size_x / 2),
                            xdensity=self.density,
                            ydensity=self.density)()

            yield (d, [self.current_phase])
Ejemplo n.º 3
0
class DriftingGratingWithEyeMovement(TopographicaBasedVisualStimulus):
    """
    A visual stimulus that simulates an eye movement over a drifting  gratings.

    This is a movie that is generated by translating a 
    full-field drifting sinusoidal gratings along a pre-specified path 
    (presumably containing path that corresponds to eye-movements).
    """

    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    spatial_frequency = SNumber(cpd, doc="Spatial frequency of grating")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of grating")
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")
    eye_movement_period = SNumber(
        ms,
        doc=
        "The time between two consequitve eye movements recorded in the eye_path file"
    )
    eye_path_location = SString(
        doc="Location of file containing the eye path (two columns of numbers)"
    )

    def frames(self):

        f = open(self.eye_path_location, 'r')
        self.eye_path = pickle.load(f)
        self.time = 0
        self.current_phase = 0
        while True:
            location = self.eye_path[int(
                numpy.floor(self.frame_duration * self.time /
                            self.eye_movement_period))]

            image = imagen.SineGrating(
                orientation=self.orientation,
                x=location[0],
                y=location[1],
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(points=((-self.size_x / 2,
                                            -self.size_y / 2),
                                           (self.size_x / 2,
                                            self.size_y / 2))),
                offset=self.background_luminance * (100.0 - self.contrast) /
                100.0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)()
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
            yield (image, [self.time])
            self.time = self.time + 1
Ejemplo n.º 4
0
class DriftingSinusoidalGratingDisk(TopographicaBasedVisualStimulus):
    """
    A drifting sinusoidal grating confined to a aperture of specified radius.

    A movies in which luminance is modulated as a sinusoid along one 
    axis and is constant in the perpendicular axis. The phase of 
    the sinusoid is advancing with time leading to a drifting pattern.
    The whole stimulus is confined to an aperture of  

    Notes
    -----
    size_x/2 is interpreted as the bounding box radius.
    """
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    spatial_frequency = SNumber(cpd, doc="Spatial frequency of the grating")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of the grating")
    radius = SNumber(
        degrees,
        doc="The radius of the grating disk - in degrees of visual field")

    def frames(self):
        self.current_phase = 0
        while True:
            a = imagen.SineGrating(orientation=self.orientation,
                                   frequency=self.spatial_frequency,
                                   phase=self.current_phase,
                                   bounds=BoundingBox(radius=self.size_x / 2),
                                   offset=self.background_luminance *
                                   (100.0 - self.contrast) / 100.0,
                                   scale=2 * self.background_luminance *
                                   self.contrast / 100.0,
                                   xdensity=self.density,
                                   ydensity=self.density)()

            b = imagen.Constant(scale=self.background_luminance,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()
            c = imagen.Disk(smoothing=0.0,
                            size=self.radius * 2,
                            scale=1.0,
                            bounds=BoundingBox(radius=self.size_x / 2),
                            xdensity=self.density,
                            ydensity=self.density)()
            d1 = numpy.multiply(a, c)
            d2 = numpy.multiply(b, -(c - 1.0))
            d = numpy.add.reduce([d1, d2])
            yield (d, [self.current_phase])
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
Ejemplo n.º 5
0
class NaturalImageWithEyeMovement(TopographicaBasedVisualStimulus):
    """
    A visual stimulus that simulates an eye movement over a static image.

    This is a movie that is generated by translating a 
    static image along a pre-specified path (presumably containing path
    that corresponds to eye-movements).
    
    """
    size = SNumber(
        degrees,
        doc="The length of the longer axis of the image in visual degrees")
    eye_movement_period = SNumber(
        ms,
        doc=
        "The time between two consequitve eye movements recorded in the eye_path file"
    )
    image_location = SString(doc="Location of the image")
    eye_path_location = SString(
        doc="Location of file containing the eye path (two columns of numbers)"
    )

    def frames(self):
        self.time = 0
        f = open(self.eye_path_location, 'r')
        self.eye_path = pickle.load(f)
        self.pattern_sampler = imagen.image.PatternSampler(
            size_normalization='fit_longest',
            whole_pattern_output_fns=[MaximumDynamicRange()])

        image = imagen.image.FileImage(
            filename=self.image_location,
            x=0,
            y=0,
            orientation=0,
            xdensity=self.density,
            ydensity=self.density,
            size=self.size,
            bounds=BoundingBox(points=((-self.size_x / 2, -self.size_y / 2),
                                       (self.size_x / 2, self.size_y / 2))),
            scale=2 * self.background_luminance,
            pattern_sampler=self.pattern_sampler)

        while True:
            location = self.eye_path[int(
                numpy.floor(self.frame_duration * self.time /
                            self.eye_movement_period))]
            image.x = location[0]
            image.y = location[1]
            yield (image(), [self.time])
            self.time += 1
Ejemplo n.º 6
0
class SingleValue(AnalysisDataStructure):
    """
    Data structure holding single value. This can be per model, if sheet parameter is None,
    or per sheet if sheet is specified. In principle it can also be per neuron if the neuron
    parameter is specified, but in most cases you probably want to use :class:`.PerNeuronValue`
    instead.
    """

    value = SNumber(units=None, default=None, doc="The value.")
    value_name = SString(doc="The name of the value.")
    period = SNumber(
        units=None,
        default=None,
        doc="The period of the value. If value is not periodic period=None")

    def __init__(self, **params):
        AnalysisDataStructure.__init__(self,
                                       identifier='SingleValue',
                                       **params)
Ejemplo n.º 7
0
class PerNeuronPairValue(AnalysisDataStructure):
    """
    Data structure holding values for each pair of neurons.
    
    Parameters
    ---------- 
    
    values : numpy.nd_array
           The 2D array holding the values. 

    value_units : quantities
                Quantities unit describing the units of the value
    
    ids : list(int)
        The ids of the neurons which are stored, in the same order as in the values (along both axis).
    """
    value_name = SString(doc="The name of the value.")
    period = SNumber(
        units=None,
        default=None,
        doc="The period of the value. If value is not periodic period=None")

    def __init__(self, values, idds, value_units, **params):
        AnalysisDataStructure.__init__(self,
                                       identifier='PerNeuronValue',
                                       **params)
        self.value_units = value_units
        self.values = numpy.array(values)
        self.ids = list(idds)
        assert values.shape == (len(idds), len(idds))

    def get_value_by_ids(self, idds1, idds2):
        """
        Parameters
        ---------- 
        idds1 : int or list(int)
            The ids for which the return the values along first dimension.
        
        idds2 : int or list(int)
            The ids for which the return the values along first dimension.
        
        Returns
        -------
        ids : scaler or array
            Array or scalar of values corresponding to `idds`.
        """
        if (isinstance(idds1, list) or isinstance(idds1, numpy.ndarray)) and (
                isinstance(idds2, list) or isinstance(idds2, numpy.ndarray)):
            return numpy.array(self.values)[
                [list(self.ids).index(i)
                 for i in idds1], :][:,
                                     [list(self.ids).index(i) for i in idds2]]
        else:
            return self.values[list(self.ids).index(idds1),
                               list(self.ids).index(idds2)]
Ejemplo n.º 8
0
class PSTextureStimulus(TextureBasedVisualStimulus):
    """
    A stimulus generated using the Portilla-Simoncelli algorithm (see textureLib/textureBasedStimulus.m)
    with statistics matched to the original image according to the Type. 
     
    Types:
        0 - original image
        1 - naturalistic texture image (matched higher order statistics)
        2 - spectrally matched noise (matched marginal statistics only). 

    Notes
    -----
    frames_number - the number of frames for which each image is presented

    ALERT!!!

    Because of optimization issues, the stimulus is not re-generated on every trial.
    We should add new 'empty' paramter to replace trial to force recalculaton of the stimuls.
    """

    stats_type = SNumber(dimensionless,
                         bounds=[0, 3],
                         doc="Type of statistial matching of the stimulus")
    seed = int

    def frames(self):
        fieldsize_x = self.size_x * self.density
        fieldsize_y = self.size_y * self.density
        libpath = visual_stimulus.__file__.replace(
            "/visual_stimulus.pyc",
            "") + "/textureLib"  #path to the image processing library
        matlabPyrToolspath = os.path.join(libpath, "textureSynth",
                                          "matlabPyrTools")
        if not os.path.isdir(matlabPyrToolspath):
            raise IOError(
                "matlabPyrTools should be downloaded from https://github.com/LabForComputationalVision/matlabPyrTools and its content should be put in the directory "
                + matlabPyrToolspath)

        octave.addpath(libpath)
        im = octave.textureBasedStimulus(self.texture_path, self.stats_type,
                                         self.seed, fieldsize_x, fieldsize_y,
                                         libpath)

        im = im / 255 * 2 * self.background_luminance
        #scipy.misc.toimage(im, cmin=0.0, cmax=2*self.background_luminance).save('/home/kaktus/Documents/mozaik/examples/img' + str(self.trial) + '.jpg')
        scipy.misc.toimage(
            im, cmin=0.0, cmax=2 *
            self.background_luminance).save('img' +
                                            str(len(self.texture_path)) +
                                            "type" + str(self.stats_type) +
                                            '.jpg')
        assert (im.shape == (fieldsize_x, fieldsize_y)
                ), "Image dimensions do not correspond to visual field size"
        while True:
            yield (im, [0])
Ejemplo n.º 9
0
class FullfieldDriftingSinusoidalGrating(TopographicaBasedVisualStimulus):
    """
    A full field sinusoidal grating stimulus. 
     
    A movies in which luminance is modulated as a sinusoid along one 
    axis and is constant in the perpendicular axis. The phase of 
    the sinusoid is increasing with time leading to a drifting pattern. 

    Notes
    -----
    `max_luminance` is interpreted as scale and `size_x/2` as the bounding box radius.
    """

    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    spatial_frequency = SNumber(cpd, doc="Spatial frequency of grating")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of grating")
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")

    def frames(self):
        self.current_phase = 0
        i = 0
        while True:
            i += 1
            yield (imagen.SineGrating(
                orientation=self.orientation,
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(radius=self.size_x / 2),
                offset=self.background_luminance * (100.0 - self.contrast) /
                100.0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)(), [self.current_phase])
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
Ejemplo n.º 10
0
class SparseNoise(TopographicaBasedVisualStimulus):
    """
    Sparse noise stimulus.
    
    Produces a matrix filled with 0.5 values and one random entry with 0 or 1.
    The output is then transformed with the following rule:
    output = output * scale  + offset.
    """

    experiment_seed = SNumber(dimensionless,
                              doc="The seed of a given experiment")
    duration = SNumber(ms, doc="Total duration of the frames")
    time_per_image = SNumber(ms, doc="Duration of one image")
    grid_size = SNumber(dimensionless, doc="Grid Size ")
    grid = SNumber(dimensionless,
                   doc="Boolean string to decide whether there is grid or not")

    def __init__(self, **params):
        TopographicaBasedVisualStimulus.__init__(self, **params)
        assert (self.time_per_image / self.frame_duration) % 1.0 == 0.0

    def frames(self):

        aux = imagen.random.SparseNoise(
            grid_density=self.grid_size * 1.0 / self.size_x,
            grid=self.grid,
            offset=0,
            scale=2 * self.background_luminance,
            bounds=BoundingBox(radius=self.size_x / 2),
            xdensity=self.density,
            ydensity=self.density,
            random_generator=numpy.random.RandomState(
                seed=self.experiment_seed))
        while True:
            aux2 = aux()
            for i in range(self.time_per_image / self.frame_duration):
                yield (aux2, [0])
Ejemplo n.º 11
0
class FullfieldDriftingSquareGrating(TopographicaBasedVisualStimulus):
    """
    A full field square grating stimulus.

    A movies composed of interlaced dark and bright bars spanning the width  
    the visual space. The bars are moving a direction perpendicular to their
    long axis. The speed is dictated by the *temporal_freuquency* parameter
    the width of the bars by *spatial_frequency* parameter.
    """

    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    spatial_frequency = SNumber(cpd, doc="Spatial frequency of grating")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of grating")
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")

    def frames(self):
        self.current_phase = 0
        i = 0
        while True:
            i += 1
            yield (imagen.SquareGrating(
                orientation=self.orientation,
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(radius=self.size_x / 2),
                offset=self.background_luminance * (100.0 - self.contrast) /
                100.0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)(), [self.current_phase])
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
Ejemplo n.º 12
0
class FlashedBar(TopographicaBasedVisualStimulus):
    """
    A flashed bar.

    This stimulus corresponds to flashing a bar of specific *orientation*,
    *width* and *length* at pre-specified position for *flash_duration* of milliseconds. 
    For the remaining time, until the *duration* of the stimulus, constant *background_luminance* 
    is displayed.
    """
    relative_luminance = SNumber(
        dimensionless,
        bounds=[0, 1.0],
        doc=
        "The scale of the stimulus. 0 is dark, 1.0 is double the background luminance"
    )
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    width = SNumber(cpd, doc="Spatial frequency of the grating")
    length = SNumber(Hz, doc="Temporal frequency of the grating")
    flash_duration = SNumber(ms, doc="The duration of the bar presentation.")
    x = SNumber(degrees, doc="The x location of the center of the bar.")
    y = SNumber(degrees, doc="The y location of the center of the bar.")

    def frames(self):
        num_frames = 0
        while True:

            d = imagen.RawRectangle(offset=self.background_luminance,
                                    scale=self.background_luminance *
                                    (self.relative_luminance - 0.5),
                                    bounds=BoundingBox(radius=self.size_x / 2),
                                    xdensity=self.density,
                                    ydensity=self.density,
                                    x=self.x,
                                    y=self.y,
                                    orientation=self.orientation,
                                    size=self.width,
                                    aspect_ratio=self.length / self.width)()

            b = imagen.Constant(scale=self.background_luminance,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()

            num_frames += 1
            if (num_frames - 1) * self.frame_duration < self.flash_duration:
                yield (d, [1])
            else:
                yield (b, [0])
Ejemplo n.º 13
0
class PerNeuronValue(AnalysisDataStructure):
    """
    Data structure holding single value per neuron.
    
    Parameters
    ---------- 
    
    values : list
           The vector of values one per neuron

    value_units : quantities
                Quantities unit describing the units of the value
    
    ids : list(int)
        The ids of the neurons which are stored, in the same order as in the values
    """
    value_name = SString(doc="The name of the value.")
    period = SNumber(
        units=None,
        default=None,
        doc="The period of the value. If value is not periodic period=None")

    def __init__(self, values, idds, value_units, **params):
        AnalysisDataStructure.__init__(self,
                                       identifier='PerNeuronValue',
                                       **params)
        self.value_units = value_units
        self.values = numpy.array(values)
        self.ids = list(idds)
        assert len(values) == len(idds), '%s %s' % (str(values), str(idds))

    def get_value_by_id(self, idds):
        """
        Parameters
        ---------- 
        idd : int or list(int)
            The ids for which the return the values.
        
        Returns
        -------
        ids : AnalogSignal or list(AnalogSignal)
            List (or single) of AnalogSignal objects corresponding to ids in `idd`.
        """
        if isinstance(idds, list) or isinstance(idds, numpy.ndarray):
            return [self.values[list(self.ids).index(i)] for i in idds]
        else:
            return numpy.array(self.values)[list(self.ids).index(idds)]
Ejemplo n.º 14
0
class PSTextureStimulusDisk(TextureBasedVisualStimulus):
    """
    A stimulus generated using the Portilla-Simoncelli algorithm (see textureLib/textureBasedStimulus.m)
    with statistics matched to the original image according to the Type.
    It is confined to an aperture of specific radius
    """

    radius = SNumber(degrees,
                     doc="The radius of the disk - in degrees of visual field")
    stats_type = SNumber(dimensionless,
                         bounds=[0, 3],
                         doc="Type of statistial matching of the stimulus")
    sample = SNumber(dimensionless,
                     doc="Index of the stimulus in its texture family")
    seed = SNumber(dimensionless, doc="The seed used for this stimulus")

    def frames(self):
        fieldsize_x = self.size_x * self.density
        fieldsize_y = self.size_y * self.density
        folder_name = Global.root_directory + "/TextureImagesStimuli"
        libpath = __file__.replace(
            "/texture_based.py",
            "") + "/textureLib"  #path to the image processing library
        matlabPyrToolspath = os.path.join(libpath, "textureSynth",
                                          "matlabPyrTools")
        if not os.path.isdir(matlabPyrToolspath):
            raise IOError(
                "matlabPyrTools should be downloaded from https://github.com/LabForComputationalVision/matlabPyrTools and its content should be put in the directory "
                + matlabPyrToolspath)

        octave.addpath(libpath)
        im = octave.textureBasedStimulus(self.texture_path, self.stats_type,
                                         self.seed, fieldsize_x, fieldsize_y,
                                         libpath)
        scale = 2. * self.background_luminance / (numpy.max(im) -
                                                  numpy.min(im))
        im = (im - numpy.min(im)) * scale

        if not os.path.exists(folder_name):
            os.mkdir(folder_name)

        assert (im.shape == (fieldsize_x, fieldsize_y)
                ), "Image dimensions do not correspond to visual field size"

        b = imagen.Constant(scale=self.background_luminance,
                            bounds=BoundingBox(radius=self.size_x / 2),
                            xdensity=self.density,
                            ydensity=self.density)()
        c = imagen.Disk(smoothing=0.0,
                        size=self.radius * 2,
                        scale=1.0,
                        bounds=BoundingBox(radius=self.size_x / 2),
                        xdensity=self.density,
                        ydensity=self.density)()

        d1 = numpy.multiply(im, c)
        d2 = numpy.multiply(b, -(c - 1.0))
        d = numpy.add.reduce([d1, d2])

        d = d.astype(numpy.uint8)
        IM = Image.fromarray(d)
        IM.save(folder_name + "/" + self.texture + "sample" +
                str(self.sample) + "type" + str(self.stats_type) + 'radius' +
                str(self.radius) + '.pgm')

        while True:
            yield (d, [0])
Ejemplo n.º 15
0
class VictorUninformativeSyntheticStimulus(VisualStimulus):
    """
    A stimulus generated using Jonathan Victor's maximum entropy algorithm: Victor, J. D., & Conte, M. M. (2012). Local image statistics: maximum-entropy constructions and perceptual salience. JOSA A, 29(7), 1313-1345.
    Constrain some uniformative 4 pixels correlation statistics as indicated in the pixel statistics list, and generate the rest of the statistics by maximizing the entropy of the image distribution
    """
    pixel_statistics = SNumber(
        dimensionless,
        bounds=[-1, 1],
        doc='The values of pixel correlations statistics')
    correlation_type = SInteger(
        dimensionless,
        bounds=[1, 2],
        doc=
        'Whether we want to modify the statistics of the 4 point correlations `wye` (1) or `foot` (2) (see Yu, Y., Schmid, A. M., & Victor, J. D. (2015). Visual processing of informative multipoint correlations arises primarily in V2. Elife, 4, e06604.)'
    )
    seed = SNumber(dimensionless, doc="The seed used for this stimulus")
    spatial_frequency = SNumber(dimensionless,
                                doc="The spatial frequency of the stimulus")

    def frames(self):
        assert len(
            numpy.nonzero(self.pixel_statistics)
        ) == 1, 'One and only one value in the tuple pixel_statistics must be non zero'
        for i, stat in enumerate(self.pixel_statistics):
            assert isinstance(stat, float) or isinstance(
                stat, int), 'Value at index %d is not a number' % i
            assert stat <= 1 and stat >= -1, 'Value at index %d is not within bounds [-1,1]' % i
        fieldsize_x = self.size_x * self.density
        fieldsize_y = self.size_y * self.density
        pixel_spatial_frequency = int(self.spatial_frequency * self.density)
        numpy.random.seed(self.seed)
        im = numpy.zeros((int(fieldsize_x), int(fieldsize_y)))

        for y in range(0, int(fieldsize_y), pixel_spatial_frequency):
            if y + pixel_spatial_frequency < im.shape[1]:
                ylim = y + pixel_spatial_frequency
            else:
                ylim = im.shape[1]

            for x in range(0, int(fieldsize_x), pixel_spatial_frequency):
                if x + pixel_spatial_frequency < im.shape[0]:
                    xlim = x + pixel_spatial_frequency
                else:
                    xlim = im.shape[0]

                if self.correlation_type == 1 and x > pixel_spatial_frequency * 2 and y > pixel_spatial_frequency * 2:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[0]) / 2:
                        im[y:ylim,
                           x:xlim] = (im[y - pixel_spatial_frequency,
                                         x - pixel_spatial_frequency] +
                                      im[y - pixel_spatial_frequency,
                                         x - 2 * pixel_spatial_frequency] +
                                      im[y - 2 * pixel_spatial_frequency,
                                         x - pixel_spatial_frequency]) % 510
                    else:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - pixel_spatial_frequency,
                                x - pixel_spatial_frequency] +
                             im[y - pixel_spatial_frequency,
                                x - 2 * pixel_spatial_frequency] +
                             im[y - 2 * pixel_spatial_frequency,
                                x - pixel_spatial_frequency]) % 510 - 255)

                elif self.correlation_type == 2 and y > pixel_spatial_frequency and x > 2 * pixel_spatial_frequency:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[1]) / 2:
                        im[y:ylim,
                           x:xlim] = (im[y - pixel_spatial_frequency,
                                         x - pixel_spatial_frequency] +
                                      im[y, x - 2 * pixel_spatial_frequency] +
                                      im[y - pixel_spatial_frequency, x]) % 510
                    else:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - pixel_spatial_frequency,
                                x - pixel_spatial_frequency] +
                             im[y, x - 2 * pixel_spatial_frequency] +
                             im[y - pixel_spatial_frequency, x]) % 510 - 255)

                else:
                    if numpy.random.rand() < 0.5:
                        im[y:ylim, x:xlim] = 255

        while True:
            yield (im, [0])
Ejemplo n.º 16
0
class VisualStimulus(BaseStimulus):
    """
    Abstract base class for visual stimuli.
    
    This class defines all parameters common to all visual stimuli.
    
    This class implements all functions specified by the :class:`mozaik.stimuli.stimulus.BaseStimulus` interface.
    The only function that remains to be implemented by the user whenever creating a new stimulus by subclassing
    this class is the :func:`mozaik.stimuli.stimulus.BaseStimulus.frames` function.
    
    This class also implements functions common to all visual stimuli that are required for it to be compatible 
    with the :class:`mozaik.space.VisualSpace` and  class.
    """
    background_luminance = SNumber(
        lux,
        doc=
        "Background luminance. Maximum luminance of object allowed is 2*background_luminance"
    )
    density = SNumber(1 / (degrees),
                      doc="The density of stimulus - units per degree")
    location_x = SNumber(degrees,
                         doc="x location of the center of  visual region.")
    location_y = SNumber(degrees,
                         doc="y location of the center of  visual region.")
    size_x = SNumber(degrees,
                     doc="The size of the region in degrees (asimuth).")
    size_y = SNumber(degrees,
                     doc="The size of the region in degrees (elevation).")

    def __init__(self, **params):
        BaseStimulus.__init__(self, **params)
        self._zoom_cache = {}
        self.region_cache = {}
        self.is_visible = True
        self.transparent = True  # And efficiency flag. It should be set to false by the stimulus if there are no transparent points in it.
        # This will avoid all the code related to transparency which is very expensive.
        self.region = VisualRegion(self.location_x, self.location_y,
                                   self.size_x, self.size_y)
        self.first_resolution_mismatch_display = True

    def _calculate_zoom(self, actual_pixel_size, desired_pixel_size):
        """
        Sometimes the interpolation procedure returns a new array that is too
        small due to rounding error. This is a crude attempt to work around that.
        """
        zoom = actual_pixel_size / desired_pixel_size
        for i in self.img.shape:
            if int(zoom * i) != round(zoom * i):
                zoom *= (1 + 1e-15)
        return zoom

    def display(self, region, pixel_size):
        assert isinstance(
            region, VisualRegion
        ), "region must be a VisualRegion-descended object. Actually a %s" % type(
            region)
        size_in_pixels = numpy.ceil(
            xy2ij((region.size_x, region.size_y)) /
            float(pixel_size)).astype(int)
        if self.transparent:
            view = TRANSPARENT * numpy.ones(size_in_pixels)
        else:
            view = self.background_luminance * numpy.ones(size_in_pixels)

        if region.overlaps(self.region):
            if not self.region_cache.has_key(region):
                intersection = region.intersection(self.region)
                assert intersection == self.region.intersection(
                    region
                )  # just a consistency check. Could be removed if necessary for performance.
                img_relative_left = (intersection.left -
                                     self.region.left) / self.region.width
                img_relative_width = intersection.width / self.region.width
                img_relative_top = (intersection.top -
                                    self.region.bottom) / self.region.height
                #img_relative_bottom = (intersection.bottom - self.region.bottom) / self.region.height
                img_relative_height = intersection.height / self.region.height
                view_relative_left = (intersection.left -
                                      region.left) / region.width
                view_relative_width = intersection.width / region.width
                view_relative_top = (intersection.top -
                                     region.bottom) / region.height
                #view_relative_bottom = (intersection.bottom - region.bottom) / region.height
                view_relative_height = intersection.height / region.height

                img_pixel_size = xy2ij(
                    (self.region.size_x, self.region.size_y
                     )) / self.img.shape  # is self.size a tuple or an array?
                assert img_pixel_size[0] == img_pixel_size[1]

                # necessary instead of == comparison due to the floating math rounding errors
                if abs(pixel_size - img_pixel_size[0]) < 0.0001:
                    img = self.img
                else:
                    if self.first_resolution_mismatch_display:
                        logger.warning(
                            "Image pixel size does not match desired size (%g vs. %g) degrees. This is extremely inefficient!!!!!!!!!!!"
                            % (pixel_size, img_pixel_size[0]))
                        logger.warning("Image pixel size %g,%g" %
                                       numpy.shape(self.img))
                        self.first_resolution_mismatch_display = False
                    # note that if the image is much larger than the view region, we might save some
                    # time by not rescaling the whole image, only the part within the view region.
                    zoom = self._calculate_zoom(
                        img_pixel_size[0],
                        pixel_size)  # img_pixel_size[0]/pixel_size
                    #logger.debug("Image pixel size (%g deg) does not match desired size (%g deg). Zooming image by a factor %g" % (img_pixel_size[0], pixel_size, zoom))
                    if zoom in self._zoom_cache:
                        img = self._zoom_cache[zoom]
                    else:
                        img = interpolation.zoom(self.img, zoom)
                        self._zoom_cache[zoom] = img

                j_start = numpy.round(img_relative_left *
                                      img.shape[1]).astype(int)
                delta_j = numpy.round(img_relative_width *
                                      img.shape[1]).astype(int)
                i_start = img.shape[0] - numpy.round(
                    img_relative_top * img.shape[0]).astype(int)
                delta_i = numpy.round(img_relative_height *
                                      img.shape[0]).astype(int)

                l_start = numpy.round(view_relative_left *
                                      size_in_pixels[1]).astype(int)
                delta_l = numpy.round(view_relative_width *
                                      size_in_pixels[1]).astype(int)
                k_start = size_in_pixels[0] - numpy.round(
                    view_relative_top * size_in_pixels[0]).astype(int)
                delta_k = numpy.round(view_relative_height *
                                      size_in_pixels[0]).astype(int)

                # unfortunatelly the above code can give inconsistent results even if the inputs are correct due to rounding errors
                # therefore:

                if abs(delta_j - delta_l) == 1:
                    delta_j = min(delta_j, delta_l)
                    delta_l = min(delta_j, delta_l)

                if abs(delta_i - delta_k) == 1:
                    delta_i = min(delta_i, delta_k)
                    delta_k = min(delta_i, delta_k)

                assert delta_j == delta_l, "delta_j = %g, delta_l = %g" % (
                    delta_j, delta_l)
                assert delta_i == delta_k, "delta_i = %g, delta_k = %g" % (
                    delta_i, delta_k)

                i_stop = i_start + delta_i
                j_stop = j_start + delta_j
                k_stop = k_start + delta_k
                l_stop = l_start + delta_l
                ##logger.debug("i_start = %d, i_stop = %d, j_start = %d, j_stop = %d" % (i_start, i_stop, j_start, j_stop))
                ##logger.debug("k_start = %d, k_stop = %d, l_start = %d, l_stop = %d" % (k_start, k_stop, l_start, l_stop))

                try:
                    self.region_cache[region] = ((k_start, k_start + delta_k,
                                                  l_start, l_start + delta_l),
                                                 (i_start, i_start + delta_i,
                                                  j_start, j_start + delta_j))
                    view[k_start:k_start + delta_k, l_start:l_start +
                         delta_l] = img[i_start:i_start + delta_i,
                                        j_start:j_start + delta_j]
                except ValueError:
                    logger.error(
                        "i_start = %d, i_stop = %d, j_start = %d, j_stop = %d"
                        % (i_start, i_stop, j_start, j_stop))
                    logger.error(
                        "k_start = %d, k_stop = %d, l_start = %d, l_stop = %d"
                        % (k_start, k_stop, l_start, l_stop))
                    logger.error("img.shape = %s, view.shape = %s" %
                                 (img.shape, view.shape))
                    logger.error(
                        "img[i_start:i_stop, j_start:j_stop].shape = %s" %
                        str(img[i_start:i_stop, j_start:j_stop].shape))
                    logger.error(
                        "view[k_start:k_stop, l_start:l_stop].shape = %s" %
                        str(view[k_start:k_stop, l_start:l_stop].shape))
                    raise
            else:
                try:
                    ((sx_min, sx_max, sy_min, sy_max),
                     (tx_min, tx_max, ty_min,
                      ty_max)) = self.region_cache[region]
                    view[sx_min:sx_max,
                         sy_min:sy_max] = self.img[tx_min:tx_max,
                                                   ty_min:ty_max]
                except ValueError:
                    logger.error(
                        "i_start = %d, i_stop = %d, j_start = %d, j_stop = %d"
                        % (i_start, i_stop, j_start, j_stop))
                    logger.error(
                        "k_start = %d, k_stop = %d, l_start = %d, l_stop = %d"
                        % (k_start, k_stop, l_start, l_stop))
                    logger.error("img.shape = %s, view.shape = %s" %
                                 (img.shape, view.shape))
                    logger.error(
                        "img[i_start:i_stop, j_start:j_stop].shape = %s" %
                        str(img[i_start:i_stop, j_start:j_stop].shape))
                    logger.error(
                        "view[k_start:k_stop, l_start:l_stop].shape = %s" %
                        str(view[k_start:k_stop, l_start:l_stop].shape))
                    raise
        return view

    def update(self):
        """
        Sets the current frame to the next frame in the sequence.
        """
        try:
            self.img, self.variables = self._frames.next()
        except StopIteration:
            self.visible = False
        else:
            assert self.img.min() >= 0 or self.img.min(
            ) == TRANSPARENT, "frame minimum is less than zero: %g" % self.img.min(
            )
            assert self.img.max(
            ) <= 2 * self.background_luminance, "frame maximum (%g) is greater than the maximum luminance (%g)" % (
                self.img.max(), 2 * self.background_luminance)
        self._zoom_cache = {}

    def reset(self):
        """
        Reset to the first frame in the sequence.
        """
        self.visible = True
        self._frames = self.frames()
        self.update()

    def next_frame(self):
        """For creating movies"""
        self.update()
        return [self.img]
Ejemplo n.º 17
0
class DriftingSinusoidalGratingCenterSurroundStimulus(
        TopographicaBasedVisualStimulus):
    """
    A standard stimulus to probe orientation specific surround modulation:
    A drifting grating in center surrounded by a drifting grating in the surround.
    Orientations of both center and surround gratings can be varied independently.

    Notes
    -----
    max_luminance is interpreted as scale and size_x/2 as the bounding box radius.
    """

    center_orientation = SNumber(rad,
                                 period=pi,
                                 bounds=[0, pi],
                                 doc="Center grating orientation")
    surround_orientation = SNumber(rad,
                                   period=pi,
                                   bounds=[0, pi],
                                   doc="Surround grating orientation")
    spatial_frequency = SNumber(
        cpd,
        doc="Spatial frequency of the grating (same for center and surround)")
    temporal_frequency = SNumber(
        Hz,
        doc="Temporal frequency of the grating (same for center and surround)")
    gap = SNumber(
        degrees,
        doc=
        "The gap between center and surround grating - in degrees of visual field"
    )
    center_radius = SNumber(
        degrees,
        doc=
        "The (outside) radius of the center grating disk - in degrees of visual field"
    )
    surround_radius = SNumber(
        degrees,
        doc=
        "The (outside) radius of the surround grating disk - in degrees of visual field"
    )
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")

    def frames(self):
        self.current_phase = 0
        while True:
            center = imagen.SineGrating(
                mask_shape=imagen.pattern.Disk(smoothing=0.0,
                                               size=self.center_radius * 2),
                orientation=self.center_orientation,
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(radius=self.size_x / 2),
                offset=self.background_luminance * (100.0 - self.contrast) /
                100.0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)()
            r = (self.center_radius + self.surround_radius + self.gap) / 2
            t = (self.surround_radius - self.surround_radius - self.gap) / 2
            surround = imagen.SineGrating(
                mask_shape=imagen.pattern.Ring(thickness=t,
                                               smoothing=0,
                                               size=r * 2),
                orientation=self.surround_orientation,
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(radius=self.size_x / 2),
                offset=self.background_luminance * (100.0 - self.contrast) /
                100.0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)()
            yield (numpy.add.reduce([center, surround]), [self.current_phase])
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
Ejemplo n.º 18
0
class FlashedInterruptedCorner(TopographicaBasedVisualStimulus):
    """
    A flashed bar.

    This stimulus corresponds to flashing a bar of specific *orientation*,
    *width* and *length* at pre-specified position for *flash_duration* of milliseconds. 
    For the remaining time, until the *duration* of the stimulus, constant *background_luminance* 
    is displayed.
    """
    relative_luminance = SNumber(
        dimensionless,
        bounds=[0, 1.0],
        doc=
        "The scale of the stimulus. 0 is dark, 1.0 is double the background luminance"
    )
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Orientation of the corner")
    width = SNumber(degrees, doc="width of lines forming the corner")
    length = SNumber(Hz, doc="length of the corner if it were colinear")
    flash_duration = SNumber(ms,
                             doc="The duration of the corner presentation.")
    x = SNumber(
        degrees,
        doc=
        "The x location of the center of the bar (where the gap will appear).")
    y = SNumber(
        degrees,
        doc=
        "The y location of the center of the bar (where the gap will appear).")
    left_angle = SNumber(rad,
                         period=pi,
                         bounds=[0, pi],
                         doc="orientation of the left arm")
    right_angle = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="orientation of the right arm")
    gap_length = SNumber(Hz, doc="Length of the gap in the center of the bar")

    def frames(self):
        num_frames = 0

        while True:
            length = self.length / 2 - self.gap_length / 2.0
            shift = length / 2.0 + self.gap_length / 2.0

            r1 = imagen.Rectangle(
                x=shift * numpy.cos(self.right_angle),
                y=shift * numpy.sin(right_angle),
                offset=self.background_luminance,
                scale=2 * self.background_luminance *
                (self.relative_luminance - 0.5),
                orientation=numpy.pi / 2 + self.right_angle,
                smoothing=0,
                aspect_ratio=self.width / length,
                size=length,
                bounds=BoundingBox(radius=self.size_x / 2),
            )

            r2 = imagen.Rectangle(
                x=shift * numpy.cos(self.left_angle),
                y=shift * numpy.sin(left_angle),
                offset=self.background_luminance,
                scale=2 * self.background_luminance *
                (self.relative_luminance - 0.5),
                orientation=numpy.pi / 2 + self.left_angle,
                smoothing=0,
                aspect_ratio=self.width / length,
                size=length,
                bounds=BoundingBox(radius=self.size_x / 2),
            )

            r = imagen.Composite(generators=[r1, r2],
                                 x=self.x,
                                 y=self.y,
                                 bounds=BoundingBox(radius=self.size_x / 2),
                                 orientation=self.orientation,
                                 xdensity=self.density,
                                 ydensity=self.density)

            b = imagen.Constant(scale=self.background_luminance,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()

            num_frames += 1
            if (num_frames - 1) * self.frame_duration < self.flash_duration:
                yield (r(), [1])
            else:
                yield (b, [0])
Ejemplo n.º 19
0
class VonDerHeydtIllusoryBar(TopographicaBasedVisualStimulus):
    """
    An illusory bar from Von Der Heydt et al. 1989.

    Von Der Heydt, R., & Peterhans, E. (1989). Mechanisms of contour perception in monkey visual cortex. I. Lines of pattern discontinuity. Journal of Neuroscience, 9(5), 1731–1748. Retrieved from https://www.jneurosci.org/content/jneuro/9/5/1731.full.pdf
    """
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    background_bar_width = SNumber(degrees, doc="Width of the background bar")
    occlusion_bar_width = SNumber(degrees, doc="Width of the occlusion bar")
    bar_width = SNumber(degrees, doc="Width of the bar")
    length = SNumber(Hz, doc="Length of the background bar")
    flash_duration = SNumber(ms, doc="The duration of the bar presentation.")
    x = SNumber(
        degrees,
        doc=
        "The x location of the center of the bar (where the gap will appear).")
    y = SNumber(
        degrees,
        doc=
        "The y location of the center of the bar (where the gap will appear).")

    def frames(self):
        num_frames = 0
        while True:

            d1 = imagen.RawRectangle(
                offset=0,
                scale=2 * self.background_luminance,
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x - (self.occlusion_bar_width / 2) -
                (self.length - self.occlusion_bar_width) / 4,
                y=self.y,
                orientation=self.orientation,
                size=self.background_bar_width,
                aspect_ratio=(self.length - self.occlusion_bar_width) / 2 /
                self.background_bar_width)()

            d2 = imagen.RawRectangle(
                offset=0,
                scale=2 * self.background_luminance,
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x + (self.occlusion_bar_width / 2) +
                (self.length - self.occlusion_bar_width) / 4,
                y=self.y,
                orientation=self.orientation,
                size=self.background_bar_width,
                aspect_ratio=(self.length - self.occlusion_bar_width) / 2 /
                self.background_bar_width)()

            b = imagen.Constant(scale=0,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()

            num_frames += 1
            if (num_frames - 1) * self.frame_duration < self.flash_duration:
                yield (numpy.add(d1, d2), [1])
            else:
                yield (b, [0])
Ejemplo n.º 20
0
class FlashedInterruptedBar(TopographicaBasedVisualStimulus):
    """
    A flashed bar.

    This stimulus corresponds to flashing a bar of specific *orientation*,
    *width* and *length* at pre-specified position for *flash_duration* of milliseconds. 
    For the remaining time, until the *duration* of the stimulus, constant *background_luminance* 
    is displayed.
    """
    relative_luminance = SNumber(
        dimensionless,
        bounds=[0, 1.0],
        doc=
        "The scale of the stimulus. 0 is dark, 1.0 is double the background luminance."
    )
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation.")
    disalignment = SNumber(
        rad,
        period=pi,
        bounds=[-pi / 2, pi / 2],
        doc=
        "The orientation by which the flanking bars are rotated away from the principal orientation axis."
    )
    width = SNumber(cpd, doc="Width of the bar")
    length = SNumber(Hz, doc="Length of the bar`")
    flash_duration = SNumber(ms, doc="The duration of the bar presentation.")
    x = SNumber(
        degrees,
        doc=
        "The x location of the center of the bar (where the gap will appear).")
    y = SNumber(
        degrees,
        doc=
        "The y location of the center of the bar (where the gap will appear).")
    gap_length = SNumber(Hz, doc="Length of the gap in the center of the bar")

    def frames(self):
        num_frames = 0
        while True:

            z = self.gap_length / 4.0 + self.length / 4.0

            d1 = imagen.RawRectangle(
                offset=self.background_luminance,
                scale=2 * self.background_luminance *
                (self.relative_luminance - 0.5),
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x + numpy.cos(self.orientation) * (z),
                y=self.y + numpy.sin(self.orientation) * (z),
                orientation=self.orientation + self.disalignment,
                size=self.width,
                aspect_ratio=(self.length - self.gap_length) / 2 /
                self.width)()

            d2 = imagen.RawRectangle(
                offset=self.background_luminance,
                scale=2 * self.background_luminance *
                (self.relative_luminance - 0.5),
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x + numpy.cos(self.orientation) * (-z),
                y=self.y + numpy.sin(self.orientation) * (-z),
                orientation=self.orientation + self.disalignment,
                size=self.width,
                aspect_ratio=(self.length - self.gap_length) / 2 /
                self.width)()

            b = imagen.Constant(scale=self.background_luminance,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()

            num_frames += 1
            if (num_frames - 1) * self.frame_duration < self.flash_duration:
                if self.relative_luminance > 0.5:
                    yield (numpy.maximum(d1, d2), [1])
                else:
                    yield (numpy.minimum(d1, d2), [1])
            else:
                yield (b, [0])
Ejemplo n.º 21
0
class DriftingSinusoidalGratingRing(TopographicaBasedVisualStimulus):
    """
    A standard stimulus to probe orientation specific surround modulation:
    A drifting grating in center surrounded by a drifting grating in the surround.
    Orientations of both center and surround gratings can be varied independently.

    Notes
    -----
    max_luminance is interpreted as scale and size_x/2 as the bounding box radius.
    """

    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Center grating orientation")
    spatial_frequency = SNumber(cpd, doc="Spatial frequency of the grating ")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of the grating ")
    outer_appareture_radius = SNumber(
        degrees,
        doc=
        "The outside radius of the grating ring - in degrees of visual field")
    inner_appareture_radius = SNumber(
        degrees,
        doc=
        "The inside radius of the  grating ring - in degrees of visual field")
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")

    def frames(self):
        self.current_phase = 0
        while True:
            r = (self.inner_appareture_radius +
                 self.outer_appareture_radius) / 2.0
            t = (self.outer_appareture_radius -
                 self.inner_appareture_radius) / 2.0
            ring = imagen.SineGrating(
                mask_shape=imagen.Ring(thickness=t * 2.0,
                                       smoothing=0.0,
                                       size=r * 2.0),
                orientation=self.orientation,
                frequency=self.spatial_frequency,
                phase=self.current_phase,
                bounds=BoundingBox(radius=self.size_x / 2.0),
                offset=0,
                scale=2 * self.background_luminance * self.contrast / 100.0,
                xdensity=self.density,
                ydensity=self.density)()

            bg = imagen.Constant(bounds=BoundingBox(radius=self.size_x / 2.0),
                                 scale=self.background_luminance,
                                 xdensity=self.density,
                                 ydensity=self.density)()

            correction = imagen.Ring(smoothing=0.0,
                                     thickness=t * 2.0,
                                     size=r * 2.0,
                                     scale=-self.background_luminance,
                                     bounds=BoundingBox(radius=self.size_x /
                                                        2.0),
                                     xdensity=self.density,
                                     ydensity=self.density)()

            yield (numpy.add.reduce([ring, bg,
                                     correction]), [self.current_phase])
            self.current_phase += 2 * pi * (self.frame_duration /
                                            1000.0) * self.temporal_frequency
Ejemplo n.º 22
0
class FlashingSquares(TopographicaBasedVisualStimulus):
    """
    A pair of displaced flashing squares. 

    A pair of squares separated by a constant distance of dimensions dictated by provided *spatial_frequency* parameter
    and flashing at frequency provided by the *temporal_frequency* parameter. 
    """
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Orientation of the square axis")
    spatial_frequency = SNumber(
        cpd,
        doc="Spatial frequency created by the squares and the gap between them"
    )
    separation = SNumber(degrees, doc="The separation between the two squares")
    temporal_frequency = SNumber(Hz, doc="Temporal frequency of the flashing")
    contrast = SNumber(dimensionless,
                       bounds=[0, 100.0],
                       doc="Contrast of the stimulus")
    separated = SNumber(
        dimensionless,
        doc=
        "Boolean string to decide whether the separation is specified or not")

    def frames(self):
        # the size length of square edge is given by half the period
        size = 1. / (2 * self.spatial_frequency)
        # if a separation size is provided we use it, otherwise we use the same as size
        if self.separated:
            halfseparation = self.separation / 2.
        else:
            halfseparation = size
        # if the separation is less than the size of a square, the two squares will overlap and the luminance will be too much
        if halfseparation < size / 2.:
            halfseparation = size / 2.
        # flashing squares with a temporal frequency of 6Hz are happening every 1000/6=167ms
        time = self.duration / self.frame_duration
        stim_period = time / self.temporal_frequency
        t = 0
        t0 = 0
        # total time of the stimulus
        while t <= time:
            # frequency tick
            if (t - t0) >= stim_period:
                t0 = t
            # Squares presence on screen is half of the period.
            # Since the two patterns will be added together,
            # the offset level is half it should be, to sum into the required level,
            # and the scale level is twice as much, in order to overcome the presence of the other pattern
            if t <= t0 + (stim_period / 2):
                a = imagen.RawRectangle(
                    x=-halfseparation,
                    y=0,
                    orientation=self.orientation,
                    bounds=BoundingBox(radius=self.size_x / 2),
                    offset=0.5 * self.background_luminance *
                    (100.0 - self.contrast) / 100.0,
                    scale=2 * self.background_luminance * self.contrast /
                    100.0,
                    xdensity=self.density,
                    ydensity=self.density,
                    size=size)()
                b = imagen.RawRectangle(
                    x=halfseparation,
                    y=0,
                    orientation=self.orientation,
                    bounds=BoundingBox(radius=self.size_x / 2),
                    offset=0.5 * self.background_luminance *
                    (100.0 - self.contrast) / 100.0,
                    scale=2 * self.background_luminance * self.contrast /
                    100.0,
                    xdensity=self.density,
                    ydensity=self.density,
                    size=size)()
                yield (numpy.add(a, b), [t])
            else:
                yield (imagen.Constant(scale=self.background_luminance *
                                       (100.0 - self.contrast) / 100.0,
                                       bounds=BoundingBox(radius=self.size_x /
                                                          2),
                                       xdensity=self.density,
                                       ydensity=self.density)(), [t])
            # time
            t += 1
Ejemplo n.º 23
0
class FlashedInterruptedBarWithOrthogonalDisruptor(
        TopographicaBasedVisualStimulus):
    """
    A flashed bar.

    This stimulus corresponds to flashing a bar of specific *orientation*,
    *width* and *length* at pre-specified position for *flash_duration* of milliseconds. 
    For the remaining time, until the *duration* of the stimulus, constant *background_luminance* 
    is displayed.
    """
    relative_luminance = SNumber(
        dimensionless,
        bounds=[0, 1.0],
        doc=
        "The scale of the stimulus. 0 is dark, 1.0 is double the background luminance"
    )
    orientation = SNumber(rad,
                          period=pi,
                          bounds=[0, pi],
                          doc="Grating orientation")
    width = SNumber(cpd, doc="Width of the bar")
    length = SNumber(Hz, doc="Length of the bar`")
    flash_duration = SNumber(ms, doc="The duration of the bar presentation.")
    x = SNumber(
        degrees,
        doc=
        "The x location of the center of the bar (where the gap will appear).")
    y = SNumber(
        degrees,
        doc=
        "The y location of the center of the bar (where the gap will appear).")
    gap_length = SNumber(Hz, doc="Length of the gap in the center of the bar")

    def frames(self):
        num_frames = 0
        while True:

            d1 = imagen.RawRectangle(offset=self.background_luminance,
                                     scale=2 * self.background_luminance *
                                     (self.relative_luminance - 0.5),
                                     bounds=BoundingBox(radius=self.size_x /
                                                        2),
                                     xdensity=self.density,
                                     ydensity=self.density,
                                     x=self.x,
                                     y=self.y,
                                     orientation=self.orientation,
                                     size=self.width,
                                     aspect_ratio=self.length / self.width)()

            d2 = imagen.RawRectangle(
                offset=1,
                scale=-1,
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x,
                y=self.y,
                orientation=self.orientation,
                size=self.width,
                aspect_ratio=self.gap_length / self.width)()

            d3 = imagen.RawRectangle(
                offset=0,
                scale=self.background_luminance,
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x,
                y=self.y,
                orientation=self.orientation,
                size=self.width,
                aspect_ratio=self.gap_length / self.width)()

            d4 = imagen.RawRectangle(
                offset=self.background_luminance,
                scale=2 * self.background_luminance *
                (self.relative_luminance - 0.5),
                bounds=BoundingBox(radius=self.size_x / 2),
                xdensity=self.density,
                ydensity=self.density,
                x=self.x,
                y=self.y,
                orientation=self.orientation + numpy.pi / 2,
                size=self.width,
                aspect_ratio=0.8 / self.width)()

            b = imagen.Constant(scale=self.background_luminance,
                                bounds=BoundingBox(radius=self.size_x / 2),
                                xdensity=self.density,
                                ydensity=self.density)()

            num_frames += 1
            if (num_frames - 1) * self.frame_duration < self.flash_duration:
                if self.relative_luminance > 0.5:
                    #yield (numpy.maximum(d4,numpy.add(numpy.multiply(d1,d2),d3)),[1])
                    yield (d4, [1])
                else:
                    yield (d4, [1])
                    #yield (numpy.minimum(d4,numpy.add(numpy.multiply(d1,d2),d3)),[1])
            else:
                yield (b, [0])
Ejemplo n.º 24
0
class VictorInformativeSyntheticStimulus(VisualStimulus):
    """
    A stimulus generated using Jonathan Victor's maximum entropy algorithm
    Some statistics of each 2*2 regions of the image are constrained as indicated in the pixel statistics list, and the rest of the statistics are computed in order to maximize the entropy of the image distribution
    """
    pixel_statistics = SNumber(
        dimensionless,
        bounds=[-1, 1],
        doc='The values of pixel correlations statistics')
    correlation_type = SInteger(
        dimensionless,
        bounds=[1, 10],
        doc=
        'Which multipoint correlation statistic to select, ranked in the same order as in Fig2 in Hermundstad, A. M., Briguglio, J. J., Conte, M. M., Victor, J. D., Balasubramanian, V., & Tkacik, G. (2014). Variance predicts salience in central sensory processing. Elife, 3, e03722.'
    )
    seed = SNumber(dimensionless, doc="The seed used for this stimulus")
    spatial_frequency = SNumber(dimensionless,
                                doc="The spatial frequency of the stimulus")

    def frames(self):
        assert len(
            numpy.nonzero(self.pixel_statistics)
        ) == 1, 'One and only one value in the tuple pixel_statistics must be non zero'
        for i, stat in enumerate(self.pixel_statistics):
            assert isinstance(stat, float) or isinstance(
                stat, int), 'Value at index %d is not a number' % i
            assert stat <= 1 and stat >= -1, 'Value at index %d is not within bounds [-1,1]' % i
        fieldsize_x = self.size_x * self.density
        fieldsize_y = self.size_y * self.density
        pixel_spatial_frequency = int(self.spatial_frequency * self.density)
        numpy.random.seed(self.seed)
        im = numpy.zeros((int(fieldsize_x), int(fieldsize_y)))

        for y in range(0, int(fieldsize_y), pixel_spatial_frequency):
            if y + pixel_spatial_frequency < im.shape[1]:
                ylim = y + pixel_spatial_frequency
            else:
                ylim = im.shape[1]

            for x in range(0, int(fieldsize_x), pixel_spatial_frequency):
                if x + pixel_spatial_frequency < im.shape[0]:
                    xlim = x + pixel_spatial_frequency
                else:
                    xlim = im.shape[0]

                if self.correlation_type == 1:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[0]) / 2:
                        im[y:ylim, x:xlim] = 255

                elif self.correlation_type == 2 and y > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[1]) / 2:
                        im[y:ylim, x:xlim] = im[y - 1, x]
                    else:
                        im[y:ylim, x:xlim] = -1 * (im[y - 1, x] - 255)

                elif self.correlation_type == 3 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[2]) / 2:
                        im[y:ylim, x:xlim] = im[y, x - 1]
                    else:
                        im[y:ylim, x:xlim] = -1 * (im[y, x - 1] - 255)

                elif self.correlation_type == 4 and y > 0 and x < fieldsize_x - pixel_spatial_frequency:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[3]) / 2:
                        im[y:ylim, x:xlim] = im[y - 1,
                                                x + pixel_spatial_frequency]
                    else:
                        im[y:ylim, x:xlim] = -1 * (
                            im[y - 1, x + pixel_spatial_frequency] - 255)

                elif self.correlation_type == 5 and y > 0 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[4]) / 2:
                        im[y:ylim, x:xlim] = im[y - 1, x - 1]
                    else:
                        im[y:ylim, x:xlim] = -1 * (im[y - 1, x - 1] - 255)

                elif self.correlation_type == 6 and y > 0 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[5]) / 2:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - 1, x - 1] + im[y, x - 1]) % 510 - 255)
                    else:
                        im[y:ylim,
                           x:xlim] = (im[y - 1, x - 1] + im[y, x - 1]) % 510

                elif self.correlation_type == 7 and y > 0 and x < fieldsize_x - pixel_spatial_frequency:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[6]) / 2:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - 1, x + pixel_spatial_frequency] +
                             im[y - 1, x]) % 510 - 255)
                    else:
                        im[y:ylim,
                           x:xlim] = (im[y - 1, x + pixel_spatial_frequency] +
                                      im[y - 1, x]) % 510

                elif self.correlation_type == 8 and y > 0 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[7]) / 2:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - 1, x] + im[y - 1, x - 1]) % 510 - 255)
                    else:
                        im[y:ylim,
                           x:xlim] = (im[y - 1, x] + im[y - 1, x - 1]) % 510

                elif self.correlation_type == 9 and y > 0 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[8]) / 2:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - 1, x] + im[y, x - 1]) % 510 - 255)
                    else:
                        im[y:ylim,
                           x:xlim] = (im[y - 1, x] + im[y, x - 1]) % 510

                elif self.correlation_type == 10 and y > 0 and x > 0:
                    if numpy.random.rand() < (1 +
                                              self.pixel_statistics[9]) / 2:
                        im[y:ylim, x:xlim] = (im[y - 1, x - 1] + im[y, x - 1] +
                                              im[y - 1, x]) % 510
                    else:
                        im[y:ylim, x:xlim] = -1 * (
                            (im[y - 1, x - 1] + im[y, x - 1] + im[y - 1, x]) %
                            510 - 255)

                else:
                    if numpy.random.rand() < 0.5:
                        im[y:ylim, x:xlim] = 255

        while True:
            yield (im, [0])
Ejemplo n.º 25
0
class PSTextureStimulus(TextureBasedVisualStimulus):
    """
    A stimulus generated using the Portilla-Simoncelli algorithm (see textureLib/textureBasedStimulus.m)
    with statistics matched to the original image according to the Type.
    It is presented for *stimulus_duration* milliseconds. For the remaining time, 
    until the *duration* of the stimulus, constant *background_luminance* 
    is displayed.. 
     
    Types:
        0 - original image
        1 - naturalistic texture image (matched higher order statistics)
        2 - spectrally matched noise (matched marginal statistics only). 

    Notes
    -----
    frames_number - the number of frames for which each image is presented

    ALERT!!!

    Because of optimization issues, the stimulus is not re-generated on every trial.
    We should add new 'empty' paramter to replace trial to force recalculaton of the stimuls.
    """

    stats_type = SNumber(dimensionless,
                         bounds=[0, 3],
                         doc="Type of statistial matching of the stimulus")
    sample = SNumber(dimensionless,
                     doc="Index of the stimulus in its texture family")
    seed = SNumber(dimensionless, doc="The seed used for this stimulus")

    def frames(self):
        fieldsize_x = self.size_x * self.density
        fieldsize_y = self.size_y * self.density
        folder_name = Global.root_directory + "/TextureImagesStimuli"
        #libpath = visual_stimulus.__file__.replace("/visual_stimulus.pyc", "") + "/textureLib" #path to the image processing library
        libpath = __file__.replace(
            "/texture_based.py",
            "") + "/textureLib"  #path to the image processing library
        matlabPyrToolspath = os.path.join(libpath, "textureSynth",
                                          "matlabPyrTools")
        if not os.path.isdir(matlabPyrToolspath):
            raise IOError(
                "matlabPyrTools should be downloaded from https://github.com/LabForComputationalVision/matlabPyrTools and its content should be put in the directory "
                + matlabPyrToolspath)

        octave.addpath(libpath)
        im = octave.textureBasedStimulus(self.texture_path, self.stats_type,
                                         self.seed, fieldsize_x, fieldsize_y,
                                         libpath)
        scale = 2. * self.background_luminance / (numpy.max(im) -
                                                  numpy.min(im))
        im = (im - numpy.min(im)) * scale
        im = im.astype(numpy.uint8)

        if not os.path.exists(folder_name):
            os.mkdir(folder_name)

        IM = Image.fromarray(im)
        IM.save(folder_name + "/" + self.texture + "sample" +
                str(self.sample) + "type" + str(self.stats_type) + '.pgm')
        assert (im.shape == (fieldsize_x, fieldsize_y)
                ), "Image dimensions do not correspond to visual field size"

        while True:
            yield (im, [0])
Ejemplo n.º 26
0
class BaseStimulus(MozaikParametrized):
    """
    The abstract stimulus class. It defines the parameters common to all stimuli and
    the list of function each stimulus has to provide.
    """
    frame_duration = SNumber(qt.ms, doc="The duration of single frame")
    duration = SNumber(qt.ms, doc="The duration of stimulus")
    trial = SInteger(doc="The trial of the stimulus")
    direct_stimulation_name = SString(
        default="None", doc="The name of the artifical stimulation protocol")

    def __init__(self, **params):
        MozaikParametrized.__init__(self, **params)
        self.input = None
        self._frames = self.frames()
        self.n_frames = numpy.inf  # possibly very dangerous. Don't do 'for i in range(stim.n_frames)'!

    def __eq__(self, other):
        """
        Are the name and all parameters of two stimuli are equivallent?
        """
        return self.equalParams(other) and (self.__class__ == other.__class__)

    def number_of_parameters(self):
        """
        Returns number of parameters of the stimulus.
        """
        return len(self.get_param_values())

    def frames(self):
        """
        Return a generator which yields the frames of the stimulus in sequence.
        Each frame is returned as a tuple `(frame, variables)` where
        `frame` is a numpy array containing the stimulus at the given time and
        `variables` is a list of variable values (e.g., orientation) associated
        with that frame.

        See topographica_based for examples.
        """
        raise NotImplementedError("Must be implemented by child class.")

    def update(self):
        """
        Sets the current frame to the next frame in the sequence.
        """
        raise NotImplementedError("Must be implemented by child class.")

    def reset(self):
        """
        Reset to the first frame in the sequence.
        """
        raise NotImplementedError("Must be implemented by child class.")

    def export(self, path=None):
        """
        Save the frames to disk. Returns a list of paths to the individual
        frames.

        path - the directory in which the individual frames will be saved. If
               path is None, then a temporary directory is created.
        """
        raise NotImplementedError("Must be implemented by child class.")