Source code for featuremapper.command

"""
User-level measurement commands, typically for measuring or generating
SheetViews.

The file defines base classes for defining measurements and a large
set of predefined measurement commands.

Some of the commands are ordinary Python functions, but the rest are
ParameterizedFunctions, which act like Python functions but support
Parameters with defaults, bounds, inheritance, etc.  These commands
are usually grouped together using inheritance so that they share a
set of parameters and some code, and only the bits that are specific
to that particular plot or analysis appear below.  See the
superclasses for the rest of the parameters and code.
"""

import copy
import ImageDraw
import Image as PILImage

import numpy as np

import param
from param import ParameterizedFunction, ParamOverrides

from holoviews import HoloMap, Layout
from holoviews.interface.collector import AttrDict, Collector
from holoviews.element.raster import Image

import imagen
from imagen import random
from imagen import SineGrating, Gaussian, RawRectangle, Disk, Composite, \
    OrientationContrast
from imagen.deprecated import GaussiansCorner

from featuremapper import FeatureResponses, FeatureMaps, FeatureCurves, \
    ReverseCorrelation
import features as f
from metaparams import *
from distribution import DSF_MaxValue, DistributionStatisticFn, \
    DSF_WeightedAverage, DSF_BimodalPeaks


class PatternPresentingCommand(ParameterizedFunction):
    """Parameterized command for presenting input patterns"""

    durations = param.Parameter(default=[1.0], doc="""Times after presentation
        begins at which to record a measurement.""")

    measurement_prefix = param.String(default='', doc="""
        Optional prefix to add to the name under which results are stored as
        part of a measurement response.""")

    __abstract = True



class MeasureResponseCommand(PatternPresentingCommand):
    """Parameterized command for presenting input patterns and measuring
    responses."""

    inputs = param.List(default=[], doc="""Name of input supplied to the
        metadata_fns to filter out desired input.""")

    metafeature_fns = param.HookList(default=[contrast2scale], doc="""
        Metafeature_fns is a hooklist, which accepts any function, which applies
        coordinated changes to a set of inputs based on some parameter or feature
        value. Can be used to present different patterns to different inputs or
        to control complex features like contrast.""")

    offset = param.Number(default=0.0, softbounds=(-1.0, 1.0), doc="""
        Additive offset to input pattern.""")

    outputs = param.List(default=[], doc="""Name of output sources supplied
        to metadata_fns to filter out desired output.""")

    pattern_generator = param.Callable(default=None, instantiate=True, doc="""
        Callable object that will generate input patterns coordinated
        using a list of meta parameters.""")

    pattern_response_fn = param.Callable(default=None, instantiate=False, doc="""
        Callable object that will present a parameter-controlled pattern to a
        set of Sheets.  Needs to be supplied by a subclass or in the call.
        The attributes duration and apply_output_fns (if non-None) will
        be set on this object, and it should respect those if possible.""")

    preference_fn = param.ClassSelector(DistributionStatisticFn,
                                        default=DSF_MaxValue(), doc="""
        Function that will be used to analyze the distributions of unit
        responses.""")

    preference_lookup_fn = param.Callable(default=None, doc="""
        Callable object that will look up a preferred feature values.""",
                                          instantiate=True)

    scale = param.Number(default=1.0, softbounds=(0.0, 2.0), doc="""
        Multiplicative strength of input pattern.""")

    static_parameters = param.List(default=["scale", "offset"], doc="""
        List of names of parameters of this class to pass to the
        pattern_presenter as static parameters, i.e. values that will be fixed
        to a single value during measurement.""", class_=str)

    subplot = param.String(default='', doc="""
        Name of map to register as a subplot, if any.""")

    __abstract = True


    def __call__(self, **params):
        """Measure the response to the specified pattern and store the data
        in each sheet."""
        p = ParamOverrides(self, params, allow_extra_keywords=True)
        self._set_presenter_overrides(p)
        static_params = dict([(s, p[s]) for s in p.static_parameters])

        results = FeatureMaps(self._feature_list(p), durations=p.durations,
                              inputs=p.inputs,
                              metafeature_fns=p.metafeature_fns,
                              measurement_prefix=p.measurement_prefix,
                              outputs=p.outputs, static_features=static_params,
                              pattern_generator=p.pattern_generator,
                              pattern_response_fn=p.pattern_response_fn)

        self._restore_presenter_defaults()

        return results


    def _feature_list(self, p):
        """Return the list of features to vary; must be implemented by each
        subclass."""
        raise NotImplementedError


    def _set_presenter_overrides(self, p):
        """
        Overrides parameters of the pattern_response_fn and
        pattern_coordinator, using extra_keywords passed into the
        MeasurementResponseCommand and saves the default parameters to restore
        after measurement is complete.
        """
        self._pr_fn_params = p.pattern_response_fn.get_param_values()
        for override, value in p.extra_keywords().items():
            if override in p.pattern_response_fn.params():
                p.pattern_response_fn.set_param(override, value)


    def _restore_presenter_defaults(self):
        """
        Restores the default pattern_response_fn parameters after the
        measurement.
        """
        params = self.pattern_response_fn.params()
        for key, value in self._pr_fn_params:
            if not params[key].constant:
                self.pattern_response_fn.set_param(key, value)



