コード例 #1
0
class PatternGenerator(param.Parameterized):
    __abstract = True

    bounds = BoundingBox(points=((-0.5, -0.5), (0.5, 0.5)))
    xdensity = 256
    ydensity = 256
    x = 0.0
    y = 0.0
    z = None
    group = 'Pattern'
    position = param.Composite(attribs=['x', 'y'])
    orientation = 0.0
    size = 1.0
    scale = 1.0
    offset = 0.0
    output_fns = []

    def __init__(self, **params):
        super(PatternGenerator, self).__init__(**params)

    def __call__(self, **kwargs):
        pass

    def channels(self, **params):
        pass

    def num_channels(self):
        return 1

    def function(self, p):
        pass
コード例 #2
0
ファイル: trace.py プロジェクト: sf-issues/topographica
class SheetPositionTrace(Trace):
    """
    A trace that assumes that the data are sheet activity matrices,
    and traces the value of a given (x,y) position on the sheet.
    """

    x = param.Number(default=0.0,
                     doc="""
        The x sheet-coordinate of the position to be traced.""")

    y = param.Number(default=0.0,
                     doc="""
        The y sheet-coordinate of the position to be traced.""")

    position = param.Composite(attribs=['x', 'y'],
                               doc="""
        The sheet coordinates of the position to be traced.""")

    # JPALERT:  Would be nice to some way to set up the coordinate system
    # automatically.  The DataRecorder object already knows what Sheet
    # the data came from.
    coordframe = param.Parameter(default=None,
                                 doc="""
        The SheetCoordinateSystem to use to convert the position
        into matrix coordinates.""")

    def __call__(self, data):
        r, c = self.coordframe.sheet2matrixidx(self.x, self.y)
        return [d[r, c] for d in data]
コード例 #3
0
 class _BigDumbParams(param.Parameterized):
     action = param.Action(default_action, allow_None=True)
     array = param.Array(np.array([1.0, 2.0]))
     boolean = param.Boolean(True, allow_None=True)
     callable = param.Callable(default_action, allow_None=True)
     class_selector = param.ClassSelector(int, is_instance=False, allow_None=True)
     color = param.Color("#FFFFFF", allow_None=True)
     composite = param.Composite(["action", "array"], allow_None=True)
     try:
         data_frame = param.DataFrame(
             pd.DataFrame({"A": 1.0, "B": np.arange(5)}), allow_None=True
         )
     except TypeError:
         data_frame = param.DataFrame(pd.DataFrame({"A": 1.0, "B": np.arange(5)}))
     date = param.Date(datetime.now(), allow_None=True)
     date_range = param.DateRange((datetime.min, datetime.max), allow_None=True)
     dict_ = param.Dict({"foo": "bar"}, allow_None=True, doc="dict means dictionary")
     dynamic = param.Dynamic(default=default_action, allow_None=True)
     file_selector = param.FileSelector(
         os.path.join(FILE_DIR_DIR, "LICENSE"),
         path=os.path.join(FILE_DIR_DIR, "*"),
         allow_None=True,
     )
     filename = param.Filename(
         os.path.join(FILE_DIR_DIR, "LICENSE"), allow_None=True
     )
     foldername = param.Foldername(os.path.join(FILE_DIR_DIR), allow_None=True)
     hook_list = param.HookList(
         [CallableObject(), CallableObject()], class_=CallableObject, allow_None=True
     )
     integer = param.Integer(10, allow_None=True)
     list_ = param.List([1, 2, 3], allow_None=True, class_=int)
     list_selector = param.ListSelector([2, 2], objects=[1, 2, 3], allow_None=True)
     magnitude = param.Magnitude(0.5, allow_None=True)
     multi_file_selector = param.MultiFileSelector(
         [],
         path=os.path.join(FILE_DIR_DIR, "*"),
         allow_None=True,
         check_on_set=True,
     )
     number = param.Number(-10.0, allow_None=True, doc="here is a number")
     numeric_tuple = param.NumericTuple((5.0, 10.0), allow_None=True)
     object_selector = param.ObjectSelector(
         False, objects={"False": False, "True": 1}, allow_None=True
     )
     path = param.Path(os.path.join(FILE_DIR_DIR, "LICENSE"), allow_None=True)
     range_ = param.Range((-1.0, 2.0), allow_None=True)
     series = param.Series(pd.Series(range(5)), allow_None=True)
     string = param.String("foo", allow_None=True, doc="this is a string")
     tuple_ = param.Tuple((3, 4, "fi"), allow_None=True)
     x_y_coordinates = param.XYCoordinates((1.0, 2.0), allow_None=True)
