Source code for imagen.patterncoordinator

"""
Provides the class PatternCoordinator and a family of
FeatureCoordinator classes.

PatternCoordinator creates a set of pattern generators whose
parameters are related in some way, as controlled by a subclass of
FeatureCoordinator.
"""

import os
import math
import json
import glob
import collections
import copy

import param
from param.parameterized import ParamOverrides

from imagen.patterngenerator import PatternGenerator
from imagen.image import FileImage
from imagen import Gaussian, Composite, Selector, CompositeBase

import numbergen


[docs]class FeatureCoordinator(param.ParameterizedFunction): """ A FeatureCoordinator modifies a supplied PatternGenerator. The modification can depend on the string pattern_label and pattern_number supplied, in order to coordinate a set of patterns of the same type with systematic differences. FeatureCoordinators that introduce randomness should be seeded with a value based on the supplied master_seed, so that an entire set of patterns can be controlled with the one master_seed value. Subclasses of this class can accept parameters provided in params. This superclass ensures a common interface across all FeatureCoordinator subclasses, which is necessary because they are usually stored in a list, with each item called the same way. """ def __call__(self, pattern, pattern_label, pattern_number, master_seed, **params): """ 'pattern' is the PatternGenerator to be modified 'pattern_label' is the name to be given to this PatternGenerator, used to select different behaviors 'pattern_number' is an integer value distinguishing between multiple patterns with the same pattern_label 'master_seed' is to be used for any random number generator seeds used for this pattern 'params' consists of optional keyword-value pairs to be provided for subclasses' parameters """ raise NotImplementedError
[docs]class XCoordinator(FeatureCoordinator): """ Chooses a random value for the x coordinate, subject to the provided position_bound_x. """ position_bound_x = param.Number(default=0.8,doc=""" Left/rightmost position of the pattern center on the x axis.""") def __call__(self, pattern, pattern_label, pattern_number, master_seed, **params): p = ParamOverrides(self,params,allow_extra_keywords=True) new_pattern=copy.copy(pattern) new_pattern.x = pattern.get_value_generator('x')+\ numbergen.UniformRandom(lbound=-p.position_bound_x, ubound=p.position_bound_x, seed=master_seed+12+pattern_number, name="XCoordinator"+str(pattern_number)) return new_pattern
[docs]class YCoordinator(FeatureCoordinator): """ Chooses a random value for the y coordinate, subject to the provided position_bound_y. """ position_bound_y = param.Number(default=0.8,doc=""" Upper/lowermost position of the pattern center on the y axis.""") def __call__(self, pattern, pattern_label, pattern_number, master_seed, **params): p = ParamOverrides(self,params,allow_extra_keywords=True) new_pattern=copy.copy(pattern) new_pattern.y = pattern.get_value_generator('y')+\ numbergen.UniformRandom(lbound=-p.position_bound_y, ubound=p.position_bound_y, seed=master_seed+35+pattern_number, name="YCoordinator"+str(pattern_number)) return new_pattern
[docs]class OrientationCoordinator(FeatureCoordinator): """ Chooses a random orientation within the specified orientation_bound in each direction. """ orientation_bound = param.Number(default=math.pi,doc=""" Rotate pattern around the origin by at most orientation_bound radians (in both directions).""") align_orientations = param.Boolean(default=False, doc=""" Whether or not to align pattern orientations when composing multiple patterns together. Alignment may be useful to prevent crossing stimuli or may be appropriate for moving patterns sweeped in a single direction of motion. """) def __call__(self, pattern, pattern_label, pattern_number, master_seed, **params): p = ParamOverrides(self,params,allow_extra_keywords=True) new_pattern=copy.copy(pattern) new_pattern.orientation = pattern.get_value_generator('orientation')+\ numbergen.UniformRandom(lbound=-p.orientation_bound, ubound=p.orientation_bound, seed=master_seed+21+(0 if p.align_orientations else pattern_number), name=("OrientationCoordinator" + ('' if p.align_orientations else str(pattern_number))) ) return new_pattern
[docs]class PatternCoordinator(param.Parameterized): """ Returns a set of coordinated PatternGenerators, named according to pattern_labels. The features to be modified are specified with the features_to_vary parameter. A feature is something coordinated between the PatternGenerators, either: a. one of the existing parameters of the PatternGenerators (such as size), or b. a variable from which values for one of the existing parameters can be calculated (such as a position offset between two PatternGenerators), or c. a value inherent to a particular existing image dataset (due to how the dataset was collected or generated). Each PatternGenerator is first instantiated with the supplied pattern_parameters, and then subclasses of FeatureCoordinator are applied sequentially to modify the specified or default parameter values of each PatternGenerator. """ pattern_type = param.ClassSelector(PatternGenerator,default=Gaussian,is_instance=False,doc=""" PatternGenerator type to be used.""") pattern_parameters = param.Dict(default={'size': 0.088388, 'aspect_ratio': 4.66667},doc=""" Parameter values to be passed to the PatternGenerator specified in pattern_type.""") patterns_per_label = param.Integer(default=2,doc=""" Number of patterns to generate and combine for a given label.""") features_to_vary = param.List(default=['xy','or'],class_=str,doc=""" Stimulus features that the caller wishes to be varied, such as: :'xy': Position in x and y coordinates :'or': Orientation Subclasses and callers may extend this list to include any other features for which a coordinator has been defined in feature_coordinators.""") pattern_labels = param.List(default=['Input'],class_=str,bounds=(1,None),doc=""" For each string in this list, a PatternGenerator of the requested pattern_type will be returned, with parameters whose values may depend on the string label supplied. For instance, if the list ["Pattern1","Pattern2"] is supplied, a metafeature function might inspect those pattern_labels and set parameters differently for Pattern1 and Pattern2, returning two different PatternGenerators with those pattern_labels.""") master_seed = param.Integer(default=0,doc=""" Base seed for all pattern parameter values. Each numbered pattern on each of the various pattern_labels will normally use a different random seed, but all of these seeds should include this master_seed value, so that changing it will change all of the random pattern parameter streams.""") composite_type = param.ClassSelector(CompositeBase,default=Composite, is_instance=False,doc=""" Class that combines the patterns_per_label individual patterns and creates a single combined pattern that it returns for a given label. For instance, imagen.Composite can merge the individual patterns into a single pattern using a variety of operators like add or maximum, while imagen.Selector can choose one out of a given set of patterns.""") composite_parameters = param.Dict(default={},doc=""" If present, these parameter values will be passed to the composite specified in composite_type.""") feature_coordinators = param.Dict(default=collections.OrderedDict([ ('xy', [XCoordinator,YCoordinator]), ('or', OrientationCoordinator)]),doc=""" Mapping from the feature name (key) to the method(s) to be applied to the pattern generators. The value can either be a single method or a list of methods.""") def _create_patterns(self, properties=None): """ Return a list (of length patterns_per_label) of PatternGenerator instances. Should use pattern_type and pattern_parameters to create each pattern. properties is a dictionary, e.g. {'pattern_label': pattern_label}, which can be used to create PatternGenerators depending on the requested pattern_label """ return [self.pattern_type(**self.pattern_parameters) for i in range(self.patterns_per_label)] def __init__(self,inherent_features=[],**params): """ If a dataset already and inherently includes certain features, a list with the inherent feature names should be supplied. Any extra parameter values supplied here will be passed down to the feature_coordinators requested in features_to_vary. """ p=ParamOverrides(self,params,allow_extra_keywords=True) super(PatternCoordinator, self).__init__(**p.param_keywords()) self._feature_params = p.extra_keywords() self._inherent_features = inherent_features # TFALERT: Once spatial frequency (sf) is added, this will # cause warnings, because all image datasets will have a # spatial frequency inherent feature, but mostly we just # ignore that by having only a single size of DoG, which # discards all but a narrow range of sf. So the dataset will # have sf inherently, but that won't be an error or even # worthy of a warning. if(len(set(self._inherent_features) - set(self.features_to_vary))): self.warning('Inherent feature present which is not requested in features') self._feature_coordinators_to_apply = [] for feature, feature_coordinator in self.feature_coordinators.items(): if feature in self.features_to_vary and feature not in self._inherent_features: # if it is a list, append each list item individually if isinstance(feature_coordinator,list): for individual_feature_coordinator in feature_coordinator: self._feature_coordinators_to_apply.append(individual_feature_coordinator) else: self._feature_coordinators_to_apply.append(feature_coordinator) def __call__(self): coordinated_pattern_generators={} for pattern_label in self.pattern_labels: patterns=self._create_patterns({'pattern_label': pattern_label}) # Apply _feature_coordinators_to_apply for i in range(len(patterns)): for fn in self._feature_coordinators_to_apply: patterns[i]=fn(patterns[i],pattern_label, i, self.master_seed, **self._feature_params) combined_patterns=self.composite_type(generators=patterns, **self.composite_parameters) coordinated_pattern_generators.update({pattern_label:combined_patterns}) return coordinated_pattern_generators
class PatternCoordinatorImages(PatternCoordinator): pattern_type = param.ClassSelector(PatternGenerator, default=FileImage,is_instance=False) pattern_parameters = param.Dict(default={'size': 10}) composite_type = param.ClassSelector(CompositeBase, default=Selector,is_instance=False) def __init__(self,dataset_name,**params): """ dataset_name is the path to a folder containing a MANIFEST_json (https://docs.python.org/2/library/json.html), which contains a description for a dataset. If no MANIFEST_json is present, all image files in the specified folder are used. Any extra parameter values supplied here will be passed down to the feature_coordinators requested in features_to_vary. The JSON file can contain any of the following entries, if an entry is not present, the default is used: :'dataset_name': Name of the dataset (string, default=filepath) :'length': Number of images in the dataset (integer, default=number of files in directory matching filename_template) :'description': Description of the dataset (string, default="") :'source': Citation of paper for which the dataset was created (string, default=name) :'filename_template': Path to the images with placeholders ({placeholder_name}) for inherent features and the image number, e.g. "filename_template": "images/image{i}.png". The placeholders are replaced according to placeholder_mapping. Alternatively, glob patterns such as * or ? can be used, e.g. "filename_template": "images/*.png" (default=path_to_dataset_name/*.*) :'placeholder_mapping': Dictionary specifying the replacement of placeholders in filename_template; value is used in eval() (default={}). :'inherent_features': Features for which the corresponding feature_coordinators should not be applied (default=['sf','or','cr']) Currently, the label of the pattern generator ('pattern_label') as well as the image number ('current_image') are given as parameters to each callable supplied in placeholder_mapping, where current_image varies from 0 to length-1 and pattern_label is one of the items of pattern_labels. (python code, default={'i': lambda params: '%02d' % (params['current_image']+1)} Example 1: Imagine having images without any inherent features named as follows: "images/image01.png", "images/image02.png" and so on. Then, filename_template: "images/image{i}.png" and "placeholder_mapping": "{'i': lambda params: '%02d' % (params['current_image']+1)}" This replaces {i} in the template with the current image number + 1 Example 2: Imagine having image pairs from a stereo webcam named as follows: "images/image01_left.png", "images/image01_right.png" and so on. If pattern_labels=['Left','Right'], then filename_template: "images/image{i}_{dy}" and "placeholder_mapping": "{'i': lambda params: '%02d' % (params['current_image']+1), 'dy':lambda params: 'left' if params['pattern_label']=='Left' else 'right'}" Here, additionally {dy} gets replaced by either 'left' if the pattern_label is 'Left' or 'right' otherwise If the directory does not contain a MANIFEST_json file, the defaults are as follows: :'filename_template': filepath/*.*, whereas filepath is the path given in dataset_name :'patterns_per_label': Number of image files in filepath, whereas filepath is the path given in dataset_name :'inherent_features': [] :'placeholder_mapping': {} """ filepath=param.resolve_path(dataset_name,path_to_file=False) self.dataset_name=filepath self.filename_template=filepath+"/*.*" self.description="" self.source=self.dataset_name self.placeholder_mapping={} patterns_per_label = len(glob.glob(self.filename_template)) inherent_features=['sf','cr'] try: filename=param.resolve_path(dataset_name+'/MANIFEST_json') filepath=os.path.dirname(filename) dataset=json.loads(open(filename).read()) self.dataset_name=dataset.get('dataset_name', self.dataset_name) self.description=dataset.get('description', self.description) self.filename_template=dataset.get('filename_template', self.filename_template) patterns_per_label=dataset.get('length', len(glob.glob(self.filename_template))) self.source=dataset.get('source', self.source) self.placeholder_mapping=(eval(dataset['placeholder_mapping']) if 'placeholder_mapping' in dataset else self.placeholder_mapping) inherent_features=dataset.get('inherent_features', inherent_features) except IOError: pass if 'patterns_per_label' not in params: params['patterns_per_label'] = self.patterns_per_label super(PatternCoordinatorImages, self).__init__(inherent_features,**params) def _generate_filenames(self, params): if(len(self.placeholder_mapping)>0): filenames = [self.filename_template]*self.patterns_per_label for placeholder in self.placeholder_mapping: filenames = [filename.replace('{'+placeholder+'}', self.placeholder_mapping[placeholder](params)) for filename,params['current_image'] in zip(filenames,range(self.patterns_per_label))] else: filenames = sorted(glob.glob(self.filename_template)) return filenames def _create_patterns(self, properties): return [self.pattern_type( filename=f, cache_image=False, **self.pattern_parameters) for f,i in zip(self._generate_filenames(properties), range(self.patterns_per_label))]

ImaGen

Table Of Contents

This Page