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
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]
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)
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 A(param.Parameterized): x = param.Number(default=0) y = param.Number(default=0) xy = param.Composite(attribs=['x','y'])
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
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)