コード例 #4
0
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
コード例 #5
0
 class A(param.Parameterized):
     x = param.Number(default=0)
     y = param.Number(default=0)
     xy = param.Composite(attribs=['x','y'])
コード例 #6
0
class MontageBitmap(Bitmap):
    """
    A bitmap composed of tiles containing other bitmaps.

    Bitmaps are scaled to fit in the given tile size, and tiled
    right-to-left, top-to-bottom into the given number of rows and columns.
    """
    bitmaps = param.List(class_=Bitmap,
                         doc="""
        The list of bitmaps to compose.""")

    rows = param.Integer(default=2,
                         doc="""
        The number of rows in the montage.""")
    cols = param.Integer(default=2,
                         doc="""
        The number of columns in the montage.""")
    shape = param.Composite(attribs=['rows', 'cols'],
                            doc="""
        The shape of the montage. Same as (self.rows,self.cols).""")

    margin = param.Integer(default=5,
                           doc="""
        The size in pixels of the margin to put around each
        tile in the montage.""")

    tile_size = param.NumericTuple(default=(100, 100),
                                   doc="""
        The size in pixels of a tile in the montage.""")

    titles = param.List(class_=str,
                        default=[],
                        doc="""
        A list of titles to overlay on the tiles.""")

    title_pos = param.NumericTuple(default=(10, 10),
                                   doc="""
        The position of the upper left corner of the title in each tile.""")

    title_options = param.Dict(default={},
                               doc="""
        Dictionary of options for drawing the titles.  Dict should
        contain keyword options for the PIL draw.text method.  Possible
        options include 'fill' (fill color), 'outline' (outline color),
        and 'font' (an ImageFont font instance).  The PIL defaults will
        be used for any omitted options.""",
                               instantiate=False)

    hooks = param.List(default=[],
                       doc="""
        A list of functions, one per tile, that take a PIL image as
        input and return a PIL image as output.  The hooks are applied
        to the tile images before resizing.  The value None can be
        inserted as a placeholder where no hook function is needed.""")

    resize_filter = param.Integer(default=Image.NEAREST,
                                  doc="""
       The filter used for resizing the images.  Defaults
       to NEAREST.  See PIL Image module documentation for other
       options and their meanings.""")

    bg_color = param.NumericTuple(default=(0, 0, 0),
                                  doc="""
       The background color for the montage, as (r,g,b).""")

    def __init__(self, **params):
        ## JPALERT: The Bitmap class is a Parameterized object,but its
        ## __init__ doesn't take **params and doesn't call super.__init__,
        ## so we have to skip it.
        ## JAB: Good point; Bitmap should be modified to be more like
        ## other PO classes.
        param.Parameterized.__init__(self, **params)

        rows, cols = self.shape
        tilew, tileh = self.tile_size
        bgr, bgg, bgb = self.bg_color

        width = tilew * cols + self.margin * (cols * 2)
        height = tileh * rows + self.margin * (rows * 2)
        self.image = Image.new('RGB', (width, height),
                               (bgr * 255, bgg * 255, bgb * 255))

        self.title_options.setdefault('font', TITLE_FONT)

        for r in xrange(rows):
            for c in xrange(cols):
                i = r * self.cols + c
                if i < len(self.bitmaps):
                    bm = self.bitmaps[i]
                    bmw, bmh = bm.image.size
                    if bmw > bmh:
                        bmh = int(float(tilew) / bmw * bmh)
                        bmw = tilew
                    else:
                        bmw = int(float(tileh) / bmh * bmw)
                        bmh = tileh

                    if self.hooks and self.hooks[i]:
                        f = self.hooks[i]
                    else:
                        f = lambda x: x
                    new_bm = Bitmap(f(bm.image).resize((bmw, bmh)))
                    if self.titles:
                        draw = ImageDraw.Draw(new_bm.image)
                        draw.text(self.title_pos, self.titles[i],
                                  **self.title_options)
                    self.image.paste(new_bm.image,
                                     (c * width / cols + tilew / 2 - bmw / 2 +
                                      self.margin, r * height / rows +
                                      tileh / 2 - bmh / 2 + self.margin))

                else:
                    break