class SinusoidalMeasureResponseCommand(MeasureResponseCommand):
    """
    Parameterized command for presenting sine gratings and measuring
    responses.
    """

    pattern_generator = param.Callable(default=SineGrating(), doc="""
        Pattern to be presented on the inputs.""", instantiate=True)

    frequencies = param.List(class_=float, default=[2.4], doc="""
        Sine grating frequencies to test.""")

    num_phase = param.Integer(default=18, bounds=(1, None), softbounds=(1, 48),
                              doc="Number of phases to test.")

    num_orientation = param.Integer(default=4, bounds=(1, None),
                                    softbounds=(1, 24),
                                    doc="Number of orientations to test.")

    scale = param.Number(default=0.3)

    preference_fn = param.ClassSelector(DistributionStatisticFn,
                                        default=DSF_WeightedAverage(), doc="""
        Function that will be used to analyze the distributions of unit
        responses.""")

    __abstract = True



class PositionMeasurementCommand(MeasureResponseCommand):
    """
    Parameterized command for measuring topographic position.
    """

    divisions = param.Integer(default=7, bounds=(1, None), doc="""
        The number of different positions to measure in X and in Y.""")

    x_range = param.NumericTuple((-0.5, 0.5), doc="""
        The range of X values to test.""")

    y_range = param.NumericTuple((-0.5, 0.5), doc="""
        The range of Y values to test.""")

    size = param.Number(default=0.5, bounds=(0, None), doc="""
        The size of the pattern to present.""")

    pattern_generator = param.Callable(
        default=Gaussian(aspect_ratio=1.0), doc="""
        Callable object that will present a parameter-controlled
        pattern to a set of Sheets.  For measuring position, the
        pattern_presenter should be spatially localized, yet also able
        to activate the appropriate neurons reliably.""")

    static_parameters = param.List(default=["scale", "offset", "size"])

    __abstract = True



class SingleInputResponseCommand(MeasureResponseCommand):
    """
    A callable Parameterized command for measuring the response to
    input on a specified Sheet.

    Note that at present the input is actually presented to all input
    sheets; the specified Sheet is simply used to determine various
    parameters.  In the future, it may be modified to draw the pattern
    on one input sheet only.
    """

    scale = param.Number(default=30.0)

    offset = param.Number(default=0.5)

    pattern_generator = param.Callable(default=RawRectangle(size=0.1,
                                                            aspect_ratio=1.0))

    static_parameters = param.List(default=["scale", "offset", "size"])

    __abstract = True



class FeatureCurveCommand(SinusoidalMeasureResponseCommand):
    """A callable Parameterized command for measuring tuning curves."""

    contrasts = param.List(default=[30, 60, 80, 90])

    num_orientation = param.Integer(default=12)

    # Make constant in subclasses?
    x_axis = param.String(default='orientation', doc="""
        Parameter to use for the x axis of tuning curves.""")

    static_parameters = param.List(default=[])

    __abstract = True


    def __call__(self, **params):
        """Measure the response to the specified pattern and store the data
        in each sheet."""
        p = ParamOverrides(self, params, allow_extra_keywords=True)
        self._set_presenter_overrides(p)
        results = self._compute_curves(p)
        self._restore_presenter_defaults()
        return results


    def _compute_curves(self, p):
        """
        Compute a set of curves for the specified sheet, using the
        specified val_format to print a label for each value of a
        curve_parameter.
        """
        static_params = dict([(s, p[s]) for s in p.static_parameters])

        return FeatureCurves(self._feature_list(p), durations=p.durations,
                             inputs=p.inputs, measurement_prefix=p.measurement_prefix,
                             metafeature_fns=p.metafeature_fns, outputs=p.outputs,
                             pattern_generator=p.pattern_generator,
                             pattern_response_fn=p.pattern_response_fn,
                             static_features=static_params, x_axis=p.x_axis)



    def _feature_list(self, p):
        return [f.Orientation(steps=p.num_orientation),
                f.Phase(steps=p.num_phase),
                f.Frequency(values=p.frequencies),
                f.Contrast(values=p.contrasts, preference_fn=None)]



