def _set_image(self,image): # Stores a SheetCoordinateSystem with an activity matrix # representing the image if not isinstance(image,numpy.ndarray): image = array(image,Float) rows,cols = image.shape self.scs = SheetCoordinateSystem(xdensity=1.0,ydensity=1.0, bounds=BoundingBox(points=((-cols/2.0,-rows/2.0), ( cols/2.0, rows/2.0)))) self.scs.activity=image
def __equalize_densities(self, nominal_bounds, nominal_density): """ Calculate the true density along x, and adjust the top and bottom bounds so that the density along y will be equal. Returns (adjusted_bounds,true_density) """ left, bottom, right, top = nominal_bounds.lbrt() width = right - left height = top - bottom center_y = bottom + height / 2.0 # The true density is not equal to the nominal_density # when nominal_density*(right-left) is not an integer. true_density = int(nominal_density * (width)) / float(width) n_cells = round(height * true_density, 0) adjusted_half_height = n_cells / true_density / 2.0 # (The above might be clearer as (step*n_units)/2.0, where # step=1.0/density.) return (BoundingBox(points=((left, center_y - adjusted_half_height), (right, center_y + adjusted_half_height))), true_density)
class PatternGenerator(param.Parameterized): """ A class hierarchy for callable objects that can generate 2D patterns. Once initialized, PatternGenerators can be called to generate a value or a matrix of values from a 2D function, typically accepting at least x and y. A PatternGenerator's Parameters can make use of Parameter's precedence attribute to specify the order in which they should appear, e.g. in a GUI. The precedence attribute has a nominal range of 0.0 to 1.0, with ordering going from 0.0 (first) to 1.0 (last), but any value is allowed. The orientation and layout of the pattern matrices is defined by the SheetCoordinateSystem class, which see. Note that not every parameter defined for a PatternGenerator will be used by every subclass. For instance, a Constant pattern will ignore the x, y, orientation, and size parameters, because the pattern does not vary with any of those parameters. However, those parameters are still defined for all PatternGenerators, even Constant patterns, to allow PatternGenerators to be scaled, rotated, translated, etc. uniformly. """ __abstract = True bounds = BoundingRegionParameter( default=BoundingBox(points=((-0.5,-0.5), (0.5,0.5))),precedence=-1, doc="BoundingBox of the area in which the pattern is generated.") xdensity = param.Number(default=10,bounds=(0,None),precedence=-1,doc=""" Density (number of samples per 1.0 length) in the x direction.""") ydensity = param.Number(default=10,bounds=(0,None),precedence=-1,doc=""" Density (number of samples per 1.0 length) in the y direction. Typically the same as the xdensity.""") x = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.20,doc=""" X-coordinate location of pattern center.""") y = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.21,doc=""" Y-coordinate location of pattern center.""") position = param.Composite(attribs=['x','y'],precedence=-1,doc=""" Coordinates of location of pattern center. Provides a convenient way to set the x and y parameters together as a tuple (x,y), but shares the same actual storage as x and y (and thus only position OR x and y need to be specified).""") orientation = param.Number(default=0.0,softbounds=(0.0,2*pi),precedence=0.40,doc=""" Polar angle of pattern, i.e., the orientation in the Cartesian coordinate system, with zero at 3 o'clock and increasing counterclockwise.""") size = param.Number(default=1.0,bounds=(0.0,None),softbounds=(0.0,6.0), precedence=0.30,doc="""Determines the overall size of the pattern.""") scale = param.Number(default=1.0,softbounds=(0.0,2.0),precedence=0.10,doc=""" Multiplicative strength of input pattern, defaulting to 1.0""") offset = param.Number(default=0.0,softbounds=(-1.0,1.0),precedence=0.11,doc=""" Additive offset to input pattern, defaulting to 0.0""") mask = param.Parameter(default=None,precedence=-1,doc=""" Optional object (expected to be an array) with which to multiply the pattern array after it has been created, before any output_fns are applied. This can be used to shape the pattern.""") # Note that the class type is overridden to PatternGenerator below mask_shape = param.ClassSelector(param.Parameterized,default=None,precedence=0.06,doc=""" Optional PatternGenerator used to construct a mask to be applied to the pattern.""") output_fns = param.HookList(default=[],class_=TransferFn,precedence=0.08,doc=""" Optional function(s) to apply to the pattern array after it has been created. Can be used for normalization, thresholding, etc.""") def __init__(self,**params): super(PatternGenerator, self).__init__(**params) self.set_matrix_dimensions(self.bounds, self.xdensity, self.ydensity) def __call__(self,**params_to_override): """ Call the subclass's 'function' method on a rotated and scaled coordinate system. Creates and fills an array with the requested pattern. If called without any params, uses the values for the Parameters as currently set on the object. Otherwise, any params specified override those currently set on the object. """ p=ParamOverrides(self,params_to_override) # CEBERRORALERT: position parameter is not currently # supported. We should delete the position parameter or fix # this. # # position=params_to_override.get('position',None) if position # is not None: x,y = position self._setup_xy(p.bounds,p.xdensity,p.ydensity,p.x,p.y,p.orientation) fn_result = self.function(p) self._apply_mask(p,fn_result) result = p.scale*fn_result+p.offset for of in p.output_fns: of(result) return result def _setup_xy(self,bounds,xdensity,ydensity,x,y,orientation): """ Produce pattern coordinate matrices from the bounds and density (or rows and cols), and transforms them according to x, y, and orientation. """ self.debug(lambda:"bounds=%s, xdensity=%s, ydensity=%s, x=%s, y=%s, orientation=%s"%(bounds,xdensity,ydensity,x,y,orientation)) # Generate vectors representing coordinates at which the pattern # will be sampled. # CB: note to myself - use slice_._scs if supplied? x_points,y_points = SheetCoordinateSystem(bounds,xdensity,ydensity).sheetcoordinates_of_matrixidx() # Generate matrices of x and y sheet coordinates at which to # sample pattern, at the correct orientation self.pattern_x, self.pattern_y = self._create_and_rotate_coordinate_arrays(x_points-x,y_points-y,orientation) def function(self,p): """ Function to draw a pattern that will then be scaled and rotated. Instead of implementing __call__ directly, PatternGenerator subclasses will typically implement this helper function used by __call__, because that way they can let __call__ handle the scaling and rotation for them. Alternatively, __call__ itself can be reimplemented entirely by a subclass (e.g. if it does not need to do any scaling or rotation), in which case this function will be ignored. """ raise NotImplementedError def _create_and_rotate_coordinate_arrays(self, x, y, orientation): """ Create pattern matrices from x and y vectors, and rotate them to the specified orientation. """ # Using this two-liner requires that x increase from left to # right and y decrease from left to right; I don't think it # can be rewritten in so little code otherwise - but please # prove me wrong. pattern_y = subtract.outer(cos(orientation)*y, sin(orientation)*x) pattern_x = add.outer(sin(orientation)*y, cos(orientation)*x) return pattern_x, pattern_y def _apply_mask(self,p,mat): """Create (if necessary) and apply the mask to the given matrix mat.""" mask = p.mask ms=p.mask_shape if ms is not None: mask = ms(x=p.x+p.size*(ms.x*cos(p.orientation)-ms.y*sin(p.orientation)), y=p.y+p.size*(ms.x*sin(p.orientation)+ms.y*cos(p.orientation)), orientation=ms.orientation+p.orientation,size=ms.size*p.size, bounds=p.bounds,ydensity=p.ydensity,xdensity=p.xdensity) if mask is not None: mat*=mask def set_matrix_dimensions(self, bounds, xdensity, ydensity): """ Change the dimensions of the matrix into which the pattern will be drawn. Users of this class should call this method rather than changing the bounds, xdensity, and ydensity parameters directly. Subclasses can override this method to update any internal data structures that may depend on the matrix dimensions. """ self.bounds = bounds self.xdensity = xdensity self.ydensity = ydensity
class Sheet(EventProcessor, SheetCoordinateSystem): # pylint: disable-msg=W0223 """ The generic base class for neural sheets. See SheetCoordinateSystem for how Sheet represents space, and EventProcessor for how Sheet handles time. output_fns are functions that take an activity matrix and produce an identically shaped output matrix. The default is having no output_fns. """ __abstract = True nominal_bounds = BoundingRegionParameter(BoundingBox(radius=0.5), constant=True, doc=""" User-specified BoundingBox of the Sheet coordinate area covered by this Sheet. The left and right bounds--if specified--will always be observed, but the top and bottom bounds may be adjusted to ensure the density in the y direction is the same as the density in the x direction. In such a case, the top and bottom bounds are adjusted so that the center y point remains the same, and each bound is as close as possible to its specified value. The actual value of this Parameter is not adjusted, but the true bounds may be found from the 'bounds' attribute of this object. """) nominal_density = param.Number(default=10, constant=True, doc=""" User-specified number of processing units per 1.0 distance horizontally or vertically in Sheet coordinates. The actual number may be different because of discretization; the matrix needs to tile the plane exactly, and for that to work the density might need to be adjusted. For instance, an area of 3x2 cannot have a density of 2 in each direction. The true density may be obtained from either the xdensity or ydensity attribute (since these are identical for a Sheet). """) plastic = param.Boolean(True, doc=""" Setting this to False tells the Sheet not to change its permanent state (e.g. any connection weights) based on incoming events. """) precedence = param.Number(default=0.1, softbounds=(0.0, 1.0), doc=""" Allows a sorting order for Sheets, e.g. in the GUI.""") row_precedence = param.Number(default=0.5, softbounds=(0.0, 1.0), doc=""" Allows grouping of Sheets before sorting precedence is applied, e.g. for two-dimensional plots in the GUI.""") layout_location = param.NumericTuple(default=(-1, -1), precedence=-1, doc=""" Location for this Sheet in an arbitrary pixel-based space in which Sheets can be laid out for visualization.""") output_fns = param.HookList( default=[], class_=TransferFn, doc= "Output function(s) to apply (if apply_output_fns is true) to this Sheet's activity." ) apply_output_fns = param.Boolean( default=True, doc="Whether to apply the output_fn after computing an Activity matrix." ) def _get_density(self): return self.xdensity density = property( _get_density, doc= """The sheet's true density (i.e. the xdensity, which is equal to the ydensity for a Sheet.)""" ) def __init__(self, **params): """ Initialize this object as an EventProcessor, then also as a SheetCoordinateSystem with equal xdensity and ydensity. sheet_views is a dictionary that stores SheetViews, i.e. representations of the sheet for use by analysis or plotting code. """ EventProcessor.__init__(self, **params) # Initialize this object as a SheetCoordinateSystem, with # the same density along y as along x. SheetCoordinateSystem.__init__(self, self.nominal_bounds, self.nominal_density) n_units = round((self.lbrt[2] - self.lbrt[0]) * self.xdensity, 0) if n_units < 1: raise ValueError( "Sheet bounds and density must be specified such that the "+ \ "sheet has at least one unit in each direction; " \ +self.name+ " does not.") # setup the activity matrix self.activity = zeros(self.shape, activity_type) # For non-plastic inputs self.__saved_activity = [] self._plasticity_setting_stack = [] self.sheet_views = {} ### JABALERT: This should be deleted now that sheet_views is public ### JC: shouldn't we keep that, or at least write a function in ### utils that deletes a value in a dictinnary without returning an ### error if the key is not in the dict? I leave for the moment, ### and have to ask Jim to advise. def release_sheet_view(self, view_name): """ Delete the dictionary entry with key entry 'view_name' to save memory. """ if self.sheet_views.has_key(view_name): del self.sheet_views[view_name] # CB: what to call this? sheetcoords()? sheetcoords_of_grid()? idxsheetcoords()? def sheetcoords_of_idx_grid(self): """ Return an array of x-coordinates and an array of y-coordinates corresponding to the activity matrix of the sheet. """ nrows, ncols = self.activity.shape C, R = meshgrid(arange(ncols), arange(nrows)) X, Y = self.matrixidx2sheet(R, C) return X, Y # CB: check whether we need this function any more. def row_col_sheetcoords(self): """ Return an array of Y-coordinates corresponding to the rows of the activity matrix of the sheet, and an array of X-coordinates corresponding to the columns. """ # The row and column centers are returned in matrix (not # sheet) order (hence the reversals below). nrows, ncols = self.activity.shape return self.matrixidx2sheet(arange(nrows - 1, -1, -1), arange(ncols))[::-1] # CBALERT: to be removed once other code uses # row_col_sheetcoords() or sheetcoords_of_idx_grid(). def sheet_rows(self): return self.row_col_sheetcoords()[0] def sheet_cols(self): return self.row_col_sheetcoords()[1] # CEBALERT: haven't really thought about what to put in this. The # way it is now, subclasses could make a super.activate() call to # avoid repeating some stuff. def activate(self): """ Collect activity from each projection, combine it to calculate the activity for this sheet, and send the result out. Subclasses will need to override this method to whatever it means to calculate activity in that subclass. """ if self.apply_output_fns: for of in self.output_fns: of(self.activity) self.send_output(src_port='Activity', data=self.activity) def state_push(self): """ Save the current state of this sheet to an internal stack. This method is used by operations that need to test the response of the sheet without permanently altering its state, e.g. for measuring maps or probing the current behavior non-invasively. By default, only the activity pattern of this sheet is saved, but subclasses should add saving for any additional state that they maintain, or strange bugs are likely to occur. The state can be restored using state_pop(). Note that Sheets that do learning need not save the values of all connection weights, if any, because plasticity can be turned off explicitly. Thus this method is intended only for shorter-term state. """ self.__saved_activity.append(array(self.activity)) EventProcessor.state_push(self) for of in self.output_fns: if hasattr(of, 'state_push'): of.state_push() def state_pop(self): """ Pop the most recently saved state off the stack. See state_push() for more details. """ self.activity = self.__saved_activity.pop() EventProcessor.state_pop(self) for of in self.output_fns: if hasattr(of, 'state_pop'): of.state_pop() def activity_len(self): """Return the number of items that have been saved by state_push().""" return len(self.__saved_activity) def override_plasticity_state(self, new_plasticity_state): """ Temporarily override plasticity of medium and long term internal state. This function should be implemented by all subclasses so that it preserves the ability of the Sheet to compute activity, i.e. to operate over a short time scale, while preventing any lasting changes to the state (if new_plasticity_state=False). Any operation that does not have any lasting state, such as those affecting only the current activity level, should not be affected by this call. By default, simply saves a copy of the plastic flag to an internal stack (so that it can be restored by restore_plasticity_state()), and then sets plastic to new_plasticity_state. """ self._plasticity_setting_stack.append(self.plastic) self.plastic = new_plasticity_state def restore_plasticity_state(self): """ Restores plasticity of medium and long term internal state after a override_plasticity_state call. This function should be implemented by all subclasses to remove the effect of the most recent override_plasticity_state call, i.e. to restore plasticity of any type that was overridden. """ self.plastic = self._plasticity_setting_stack.pop() def n_bytes(self): """ Return a lower bound for the memory taken by this sheet, in bytes. Typically, this number will include the activity array and any similar arrays, plus any other significant data owned (in some sense) by this Sheet. It will not usually include memory taken by the Python dictionary or various "housekeeping" attributes, which usually contribute only a small amount to the memory requirements. Subclasses should reimplement this method if they store a significant amount of data other than in the activity array. """ return self.activity.nbytes
class CFProjection(Projection): """ A projection composed of ConnectionFields from a Sheet into a ProjectionSheet. CFProjection computes its activity using a response_fn of type CFPResponseFn (typically a CF-aware version of mdot) and output_fns (typically none). The initial contents of the ConnectionFields mapping from the input Sheet into the target ProjectionSheet are controlled by the weights_generator, cf_shape, and weights_output_fn parameters, while the location of the ConnectionField is controlled by the coord_mapper parameter. Any subclass has to implement the interface activate(self,input_activity) that computes the response from the input and stores it in the activity array. """ response_fn = param.ClassSelector( CFPResponseFn, default=CFPRF_Plugin(), doc= 'Function for computing the Projection response to an input pattern.') cf_type = param.Parameter( default=ConnectionField, constant=True, doc="Type of ConnectionField to use when creating individual CFs.") # JPHACKALERT: Not all support for null CFs has been implemented. # CF plotting and C-optimized CFPxF_ functions need # to be fixed to support null CFs without crashing. allow_null_cfs = param.Boolean( default=False, doc="Whether or not the projection can have entirely empty CFs") nominal_bounds_template = BoundingRegionParameter( default=BoundingBox(radius=0.1), doc=""" Bounds defining the Sheet area covered by a prototypical ConnectionField. The true bounds will differ depending on the density (see create_slice_template()).""" ) weights_generator = param.ClassSelector( PatternGenerator, default=patterngenerator.Constant(), constant=True, doc="Generate initial weights values.") cf_shape = param.ClassSelector( PatternGenerator, default=patterngenerator.Constant(), constant=True, doc="Mask pattern to define the shape of the connection fields.") same_cf_shape_for_all_cfs = param.Boolean(default=True, doc=""" Whether or not to share a single cf_shape mask for all CFs. If True, the cf_shape is evaluated only once and shared for all CFs, which saves computation time and memory. If False, the cf_shape is evaluated once for each CF, allowing each to have its own shape.""") learning_fn = param.ClassSelector( CFPLearningFn, default=CFPLF_Plugin(), doc= 'Function for computing changes to the weights based on one activation step.' ) # JABALERT: Shouldn't learning_rate be owned by the learning_fn? learning_rate = param.Number(default=0.0, softbounds=(0, 100), doc=""" Amount of learning at each step for this projection, specified in units that are independent of the density of each Sheet.""") weights_output_fns = param.HookList( default=[CFPOF_Plugin()], class_=CFPOutputFn, doc='Functions applied to each CF after learning.') strength = param.Number(default=1.0, doc=""" Global multiplicative scaling applied to the Activity of this Sheet.""" ) coord_mapper = param.ClassSelector( CoordinateMapperFn, default=IdentityMF(), doc='Function to map a projected coordinate into the target sheet.') # CEBALERT: this is temporary (allows c++ matching in certain # cases). We will allow the user to override the mask size, but # by offering a scaling parameter. autosize_mask = param.Boolean(default=True, constant=True, precedence=-1, doc=""" Topographica sets the mask size so that it is the same as the connection field's size, unless this parameter is False - in which case the user-specified size of the cf_shape is used. In normal usage of Topographica, this parameter should remain True.""") mask_threshold = param.Number(default=0.5, constant=True, doc=""" If a unit is above this value in the cf_shape mask, it is included; otherwise it is excluded from the mask.""") apply_output_fns_init = param.Boolean(default=True, doc=""" Whether to apply the output function to connection fields (e.g. for normalization) when the CFs are first created.""") min_matrix_radius = param.Integer(default=1, bounds=(0, None), doc=""" Enforced minimum for radius of weights matrix. The default of 1 gives a minimum matrix of 3x3. 0 would allow a 1x1 matrix.""") precedence = param.Number(default=0.8) def __init__(self, initialize_cfs=True, **params): """ Initialize the Projection with a set of cf_type objects (typically ConnectionFields), each located at the location in the source sheet corresponding to the unit in the target sheet. The cf_type objects are stored in the 'cfs' array. The nominal_bounds_template specified may be altered: the bounds must be fitted to the Sheet's matrix, and the weights matrix must have odd dimensions. These altered bounds are passed to the individual connection fields. A mask for the weights matrix is constructed. The shape is specified by cf_shape; the size defaults to the size of the nominal_bounds_template. """ super(CFProjection, self).__init__(**params) self.weights_generator.set_dynamic_time_fn(None, sublistattr='generators') # get the actual bounds_template by adjusting a copy of the # nominal_bounds_template to ensure an odd slice, and to be # cropped to sheet if necessary self._slice_template = Slice(copy(self.nominal_bounds_template), self.src, force_odd=True, min_matrix_radius=self.min_matrix_radius) self.bounds_template = self._slice_template.compute_bounds(self.src) self.mask_template = _create_mask(self.cf_shape, self.bounds_template, self.src, self.autosize_mask, self.mask_threshold) self.n_units = self._calc_n_units() if initialize_cfs: self._create_cfs() ### JCALERT! We might want to change the default value of the ### input value to self.src.activity; but it fails, raising a ### type error. It probably has to be clarified why this is ### happening self.input_buffer = None self.activity = array(self.dest.activity) def _generate_coords(self): X, Y = self.dest.sheetcoords_of_idx_grid() vectorized_coord_mapper = simple_vectorize( self.coord_mapper, num_outputs=2, # CB: could switch to float32? output_type=float) return vectorized_coord_mapper(X, Y) # CB: should be _initialize_cfs() since we already have 'initialize_cfs' flag? def _create_cfs(self): vectorized_create_cf = simple_vectorize(self._create_cf) #: self.cfs = vectorized_create_cf(*self._generate_coords()) self.flatcfs = list(self.cfs.flat) def _create_cf(self, x, y): """ Create a ConnectionField at x,y in the src sheet. """ # (to restore would need to have an r,c counter) # self.debug("Creating CF(%d,%d) from src (%.3f,%.3f) to dest (%.3f,%.3f)"%(r,c,x_cf,y_cf,x,y)) try: if self.apply_output_fns_init: ofs = [wof.single_cf_fn for wof in self.weights_output_fns] else: ofs = [] if self.same_cf_shape_for_all_cfs: mask_template = self.mask_template else: mask_template = _create_mask(self.cf_shape, self.bounds_template, self.src, self.autosize_mask, self.mask_threshold) CF = self.cf_type(self.src, x=x, y=y, template=self._slice_template, weights_generator=self.weights_generator, mask=mask_template, output_fns=ofs, min_matrix_radius=self.min_matrix_radius) except NullCFError: if self.allow_null_cfs: CF = None else: raise return CF def _calc_n_units(self): """Return the number of unmasked units in a typical ConnectionField.""" return min( len(self.mask_template.ravel().nonzero()[0]), # CEBALERT: if the mask_template is bigger than the # src sheet (e.g. conn radius bigger than src # radius), return the size of the source sheet self.src.shape[0] * self.src.shape[1]) def cf(self, r, c): """Return the specified ConnectionField""" # CB: should we offer convenience cf(x,y) (i.e. sheetcoords) method instead? self.warning( "CFProjection.cf(r,c) is deprecated: use cfs[r,c] instead") return self.cfs[r, c] def cf_bounds(self, r, c): """Return the bounds of the specified ConnectionField.""" return self.cfs[r, c].get_bounds(self.src) def get_view(self, sheet_x, sheet_y, timestamp): """ Return a single connection field UnitView, for the unit located nearest to sheet coordinate (sheet_x,sheet_y). """ matrix_data = zeros(self.src.activity.shape, Float) (r, c) = self.dest.sheet2matrixidx(sheet_x, sheet_y) r1, r2, c1, c2 = self.cfs[r, c].input_sheet_slice matrix_data[r1:r2, c1:c2] = self.cfs[r, c].weights # CB: the following would be equivalent with Slice __call__ # cf = self.cf(self.dest.sheet2matrixidx(sheet_x,sheet_y)) # matrix_data = numpy.zeros(self.src.activity.shape,Numeric.Float) # matrix_data[cf.input_sheet_slice()]=cf.weights return UnitView((matrix_data, self.src.bounds), sheet_x, sheet_y, self, timestamp) def activate(self, input_activity): """Activate using the specified response_fn and output_fn.""" self.input_buffer = input_activity self.activity *= 0.0 self.response_fn(MaskedCFIter(self), input_activity, self.activity, self.strength) for of in self.output_fns: of(self.activity) # CEBALERT: should add active_units_mask to match # apply_learn_output_fns. def learn(self): """ For a CFProjection, learn consists of calling the learning_fn. """ # Learning is performed if the input_buffer has already been set, # i.e. there is an input to the Projection. if self.input_buffer != None: self.learning_fn(MaskedCFIter(self), self.input_buffer, self.dest.activity, self.learning_rate) # CEBALERT: called 'learn' output fns here, but called 'weights' output fns # elsewhere (mostly). Change all to 'learn'? def apply_learn_output_fns(self, active_units_mask=True): """ Apply the weights_output_fns to each unit. If active_units_mask is True, inactive units will be skipped. """ for of in self.weights_output_fns: of(MaskedCFIter(self, active_units_mask=active_units_mask)) # CEBALERT: see gc alert in simulation.__new__ def _cleanup(self): for cf in self.cfs.flat: # cf could be None or maybe something else if hasattr(cf, 'input_sheet'): cf.input_sheet = None if hasattr(cf, 'input_sheet_slice'): cf.input_sheet_slice = None if hasattr(cf, 'weights_slice'): cf.weights_slice = None def n_bytes(self): # Could also count the input_sheet_slice rows, cols = self.cfs.shape return super(CFProjection,self).n_bytes() + \ sum([cf.weights.nbytes + cf.mask.nbytes for cf,i in CFIter(self,ignore_sheet_mask=True)()]) def n_conns(self): # Counts non-masked values, if mask is available; otherwise counts # weights as connections if nonzero rows, cols = self.cfs.shape return sum([ len((cf.mask if cf.mask is not None else cf.weights).ravel().nonzero()[0]) for cf, i in MaskedCFIter(self)() ])
def __init__(self, input_sheet, x=0.0, y=0.0, template=BoundingBox(radius=0.1), weights_generator=patterngenerator.Constant(), mask=patterngenerator.Constant(), output_fns=None, min_matrix_radius=1): """ Create weights at the specified (x,y) location on the specified input_sheet. The supplied template (if a BoundingRegion) is converted to a Slice, moved to the specified (x,y) location, and then the weights pattern is drawn inside by the weights_generator. Note that if the appropriate template Slice is already known, then it can be passed in instead of a BoundingRegion template. This slice will then be used directly, instead of converting the template into a Slice. The supplied template object itself will not be modified (it is copied before use). The mask allows the weights to be limited to being non-zero in a subset of the rectangular weights area. The actual mask used is a view of the given mask created by cropping to the boundaries of the input_sheet, so that the weights all correspond to actual locations in the input sheet. For instance, if a circular pattern of weights is desired, the mask should have a disk-shaped pattern of elements with value 1, surrounded by elements with the value 0. If the CF extends over the edge of the input sheet then the weights will actually be half-moon (or similar) rather than circular. """ #print "Create CF",input_sheet.name,x,y,"template=",template,"wg=",weights_generator,"m=",mask,"ofs=",output_fns,"min r=",min_matrix_radius template = copy(template) if not isinstance(template, Slice): template = Slice(template, input_sheet, force_odd=True, min_matrix_radius=min_matrix_radius) # Note: if passed in, mask is shared between CFs (but not if created here) if not hasattr(mask, 'view'): mask = _create_mask( mask, template.compute_bounds(input_sheet), # CEBALERT: it's not really worth adding more ALERTs on this # topic, but...there's no way for the CF to control autosize # and threshold. input_sheet, True, 0.5) # CB: has to be set for C code. Can't be initialized at the # class level, or it would become a read-only class attribute # (because it's a slot: # http://docs.python.org/reference/datamodel.html). Can we # somehow avoid having to think about _has_norm_total in the # python code? Could the C code initialize this value? self._has_norm_total = array([0], dtype=numpy.int32) self._norm_total = array([0.0], dtype=float) if output_fns is None: output_fns = [] # CEBALERT: now even more confusing; weights_slice is # different from input_sheet_slice. At least need to rename. weights_slice = self._create_input_sheet_slice(input_sheet, x, y, template, min_matrix_radius) # CBNOTE: this would be clearer (but not perfect, and probably slower) # m = mask_template[self.weights_slice()] self.mask = weights_slice.submatrix(mask) # view of original mask self.mask = array(self.mask, copy=1) # CEBALERT: why is this necessary? # (without it, optimized learning function creates artifacts in CFs at # left and right edges of sheet, at some densities) # CBENHANCEMENT: might want to do something about a size # that's specified (right now the size is assumed to be that # of the bounds) # shouldn't be extra computation of boundingbox because it's gone from Slice.__init__; could avoid extra lookups by getting straight from slice w = weights_generator(x=x, y=y, bounds=self.get_bounds(input_sheet), xdensity=input_sheet.xdensity, ydensity=input_sheet.ydensity, mask=self.mask) # CEBALERT: unnecessary copy! Pass type to PG & have it draw # in that. (Should be simple, except making it work for all # the PG subclasses that override array creation in various # ways (producing or using inconsistent types) turned out to # be too painful.) self.weights = w.astype(weight_type) # CEBHACKALERT: the system of masking through multiplication # by 0 works for now, while the output_fns are all # multiplicative. But in the long run we need a better way to # apply the mask. The same applies anywhere the mask is used, # including in learningfn/. We should investigate masked # arrays (from numpy). for of in output_fns: of(self.weights)
def compute_bounds(self, scs): spec = self._slicespec2boundsspec(self, scs) return BoundingBox(points=spec)