コード例 #7
0
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=256,
                            bounds=(0, None),
                            precedence=-1,
                            doc="""
        Density (number of samples per 1.0 length) in the x direction.""")

    ydensity = param.Number(default=256,
                            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.""")

    z = param.ClassSelector(default=None,
                            precedence=-1,
                            class_=Dimension,
                            doc="""
        The Dimension object associated with the z-values generated by
        the PatternGenerator . If None, uses the default set by
        HoloViews.Image.""")

    group = param.String(default='Pattern',
                         precedence=-1,
                         doc="""
       The group name assigned to the returned HoloViews object.""")

    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=[],
                                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.
        """
        if 'output_fns' in params_to_override:
            self.warning(
                "Output functions specified through the call method will be ignored."
            )

        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)
        if p.scale != 1.0:
            result = p.scale * fn_result
        else:
            result = fn_result
        if p.offset != 0.0:
            result += p.offset

        for of in p.output_fns:
            of(result)

        return result

    def __getitem__(self, coords):
        value_dims = {}
        if self.num_channels() in [0, 1]:
            raster, data = Image, self()
            value_dims = {
                'value_dimensions': [self.z]
            } if self.z else value_dims
        elif self.num_channels() in [3, 4]:
            raster = RGB
            data = np.dstack(self.channels().values()[1:])

        image = raster(data,
                       bounds=self.bounds,
                       **dict(group=self.group,
                              label=self.__class__.__name__,
                              **value_dims))
        # Works round a bug fixed shortly after HoloViews 1.0.0 release
        return image if isinstance(coords,
                                   slice) else image.__getitem__(coords)

    def channels(self, use_cached=False, **params_to_override):
        """
        Channels() adds a shared interface for single channel and
        multichannel structures.  It will always return an ordered
        dict: its first element is the single channel of the pattern
        (if single-channel) or the channel average (if multichannel);
        the successive elements are the individual channels' arrays
        (key: 0,1,..N-1).
        """
        return collections.OrderedDict(
            {'default': self.__call__(**params_to_override)})

    def num_channels(self):
        """
        Query the number of channels implemented by the
        PatternGenerator. In case of single-channel generators this
        will return 1; in case of multichannel, it will return the
        number of channels (eg, in the case of RGB images it would
        return '3', Red-Green-Blue, even though the OrderedDict
        returned by channels() will have 4 elements -- the 3 channels
        + their average).
        """
        return 1

    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(
            "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 = np.subtract.outer(
            np.cos(orientation) * y,
            np.sin(orientation) * x)
        pattern_x = np.add.outer(
            np.sin(orientation) * y,
            np.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 * np.cos(p.orientation) - ms.y * np.sin(p.orientation)),
                y=p.y + p.size *
                (ms.x * np.sin(p.orientation) + ms.y * np.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
        scs = SheetCoordinateSystem(bounds, xdensity, ydensity)
        for of in self.output_fns:
            if isinstance(of, TransferFn):
                of.initialize(SCS=scs, shape=scs.shape)

    def state_push(self):
        "Save the state of the output functions, to be restored with state_pop."
        for of in self.output_fns:
            if hasattr(of, 'state_push'):
                of.state_push()
        super(PatternGenerator, self).state_push()

    def state_pop(self):
        "Restore the state of the output functions saved by state_push."
        for of in self.output_fns:
            if hasattr(of, 'state_pop'):
                of.state_pop()
        super(PatternGenerator, self).state_pop()

    def anim(self,
             duration,
             offset=0,
             timestep=1,
             label=None,
             unit=None,
             time_fn=param.Dynamic.time_fn):
        """
        duration: The temporal duration to animate in the units
        defined on the global time function.

        offset: The temporal offset from which the animation is
        generated given the supplied pattern

        timestep: The time interval between successive frames. The
        duration must be an exact multiple of the timestep.

        label: A label string to override the label of the global time
        function (if not None).

        unit: The unit string to override the unit value of the global
        time function (if not None).

        time_fn: The global time function object that is shared across
        the time-varying objects that are being sampled.

        Note that the offset, timestep and time_fn only affect
        patterns parameterized by time-dependent number
        generators. Otherwise, the frames are generated by successive
        call to the pattern which may or may not be varying (e.g to
        view the patterns contained within a Selector).
        """
        frames = (duration // timestep) + 1
        if duration % timestep != 0:
            raise ValueError(
                "The duration value must be an exact multiple of the timestep."
            )

        if label is None:
            label = time_fn.label if hasattr(time_fn, 'label') else 'Time'

        unit = time_fn.unit if (not unit
                                and hasattr(time_fn, 'unit')) else unit
        vmap = HoloMap(
            key_dimensions=[Dimension(label, unit=unit if unit else '')])

        self.state_push()
        with time_fn as t:
            t(offset)
            for i in range(frames):
                vmap[t()] = self[:]
                t += timestep
        self.state_pop()
        return vmap

    ## Support for compositional expressions of PatternGenerator objects
    def _promote(self, other):
        if not isinstance(other, PatternGenerator):
            other = Constant(scale=other, offset=0)
        return [self, other]

    def _rpromote(self, other):
        if not isinstance(other, PatternGenerator):
            other = Constant(scale=other, offset=0)
        return [other, self]

    # Could define any of Python's operators here, esp. if they have operator or ufunc equivalents
    def __add__(self, other):
        return Composite(generators=self._promote(other), operator=np.add)

    def __sub__(self, other):
        return Composite(generators=self._promote(other), operator=np.subtract)

    def __mul__(self, other):
        return Composite(generators=self._promote(other), operator=np.multiply)

    def __mod__(self, other):
        return Composite(generators=self._promote(other), operator=np.mod)

    def __pow__(self, other):
        return Composite(generators=self._promote(other), operator=np.power)

    def __div__(self, other):
        return Composite(generators=self._promote(other), operator=np.divide)

    def __and__(self, other):
        return Composite(generators=self._promote(other), operator=np.minimum)

    def __or__(self, other):
        return Composite(generators=self._promote(other), operator=np.maximum)

    def __radd__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.add)

    def __rsub__(self, other):
        return Composite(generators=self._rpromote(other),
                         operator=np.subtract)

    def __rmul__(self, other):
        return Composite(generators=self._rpromote(other),
                         operator=np.multiply)

    def __rmod__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.mod)

    def __rpow__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.power)

    def __rdiv__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.divide)

    def __rand__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.minimum)

    def __ror__(self, other):
        return Composite(generators=self._rpromote(other), operator=np.maximum)

    def __neg__(self):
        return Composite(generators=[Constant(scale=0), self],
                         operator=np.subtract)

    class abs_first(object):
        @staticmethod
        def reduce(x):
            return np.abs(x[0])

    def __abs__(self):
        return Composite(generators=[self], operator=self.abs_first)

    def pil(self, **params_to_override):
        """Returns a PIL image for this pattern, overriding parameters if provided."""
        from PIL.Image import fromarray
        nchans = self.num_channels()

        if nchans in [0, 1]:
            mode, arr = None, self(**params_to_override)
            arr = (255.0 / arr.max() * (arr - arr.min())).astype(np.uint8)

        elif nchans in [3, 4]:
            mode = 'RGB' if nchans == 3 else 'RGBA'
            arr = np.dstack(self.channels(**params_to_override).values()[1:])
            arr = (255.0 * arr).astype(np.uint8)

        else:
            raise ValueError("Unsupported number of channels")

        return fromarray(arr, mode)