class UnitCurveCommand(FeatureCurveCommand):
    """
    Measures tuning curve(s) of particular unit(s).
    """

    pattern_generator = param.Callable(
        default=SineGrating(mask_shape=Disk(smoothing=0.0, size=1.0)))

    metafeature_fns = param.HookList(
        default=[contrast2scale.instance(contrast_parameter='weber_contrast')])

    size = param.Number(default=0.5, bounds=(0, None), doc="""
        The size of the pattern to present.""")

    coords = param.List(default=[(0, 0)], doc="""
        List of coordinates of units to measure.""")

    __abstract = True


    def _populate_grid(self, results):
        trees = []
        for coord, viewgroup in results.items():
            for path, stack in viewgroup.data.items():
                grid_container = Layout()
                coord_map = stack.add_dimension(f.Y, 0, coord[1])
                coord_map = coord_map.add_dimension(f.X, 0, coord[0])
                grid_container.set_path(path, coord_map)
                trees.append(grid_container)
        return Layout.merge(trees)



[docs]class measure_response(FeatureResponses): input_patterns = param.Dict(default={}, doc=""" Assigns patterns to different inputs overriding the pattern_generator parameter. If all inputs have not been assigned a pattern, remaining inputs will be presented a blank pattern.""") pattern_generator = param.Callable(default=Gaussian(), instantiate=True, doc=""" Callable object that will generate input patterns coordinated using a list of meta parameters.""") def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._apply_cmd_overrides(p) self.metadata = AttrDict(p.metadata) for fn in p.metadata_fns: self.metadata.update(fn(p.inputs, p.outputs)) output_names = self.metadata.outputs.keys() input_names = self.metadata.inputs.keys() inputs = dict.fromkeys(input_names) if p.input_patterns: for k, ip in p.input_patterns.items(): inputs[k] = ip for name in [k for k, ip in inputs.items() if ip is None]: self.warning("No pattern specified for input %s, defaulting" "to blank Constant pattern." % name) inputs[name] = imagen.Constant(scale=0) else: for k in inputs.keys(): inputs[k] = copy.deepcopy(p.pattern_generator) for f in p.pre_presentation_hooks: f() responses = p.pattern_response_fn(inputs, output_names, durations=p.durations) for f in p.post_presentation_hooks: f() label = inputs.values()[0].__class__.__name__ results = self._collate_results(responses, label) if p.measurement_storage_hook: p.measurement_storage_hook(results) return results def _collate_results(self, responses, label): time = self.metadata.timestamp dims = [f.Time, f.Duration] response_label = label + ' Response' results = Layout() for label, response in responses.items(): name, duration = label path = (response_label.replace(' ', ''), name) label = ' '.join([name, response_label]) metadata = self.metadata['outputs'][name] if path not in results: vmap = HoloMap(key_dimensions=dims) vmap.metadata = AttrDict(**metadata) results.set_path(path, vmap) im = Image(response, metadata['bounds'], label=label, group='Activity') im.metadata=AttrDict(timestamp=time) results[path][(time, duration)] = im return results def _apply_cmd_overrides(self, p): super(measure_response, self)._apply_cmd_overrides(p) for override, value in p.extra_keywords().items(): if override in p.pattern_response_fn.params(): p.pattern_response_fn.set_param(override, value) else: self.warning('%s not a parameter of measure_response ' 'or the pattern_response_fn.' % override)
[docs]class measure_rfs(SingleInputResponseCommand): """ Map receptive fields by reverse correlation. Presents a large collection of input patterns, typically white noise, keeping track of which units in the specified input_sheet were active when each unit in other Sheets in the simulation was active. This data can then be used to plot receptive fields for each unit. Note that the results are true receptive fields, not the connection fields usually presented in lieu of receptive fields, because they take all circuitry in between the input and the target unit into account. Note also that it is crucial to set the scale parameter properly when using units with a hard activation threshold (as opposed to a smooth sigmoid), because the input pattern used here may not be a very effective way to drive the unit to activate. The value should be set high enough that the target units activate at least some of the time there is a pattern on the input. """ static_parameters = param.List(default=["scale", "offset"]) pattern_generator = param.Callable(default=random.UniformRandom(name='UniformNoise'), doc="""Presented pattern for reverse correlation, usually white noise.""") presentations = param.Number(default=100, doc=""" Number of presentations to run the reverse correlation for.""") roi = param.NumericTuple(default=(0, 0, 0, 0), doc=""" If non-zero ROI bounds is specified only the RFs in that subregion are recorded.""") __abstract = True def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) static_params = dict([(s, p[s]) for s in p.static_parameters]) results = ReverseCorrelation(self._feature_list(p), durations=p.durations, inputs=p.inputs, outputs=p.outputs, roi=p.roi, static_features=static_params, pattern_response_fn=p.pattern_response_fn, pattern_generator=p.pattern_generator) self._restore_presenter_defaults() return results def _feature_list(self, p): return [f.Presentation(range=(0, p.presentations-1), steps=p.presentations)] # Helper function for measuring direction maps
def compute_orientation_from_direction(current_values): """ Return the orientation corresponding to the given direction. Wraps the value to be in the range [0,pi), and rounds it slightly so that wrapped values are precisely the same (to avoid biases caused by vector averaging with keep_peak=True). Note that in very rare cases (1 in 10^-13?), rounding could lead to different values for a wrapped quantity, and thus give a heavily biased orientation map. In that case, just choose a different number of directions to test, to avoid that floating point boundary. """ return np.round(((dict(current_values)['direction']) + (np.pi / 2)) % np.pi, 13)
[docs]class measure_sine_pref(SinusoidalMeasureResponseCommand): """ Measure preferences for sine gratings in various combinations. Can measure orientation, spatial frequency, spatial phase, ocular dominance, horizontal phase disparity, color hue, motion direction, and speed of motion. In practice, this command is useful for any subset of the possible combinations, but if all combinations are included, the number of input patterns quickly grows quite large, much larger than the typical number of patterns required for an entire simulation. Thus typically this command will be used for the subset of dimensions that need to be evaluated together, while simpler special-purpose routines are provided below for other dimensions (such as hue and disparity). """ max_speed = param.Number(default=2.0/24.0, bounds=(0, None), doc=""" The maximum speed to measure (with zero always the minimum).""") metafeature_fns = param.HookList(default=[contrast2scale, direction2translation]) num_ocularity = param.Integer(default=1, bounds=(1, None), softbounds=(1, 3), doc=""" Number of ocularity values to test; set to 1 to disable or 2 to enable.""") num_disparity = param.Integer(default=1, bounds=(1, None), softbounds=(1, 48), doc=""" Number of disparity values to test; set to 1 to disable or e.g. 12 to enable.""") num_hue = param.Integer(default=1, bounds=(1, None), softbounds=(1, 48), doc=""" Number of hues to test; set to 1 to disable or e.g. 8 to enable.""") num_direction = param.Integer(default=0, bounds=(0, None), softbounds=(0, 48), doc=""" Number of directions to test. If nonzero, overrides num_orientation, because the orientation is calculated to be perpendicular to the direction.""") num_speeds = param.Integer(default=4, bounds=(0, None), softbounds=(0, 10), doc=""" Number of speeds to test (where zero means only static patterns). Ignored when num_direction=0.""") subplot = param.String("Orientation") def _feature_list(self, p): # Always varies frequency and phase; everything else depends on parameters. features = [f.Frequency(values=p.frequencies)] if p.num_direction == 0: features += [f.Orientation(steps=p.num_orientation, preference_fn=self.preference_fn)] features += [f.Phase(steps=p.num_phase)] if p.num_ocularity > 1: features += \ [f.Ocular(range=(0.0, 1.0), steps=p.num_ocularity)] if p.num_disparity > 1: features += \ [f.PhaseDisparity(steps=p.num_disparity)] if p.num_hue > 1: features += \ [f.Hue(steps=p.num_hue)] if p.num_direction > 0 and p.num_speeds == 0: features += \ [f.Speed(values=[0])] if p.num_direction > 0 and p.num_speeds > 0: features += \ [f.Speed(range=(0.0, p.max_speed), steps=p.num_speeds)] if p.num_direction > 0: # Compute orientation from direction dr = f.Direction(range=(0.0, 2*np.pi), steps=p.num_direction) or_values = list(set( [compute_orientation_from_direction([("direction", v)]) for v in dr.values])) features += [dr, \ f.Orientation(values=or_values, compute_fn=compute_orientation_from_direction, preference_fn=self.preference_fn)] return features
[docs]class measure_or_pref(SinusoidalMeasureResponseCommand): """Measure an orientation preference map by collating the response to patterns.""" subplot = param.String("Orientation") preference_fn = param.ClassSelector(DistributionStatisticFn, default=DSF_WeightedAverage(), doc=""" Function that will be used to analyze the distributions of unit responses.""") def _feature_list(self, p): return [f.Frequency(values=p.frequencies), f.Orientation(steps=p.num_orientation, preference_fn=self.preference_fn), f.Phase(steps=p.num_phase)]
[docs]class measure_od_pref(SinusoidalMeasureResponseCommand): """ Measure an ocular dominance preference map by collating the response to patterns. """ metafeature_fns = param.HookList( default=[contrast2scale, ocular2leftrightscale]) def _feature_list(self, p): return [f.Frequency(values=p.frequencies), f.Orientation(steps=p.num_orientation, preference_fn=self.preference_fn), f.Phase(steps=p.num_phase), f.Ocular(range=(0.0, 1.0), values=[0.0, 1.0])]
[docs]class measure_phasedisparity(SinusoidalMeasureResponseCommand): """ Measure a phase disparity preference map by collating the response to patterns. """ metafeature_fns = param.HookList(default=[contrast2scale, phasedisparity2leftrightphase]) num_disparity = param.Integer(default=12, bounds=(1, None), softbounds=(1, 48), doc="Number of disparity values to test.") orientation = param.Number(default=np.pi / 2, softbounds=(0.0, 2 * np.pi), doc=""" Orientation of the test pattern; typically vertical to measure horizontal disparity.""") static_parameters = param.List(default=["orientation", "scale", "offset"]) def _feature_list(self, p): return [f.Frequency(values=p.frequencies), f.Phase(steps=p.num_phase), f.PhaseDisparity(steps=p.num_disparity)]
[docs]class measure_dr_pref(SinusoidalMeasureResponseCommand): """Measure a direction preference map by collating the response to patterns.""" metafeature_fns = param.HookList(default=[contrast2scale, direction2translation]) num_phase = param.Integer(default=12) num_direction = param.Integer(default=6, bounds=(1, None), softbounds=(1, 48), doc="Number of directions to test.") num_speeds = param.Integer(default=4, bounds=(0, None), softbounds=(0, 10), doc=""" Number of speeds to test (where zero means only static patterns).""") max_speed = param.Number(default=2.0 / 24.0, bounds=(0, None), doc=""" The maximum speed to measure (with zero always the minimum).""") subplot = param.String("Direction") preference_fn = param.ClassSelector(DistributionStatisticFn, default=DSF_WeightedAverage(), doc=""" Function that will be used to analyze the distributions of unit responses. Sets value_scale to normalize direction preference values.""") def _feature_list(self, p): # orientation is computed from direction dr = f.Direction(steps=p.num_direction) or_values = list(set( [compute_orientation_from_direction([("direction", v)]) for v in dr.values])) return [f.Speed(values=[0]) if p.num_speeds is 0 else f.Speed(range=(0.0, p.max_speed), steps=p.num_speeds), f.Duration(values=[np.max(p.durations)]), f.Frequency(values=p.frequencies), f.Direction(steps=p.num_direction, preference_fn=self.preference_fn), f.Phase(steps=p.num_phase), f.Orientation(values=or_values, compute_fn=compute_orientation_from_direction)]
[docs]class measure_hue_pref(SinusoidalMeasureResponseCommand): """Measure a hue preference map by collating the response to patterns.""" metafeature_fns = param.HookList(default=[contrast2scale, hue2rgbscale]) num_phase = param.Integer(default=12) num_hue = param.Integer(default=8, bounds=(1, None), softbounds=(1, 48), doc="Number of hues to test.") subplot = param.String("Hue") # For backwards compatibility; not sure why it needs to differ from the default static_parameters = param.List(default=[]) def _feature_list(self, p): return [f.Frequency(values=p.frequencies), f.Orientation(steps=p.num_orientation), f.Hue(steps=p.num_hue), f.Phase(steps=p.num_phase)]
[docs]class measure_second_or_pref(SinusoidalMeasureResponseCommand): """Measure the secondary orientation preference maps.""" num_orientation = param.Integer(default=16, bounds=(1, None), softbounds=(1, 64), doc="Number of orientations to test.") true_peak = param.Boolean(default=True, doc="""If set the second orientation response is computed on the true second mode of the orientation distribution, otherwise is just the second maximum response""") subplot = param.String("Second Orientation") def _feature_list(self, p): fs = [f.Frequency(values=p.frequencies)] if p.true_peak: fs.append(f.Orientation(steps=p.num_orientation, preference_fn=DSF_BimodalPeaks())) else: fs.append(f.Orientation(steps=p.num_orientation, preference_fn=DSF_BimodalPeaks())) fs.append(f.Phase(steps=p.num_phase)) return fs
gaussian_corner = Composite( operator=np.maximum, generators=[ Gaussian(size=0.06, orientation=0, aspect_ratio=7, x=0.3), Gaussian(size=0.06, orientation=np.pi / 2, aspect_ratio=7, y=0.3)])
[docs]class measure_corner_or_pref(PositionMeasurementCommand): """Measure a corner preference map by collating the response to patterns.""" scale = param.Number(default=1.0) divisions = param.Integer(default=11) pattern_generator = param.Callable(default=gaussian_corner) x_range = param.NumericTuple((-1.2, 1.2)) y_range = param.NumericTuple((-1.2, 1.2)) num_orientation = param.Integer(default=4, bounds=(1, None), softbounds=(1, 24), doc="Number of orientations to test.") # JABALERT: Presumably this should be omitted, so that size is included? static_parameters = param.List(default=["scale", "offset"]) def _feature_list(self, p): return [f.X(range=p.x_range, steps=p.divisions, preference_fn=self.preference_fn), f.Y(range=p.y_range, steps=p.divisions, preference_fn=self.preference_fn), f.Orientation(range=(0, 2*np.pi), steps=p.num_orientation)]
[docs]class measure_corner_angle_pref(PositionMeasurementCommand): """Generate the preference map for angle shapes, by collating the response to patterns.""" scale = param.Number(default=1.0) size = param.Number(default=0.2) positions = param.Integer(default=7) x_range = param.NumericTuple((-1.0, 1.0)) y_range = param.NumericTuple((-1.0, 1.0)) num_or = param.Integer(default=4, bounds=(1, None), softbounds=(1, 24), doc=""" Number of orientations to test.""") angle_0 = param.Number(default=0.25 * np.pi, bounds=(0.0, np.pi), softbounds=(0.0, 0.5 * np.pi), doc=""" First angle to test.""") angle_1 = param.Number(default=0.75 * np.pi, bounds=(0.0, np.pi), softbounds=(0.5 * np.pi, np.pi), doc=""" Last angle to test.""") num_angle = param.Integer(default=4, bounds=(1, None), softbounds=(1, 12), doc="Number of angles to test.") pattern_generator = param.Callable( default=GaussiansCorner(aspect_ratio=4.0, cross=0.85)) static_parameters = param.List(default=["size", "scale", "offset"]) def _feature_list(self, p): """Return the list of features to vary, generate hue code static image""" if p.angle_0 < p.angle_1: angle_0 = p.angle_0 angle_1 = p.angle_1 else: angle_0 = p.angle_1 angle_1 = p.angle_0 a_range = (angle_0, angle_1) self._make_key_image(p) return [f.X(range=p.x_range, steps=p.positions), f.Y(range=p.y_range, steps=p.positions), f.Orientation(range=(0, 2 * np.pi), steps=p.num_or), f.Angle(range=a_range, steps=p.num_angle)] def _make_key_image(self, p): """ Generate the image with keys to hues used to code angles the image is saved on-the-fly, in order to fit the current choice of angle range """ width = 60 height = 300 border = 6 n_a = 7 angle_0 = p.angle_0 angle_1 = p.angle_1 a_step = 0.5 * ( angle_1 - angle_0 ) / float(n_a) x_0 = border x_1 = ( width - border ) / 2 x_a = x_1 + 2 * border y_use = height - 2 * border y_step = y_use / float(n_a) y_d = int(float(0.5 * y_step)) y_0 = border + y_d l = 15 hues = ["hsl(%2d,100%%,50%%)" % h for h in range(0, 360, 360 / n_a)] angles = [0.5 * angle_0 + a_step * a for a in range(n_a)] y_pos = [int(np.round(y_0 + y * y_step)) for y in range(n_a)] deltas = [(int(np.round(l * np.cos(a))), int(np.round(l * np.sin(a)))) for a in angles] lb_img = PILImage.new("RGB", (width, height), "white") dr_img = ImageDraw.Draw(lb_img) for h, y, d in zip(hues, y_pos, deltas): dr_img.rectangle([(x_0, y - y_d), (x_1, y + y_d)], fill=h) dr_img.line([(x_a, y), (x_a + d[0], y + d[1])], fill="black") dr_img.line([(x_a, y), (x_a + d[0], y - d[1])], fill="black") lb_img.save(p.key_img_fname) return ( self.key_img_fname )
[docs]class measure_position_pref(PositionMeasurementCommand): """Measure a position preference map by collating the response to patterns.""" scale = param.Number(default=0.3) def _feature_list(self, p): return [f.X(range=p.x_range, steps=p.divisions, preference_fn=self.preference_fn), f.Y(range=p.y_range, steps=p.divisions, preference_fn=self.preference_fn)]
[docs]class measure_or_tuning_fullfield(FeatureCurveCommand): """ Measures orientation tuning curve(s) of a particular unit using a full-field sine grating stimulus. The curve can be plotted at various different values of the contrast (or actually any other parameter) of the stimulus. If using contrast and the network contains an LGN layer, then one would usually specify michelson_contrast as the contrast_parameter. If there is no explicit LGN, then scale (offset=0.0) can be used to define the contrast. Other relevant contrast definitions (or other parameters) can also be used, provided they are defined in CoordinatedPatternGenerator and the units parameter is changed as appropriate. """ coords = param.Parameter(default=None, doc="""Ignored; here just to suppress warning.""") pattern_generator = param.Callable(default=SineGrating())
[docs]class measure_or_tuning(UnitCurveCommand): """ Measures orientation tuning curve(s) of a particular unit. Uses a circular sine grating patch as the stimulus on the retina. The curve can be plotted at various different values of the contrast (or actually any other parameter) of the stimulus. If using contrast and the network contains an LGN layer, then one would usually specify weber_contrast as the contrast_parameter. If there is no explicit LGN, then scale (offset=0.0) can be used to define the contrast. Other relevant contrast definitions (or other parameters) can also be used, provided they are defined in CoordinatedPatternGenerator and the units parameter is changed as appropriate. """ num_orientation = param.Integer(default=12) static_parameters = param.List(default=["size", "x", "y"]) def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) results = {} for coord in p.coords: p.x = p.preference_lookup_fn('x', p.outputs[0], coord, default=coord[0]) p.y = p.preference_lookup_fn('y', p.outputs[0], coord, default=coord[1]) results[coord] = self._compute_curves(p) results = self._populate_grid(results) self._restore_presenter_defaults() return results
[docs]class measure_size_response(UnitCurveCommand): """ Measure receptive field size of one unit of a sheet. Uses an expanding circular sine grating stimulus at the preferred orientation and retinal position of the specified unit. Orientation and position preference must be calulated before measuring size response. The curve can be plotted at various different values of the contrast (or actually any other parameter) of the stimulus. If using contrast and the network contains an LGN layer, then one would usually specify weber_contrast as the contrast_parameter. If there is no explicit LGN, then scale (offset=0.0) can be used to define the contrast. Other relevant contrast definitions (or other parameters) can also be used, provided they are defined in CoordinatedPatternGenerator and the units parameter is changed as appropriate. """ size = None # Disabled unused parameter static_parameters = param.List(default=["orientation", "x", "y"]) num_sizes = param.Integer(default=11, bounds=(1, None), softbounds=(1, 50), doc="Number of different sizes to test.") max_size = param.Number(default=1.0, bounds=(0.1, None), softbounds=(1, 50), doc="Maximum extent of the grating") x_axis = param.String(default="size", constant=True) def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) results = {} for coord in p.coords: # Orientations are stored as a normalized value beween 0 # and 1, so we scale them by pi to get the true orientations. p.orientation = p.preference_lookup_fn('orientation', p.outputs[0], coord) p.x = p.preference_lookup_fn('x', p.outputs[0], coord, default=coord[0]) p.y = p.preference_lookup_fn('y', p.outputs[0], coord, default=coord[1]) results[coord] = self._compute_curves(p) results = self._populate_grid(results) self._restore_presenter_defaults() return results def _feature_list(self, p): return [f.Phase(steps=p.num_phase), f.Frequency(values=p.frequencies), f.Size(range=(0.0, p.max_size), steps=p.num_sizes), f.Contrast(values=p.contrasts, preference_fn=None)]
[docs]class measure_contrast_response(UnitCurveCommand): """ Measures contrast response curves for a particular unit. Uses a circular sine grating stimulus at the preferred orientation and retinal position of the specified unit. Orientation and position preference must be calulated before measuring contrast response. The curve can be plotted at various different values of the contrast (or actually any other parameter) of the stimulus. If using contrast and the network contains an LGN layer, then one would usually specify weber_contrast as the contrast_parameter. If there is no explicit LGN, then scale (offset=0.0) can be used to define the contrast. Other relevant contrast definitions (or other parameters) can also be used, provided they are defined in CoordinatedPatternGenerator and the units parameter is changed as appropriate. """ static_parameters = param.List(default=["size", "x", "y"]) contrasts = param.List(class_=int, default=[10, 20, 30, 40, 50, 60, 70, 80, 90, 100]) relative_orientations = param.List(class_=float, default=[0.0, np.pi / 6, np.pi / 4, np.pi / 2]) x_axis = param.String(default='contrast', constant=True) def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) results = {} for coord in p.coords: p.orientation = p.preference_lookup_fn('orientation', p.outputs[0], coord) p.x = p.preference_lookup_fn('x', p.outputs[0], coord, default=coord[0]) p.y = p.preference_lookup_fn('y', p.outputs[0], coord, default=coord[1]) results[coord] = self._compute_curves(p) results = self._populate_grid(results) self._restore_presenter_defaults() return results def _feature_list(self, p): return [f.Phase(steps=p.num_phase), f.Frequency(values=p.frequencies), f.Contrast(values=p.contrasts), f.Orientation(preference_fn=None, values=[p.orientation+ro for ro in p.relative_orientations])]
[docs]class measure_frequency_response(UnitCurveCommand): """ Measure spatial frequency preference of one unit of a sheet. Uses an constant circular sine grating stimulus at the preferred with varying spatial frequency orientation and retinal position of the specified unit. Orientation and position preference must be calulated before measuring size response. The curve can be plotted at various different values of the contrast (or actually any other parameter) of the stimulus. If using contrast and the network contains an LGN layer, then one would usually specify weber_contrast as the contrast_parameter. If there is no explicit LGN, then scale (offset=0.0) can be used to define the contrast. Other relevant contrast definitions (or other parameters) can also be used, provided they are defined in one of the appropriate metaparameter_fns. """ x_axis = param.String(default="frequency", constant=True) static_parameters = param.List(default=["orientation", "x", "y"]) num_freq = param.Integer(default=21, bounds=(1, None), softbounds=(1, 50), doc="Number of different sizes to test.") max_freq = param.Number(default=10.0, bounds=(0.1, None), softbounds=(1, 50), doc="Maximum extent of the grating") def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) results = {} for coord in p.coords: # Orientations are stored as a normalized value beween 0 # and 1, so we scale them by pi to get the true orientations. p.orientation = np.pi * p.preference_lookup_fn('orientation', p.outputs[0], coord) p.x = p.preference_lookup_fn('x', p.outputs[0], coord, default=coord[0]) p.y = p.preference_lookup_fn('y', p.outputs[0], coord, default=coord[1]) results[coord] = self._compute_curves(p) results = self._populate_grid(results) self._restore_presenter_defaults() return results def _feature_list(self, p): return [f.Orientation(values=[p.orientation]), f.Phase(steps=p.num_phase), f.Frequency(range=(0.0, p.max_freq), steps=p.num_freq), f.Size(values=[p.size]), f.Contrast(values=p.contrasts, preference_fn=None)]
[docs]class measure_orientation_contrast(UnitCurveCommand): """ Measures the response to a center sine grating disk and a surround sine grating ring at different contrasts of the central disk. The central disk is set to the preferred orientation of the unit to be measured. The surround disk orientation (relative to the central grating) and contrast can be varied, as can the size of both disks. """ metafeature_fns = param.HookList( default=[contrast2centersurroundscale.instance(contrast_parameter='weber_contrast')]) pattern_generator = param.Callable( default=OrientationContrast(surround_orientation_relative=True)) size = None # Disabled unused parameter # Maybe instead of the below, use size and some relative parameter, to allow easy scaling? sizecenter = param.Number(default=0.5, bounds=(0, None), doc=""" The size of the central pattern to present.""") sizesurround = param.Number(default=1.0, bounds=(0, None), doc=""" The size of the surround pattern to present.""") thickness = param.Number(default=0.5, bounds=(0, None), softbounds=(0, 1.5), doc="""Ring thickness.""") contrastsurround = param.List(default=[30, 60, 80, 90], doc="Contrast of the surround.") contrastcenter = param.Number(default=100, bounds=(0, 100), doc="""Contrast of the center.""") x_axis = param.String(default='orientationsurround', constant=True) orientation_center = param.Number(default=0.0, softbounds=(0.0, np.pi), doc=""" Orientation of the center grating patch""") num_orientation = param.Integer(default=9) static_parameters = param.List( default=["x", "y", "sizecenter", "sizesurround", "orientationcenter", "thickness", "contrastcenter"]) or_surrounds = [] def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self._set_presenter_overrides(p) if not p.num_orientation % 2: raise Exception("Use odd number of surround orientation to ensure" "the orthogonal to the preferred orientation is" "covered.") results = {} for coord in p.coords: self.or_surrounds = [] orientation = p.preference_lookup_fn('orientation', p.outputs[0], coord, default=p.orientation_center) p.orientationcenter = orientation orientation_step = np.pi / (p.num_orientation - 1) for i in xrange(0, p.num_orientation - 1): self.or_surrounds.append( orientation - np.pi / 2 + i * orientation_step) p.x = p.preference_lookup_fn('x', p.outputs[0], coord, default=coord[0]) p.y = p.preference_lookup_fn('y', p.outputs[0], coord, default=coord[1]) results[coord] = self._compute_curves(p) results = self._populate_grid(results) self._restore_presenter_defaults() return results def _feature_list(self, p): return [f.Phase(steps=p.num_phase), f.Frequency(values=p.frequencies), f.OrientationSurround(values=self.or_surrounds), f.ContrastSurround(values=p.contrastsurround, preference_fn=None)]
[docs]class test_measure(UnitCurveCommand): static_parameters = param.List(default=["size", "x", "y"]) x_axis = param.String(default='contrast', constant=True) units = param.String(default=" rad") def __call__(self, **params): p = ParamOverrides(self, params, allow_extra_keywords=True) self.x = 0.0 self.y = 0.0 self._compute_curves(p) def _feature_list(self, p): return [f.Orientation(values=[1.0] * 22, preference_fn=None), f.Contrast(values=[100])]
__all__ = [ "measure_corner_angle_pref", "measure_corner_or_pref", "measure_dr_pref", "measure_hue_pref", "measure_od_pref", "measure_or_pref", "measure_phasedisparity", "measure_response", "measure_rfs", "measure_second_or_pref", "measure_sine_pref", "measure_contrast_response", "measure_frequency_response", "measure_or_tuning", "measure_or_tuning_fullfield", "measure_orientation_contrast", "measure_position_pref", "measure_size_response", "test_measure" ] #=================# # Collector hooks # #=================# def array_hook(obj, *args, **kwargs): return None if obj is None else Image(obj.copy(), **kwargs) def measurement_hook(obj, *args, **kwargs): return obj(*args, **kwargs) def pattern_hook(obj,*args, **kwargs): return obj[:] Collector.for_type(np.ndarray, array_hook) Collector.for_type(measure_response, measurement_hook, mode='merge') Collector.for_type(MeasureResponseCommand, measurement_hook, mode='merge') Collector.for_type(imagen.PatternGenerator, pattern_hook)

FeatureMapper

Table Of Contents

This Page