# -*- Mode: Python; py-indent-offset: 4 -*- # vim: tabstop=4 shiftwidth=4 expandtab from __future__ import division # overrides for pygobject gobject-introspection binding for libvips, tested # with python2.7 and python3.4 # copy this file to dist-packages/gi/overrides, eg. # # sudo cp Vips.py /usr/lib/python2.7/dist-packages/gi/overrides # sudo cp Vips.py /usr/lib/python3/dist-packages/gi/overrides # # Alternatively, build vips to another prefix, then copy Vips.py and Vips.pyc # from $prefix/lib/python2.7/dist-packages/gi/overrides to /usr # This file is part of VIPS. # # VIPS is free software; you can redistribute it and/or modify it under the # terms of the GNU Lesser General Public License as published by the Free # Software Foundation; either version 2 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for # more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # # These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk import sys import re import logging import numbers logger = logging.getLogger(__name__) from gi.repository import GObject from ..overrides import override from ..module import get_introspection_module Vips = get_introspection_module('Vips') __all__ = [] # start up vips! # passing argv[0] helps vips find its data files on some platforms Vips.init(sys.argv[0]) # need the gtypes for various vips types vips_type_array_int = GObject.GType.from_name("VipsArrayInt") vips_type_array_double = GObject.GType.from_name("VipsArrayDouble") vips_type_array_image = GObject.GType.from_name("VipsArrayImage") vips_type_blob = GObject.GType.from_name("VipsBlob") vips_type_image = GObject.GType.from_name("VipsImage") vips_type_operation = GObject.GType.from_name("VipsOperation") vips_type_ref_string = GObject.GType.from_name("VipsRefString") # 8.4 and earlier had a bug which swapped the order of const args to enum # operations swap_const_args = Vips.version(0) < 8 or (Vips.version(0) == 8 and Vips.version(1) <= 4) def is_2D(value): if not isinstance(value, list): return False for x in value: if not isinstance(x, list): return False if len(x) != len(value[0]): return False return True def imageize(match_image, value): logger.debug('imageize match_image=%s, value=%s' % (match_image, value)) # 2D arrays become array images if is_2D(value): return Vips.Image.new_from_array(value) # if there's nothing to match to, also make an array if match_image is None: return Vips.Image.new_from_array(value) # assume this is a pixel constant ... expand into an image using # match as a template return match_image.new_from_image(value) # we'd like to use memoryview to avoid copying things like ICC profiles, but # unfortunately pygobject does not support this ... so for blobs we just use # bytes(). unpack_types = [[Vips.Blob, lambda x: bytes(x.get())], [Vips.RefString, lambda x: x.get()], [Vips.ArrayDouble, lambda x: x.get()], [Vips.ArrayImage, lambda x: x.get()], [Vips.ArrayInt, lambda x: x.get()]] def unpack(value): for t, cast in unpack_types: if isinstance(value, t): return cast(value) return value def array_image_new(array): match_image = next((x for x in array if isinstance(x, Vips.Image)), None) if match_image is None: raise Error('Unable to make image array argument.', 'Array must contain at least one image.') for i in range(0, len(array)): if not isinstance(array[i], Vips.Image): array[i] = imageize(match_image, array[i]) return Vips.ArrayImage.new(array) arrayize_types = [[vips_type_array_int, Vips.ArrayInt.new], [vips_type_array_double, Vips.ArrayDouble.new], [vips_type_array_image, array_image_new]] def arrayize(gtype, value): for t, cast in arrayize_types: if GObject.type_is_a(gtype, t): if not isinstance(value, list): value = [value] return cast(value) return value def run_cmplx(fn, image): """Run a complex function on a non-complex image. The image needs to be complex, or have an even number of bands. The input can be int, the output is always float or double. """ original_format = image.format if not Vips.band_format_iscomplex(image.format): if image.bands % 2 != 0: raise "not an even number of bands" if not Vips.band_format_isfloat(image.format): image = image.cast(Vips.BandFormat.FLOAT) if image.format == Vips.BandFormat.DOUBLE: new_format = Vips.BandFormat.DPCOMPLEX else: new_format = Vips.BandFormat.COMPLEX image = image.copy(format = new_format, bands = image.bands / 2) image = fn(image) if not Vips.band_format_iscomplex(original_format): if image.format == Vips.BandFormat.DPCOMPLEX: new_format = Vips.BandFormat.DOUBLE else: new_format = Vips.BandFormat.FLOAT image = image.copy(format = new_format, bands = image.bands * 2) return image class Error(Exception): """An error from vips. message -- a high-level description of the error detail -- a string with some detailed diagnostics """ def __init__(self, message, detail = None): self.message = message if detail == None or detail == "": detail = Vips.error_buffer() Vips.error_clear() self.detail = detail logger.debug('Error %s %s', self.message, self.detail) def __str__(self): return '%s\n %s' % (self.message, self.detail) Vips.Error = Error class Argument(object): def __init__(self, op, prop): self.op = op self.prop = prop self.name = re.sub("-", "_", prop.name) self.flags = op.get_argument_flags(self.name) self.priority = op.get_argument_priority(self.name) self.isset = op.argument_isset(self.name) def set_value(self, match_image, value): logger.debug('assigning %s to %s' % (value, self.name)) logger.debug('%s needs a %s' % (self.name, self.prop.value_type)) # blob-ize if GObject.type_is_a(self.prop.value_type, vips_type_blob): if not isinstance(value, Vips.Blob): value = Vips.Blob.new(None, value) # image-ize if GObject.type_is_a(self.prop.value_type, vips_type_image): if not isinstance(value, Vips.Image): value = imageize(match_image, value) # array-ize some types, if necessary value = arrayize(self.prop.value_type, value) # MODIFY input images need to be copied before assigning them if self.flags & Vips.ArgumentFlags.MODIFY: # don't use .copy(): we want to make a new pipeline with no # reference back to the old stuff ... this way we can free the # previous image earlier logger.debug('MODIFY argument: copying image') new_image = Vips.Image.new_memory() value.write(new_image) value = new_image logger.debug('assigning %s' % value) self.op.props.__setattr__(self.name, value) def get_value(self): value = self.op.props.__getattribute__(self.name) logger.debug('read out %s from %s' % (value, self.name)) return unpack(value) def description(self): result = self.name result += " " * (10 - len(self.name)) + " -- " + self.prop.blurb result += ", " + self.prop.value_type.name return result Vips.Argument = Argument class Operation(Vips.Operation): # find all the args for this op, sort into priority order # we leave deprecated args in this list: for compatibility, we want users # to be able to set them # if you are (for example) generating docs, you'll need to filter out the # deprecated args yourself def get_args(self): args = [Argument(self, x) for x in self.props] args.sort(key = lambda x: x.priority) return args Operation = override(Operation) __all__.append('Operation') # search a list recursively for a Vips.Image object def find_image(x): if isinstance(x, Vips.Image): return x if isinstance(x, list): for i in x: y = find_image(i) if y is not None: return y return None def _call_base(name, required, optional, self = None, option_string = None): logger.debug('_call_base name=%s, required=%s optional=%s' % (name, required, optional)) if self: logger.debug('_call_base self=%s' % self) if option_string: logger.debug('_call_base option_string = %s' % option_string) try: op = Vips.Operation.new(name) except TypeError as e: raise Error('No such operator.') if op.get_flags() & Vips.OperationFlags.DEPRECATED: raise Error('No such operator.', 'operator "%s" is deprecated' % name) # set str options first so the user can't override things we set # deliberately and break stuff if option_string: if op.set_from_string(option_string) != 0: raise Error('Bad arguments.') args = op.get_args() enm = Vips.ArgumentFlags # find all required, unassigned, undeprecated input args required_input = [x for x in args if x.flags & enm.INPUT and x.flags & enm.REQUIRED and not x.flags & enm.DEPRECATED and not x.isset] # do we have a non-None self pointer? this is used to set the first # compatible input arg if self is not None: found = False for x in required_input: if GObject.type_is_a(self, x.prop.value_type): x.set_value(None, self) required_input.remove(x) found = True break if not found: raise Error('Bad arguments.', 'No %s argument to %s.' % (str(self.__class__), name)) if len(required_input) != len(required): raise Error('Wrong number of arguments.', '%s needs %d arguments, you supplied %d.' % (name, len(required_input), len(required))) # if we need an image arg but the user supplied a number or list of # numbers, we expand it into an image automatically ... the number is # expanded to match self, or if that's None, the first image we can find in # the required or optional arguments match_image = self if match_image is None: for arg in required: match_image = find_image(arg) if match_image is not None: break if match_image is None: for arg_name in optional: match_image = find_image(optional[arg_name]) if match_image is not None: break for i in range(len(required_input)): required_input[i].set_value(match_image, required[i]) # find all optional, unassigned input args ... make a hash from name to # Argument # we let deprecated ones through, we want to allow assigment to them for # compat optional_input = {x.name: x for x in args if x.flags & enm.INPUT and not x.flags & enm.REQUIRED and not x.isset} # find all optional output args ... we use "x = True" # in args to mean add that to output optional_output = {x.name: x for x in args if x.flags & enm.OUTPUT and not x.flags & enm.REQUIRED} # set optional input args for key in list(optional.keys()): if key in optional_input: optional_input[key].set_value(match_image, optional[key]) elif key in optional_output: # must be a literal True value if optional[key] is not True: raise Error('Optional output argument must be True.', 'Argument %s should equal True.' % key) else: raise Error('Unknown argument.', 'Operator %s has no argument %s.' % (name, key)) # call logger.debug('_call_base checking cache for op %s' % op) op2 = Vips.cache_operation_build(op) logger.debug('_call_base got op2 %s' % op2) if op2 == None: raise Error('Error calling operator %s.' % name) # rescan args if op2 is different from op if op2 != op: logger.debug('_call_base rescanning args') args = op2.get_args() optional_output = {x.name: x for x in args if x.flags & enm.OUTPUT and not x.flags & enm.REQUIRED} # gather output args logger.debug('_call_base fetching required output args') out = [] for x in args: # required non-deprecated output arg if x.flags & enm.OUTPUT and x.flags & enm.REQUIRED and not x.flags & enm.DEPRECATED: out.append(x.get_value()) # modified input arg ... this will get the memory image we made above if x.flags & enm.INPUT and x.flags & enm.MODIFY: out.append(x.get_value()) logger.debug('_call_base fetching optional output args') out_dict = {} for x in list(optional.keys()): if x in optional_output: out_dict[x] = optional_output[x].get_value() if out_dict != {}: out.append(out_dict) if len(out) == 1: out = out[0] elif len(out) == 0: out = None # unref everything now we have refs to all outputs we want op2.unref_outputs() logger.debug('success') return out # handy for expanding enums def _call_enum(self, name, enum, other): if isinstance(other, Vips.Image): return _call_base(name, [other, enum], {}, self) elif swap_const_args: return _call_base(name + "_const", [other, enum], {}, self) else: return _call_base(name + "_const", [enum, other], {}, self) # for equality style operations, we need to allow comparison with None def _call_enum_eq(self, name, enum, other): if isinstance(other, Vips.Image): return _call_base(name, [other, enum], {}, self) elif isinstance(other, list) or isinstance(other, numbers.Number): if swap_const_args: return _call_base(name + "_const", [other, enum], {}, self) else: return _call_base(name + "_const", [enum, other], {}, self) else: return False # general user entrypoint def call(name, *args, **kwargs): return _call_base(name, args, kwargs) Vips.call = call # here from getattr ... try to run the attr as a method def _call_instance(self, name, args, kwargs): return _call_base(name, args, kwargs, self) @classmethod def vips_image_new_from_file(cls, vips_filename, **kwargs): """Create a new Image from a filename. Extra optional arguments depend on the loader selected by libvips. See each loader for details. """ filename = Vips.filename_get_filename(vips_filename) option_string = Vips.filename_get_options(vips_filename) loader = Vips.Foreign.find_load(filename) if loader == None: raise Error('No known loader for "%s".' % filename) logger.debug('Image.new_from_file: loader = %s' % loader) return _call_base(loader, [filename], kwargs, None, option_string) setattr(Vips.Image, 'new_from_file', vips_image_new_from_file) @classmethod def vips_image_new_from_buffer(cls, data, option_string, **kwargs): """Create a new Image from binary data in a string. data -- binary image data option_string -- optional arguments in string form option_string can be something like "page=10" to load the 10th page of a tiff file. You can also give load options as keyword arguments. """ loader = Vips.Foreign.find_load_buffer(data) if loader == None: raise Error('No known loader for buffer.') logger.debug('Image.new_from_buffer: loader = %s' % loader) return _call_base(loader, [data], kwargs, None, option_string) setattr(Vips.Image, 'new_from_buffer', vips_image_new_from_buffer) @classmethod def vips_image_new_from_array(cls, array, scale = 1, offset = 0): """Create a new image from an array. The array argument can be a 1D array to create a height == 1 image, or a 2D array to make a 2D image. Use scale and offset to set the scale factor, handy for integer convolutions. """ # we accept a 1D array and assume height == 1, or a 2D array and check all # lines are the same length if not isinstance(array, list): raise TypeError('new_from_array() takes a list argument') if not isinstance(array[0], list): height = 1 width = len(array) else: # must copy the first row, we don't want to modify the passed-in array flat_array = list(array[0]) height = len(array) width = len(array[0]) for i in range(1, height): if len(array[i]) != width: raise TypeError('new_from_array() array not rectangular') flat_array += array[i] array = flat_array image = cls.new_matrix_from_array(width, height, array) # be careful to set them as double image.set('scale', float(scale)) image.set('offset', float(offset)) return image setattr(Vips.Image, 'new_from_array', vips_image_new_from_array) def generate_docstring(name): try: op = Vips.Operation.new(name) except TypeError as e: raise Error('No such operator.') if op.get_flags() & Vips.OperationFlags.DEPRECATED: raise Error('No such operator.', 'operator "%s" is deprecated' % name) # find all the args for this op, sort into priority order args = op.get_args() # we are only interested in non-deprecated args args = [y for y in args if not y.flags & Vips.ArgumentFlags.DEPRECATED] enm = Vips.ArgumentFlags # find all required, unassigned input args required_input = [x for x in args if x.flags & enm.INPUT and x.flags & enm.REQUIRED and not x.isset] optional_input = [x for x in args if x.flags & enm.INPUT and not x.flags & enm.REQUIRED and not x.isset] required_output = [x for x in args if x.flags & enm.OUTPUT and x.flags & enm.REQUIRED] optional_output = [x for x in args if x.flags & enm.OUTPUT and not x.flags & enm.REQUIRED] # find the first required input image, if any ... we will be a member # function of this instance member_x = None for i in range(0, len(required_input)): x = required_input[i] if GObject.type_is_a(vips_type_image, x.prop.value_type): member_x = x break description = op.get_description() result = description[0].upper() + description[1:] + ".\n\n" result += "Usage:\n" result += " " + ", ".join([x.name for x in required_output]) + " = " if member_x: result += member_x.name + "." + name + "(" else: result += "Vips.Image." + name + "(" required_input_args = [x.name for x in required_input if x != member_x] result += ", ".join(required_input_args) if len(optional_input) > 0 and len(required_input_args) > 0: result += ", " result += ", ".join([x.name + " = " + x.prop.value_type.name for x in optional_input]) result += ")\n" result += "Where:\n" for x in required_output: result += " " + x.description() + "\n" for x in required_input: result += " " + x.description() + "\n" if len(optional_input) > 0: result += "Keyword parameters:\n" for x in optional_input: result += " " + x.description() + "\n" if len(optional_output) > 0: result += "Extra output options:\n" for x in optional_output: result += " " + x.description() + "\n" return result # apply a function to a thing, or map over a list # we often need to do something like (1.0 / other) and need to work for lists # as well as scalars def smap(func, x): if isinstance(x, list): return list(map(func, x)) else: return func(x) # decorator to set docstring def add_doc(value): def _doc(func): func.__doc__ = value return func return _doc class Image(Vips.Image): # for constructors, see class methods above def new_from_image(self, value): """Create a new image based on an existing one. A new image is created with the same width, height, format, interpretation, resolution and offset as self, but with every pixel having the value of value. You can pass an array to create a many-band image. """ # we'd like to call the vips function vips_image_new_from_image() but we # can't call __getattr__ methods from a subclass pixel = (Vips.Image.black(1, 1) + value).cast(self.format) image = pixel.embed(0, 0, self.width, self.height, extend = Vips.Extend.COPY) image = image.copy(interpretation = self.interpretation, xres = self.xres, yres = self.yres, xoffset = self.xoffset, yoffset = self.yoffset) return image # output def write_to_file(self, vips_filename, **kwargs): """Write an Image to a file. The filename can contain save options, for example "fred.tif[compression=jpeg]", or save options can be given as keyword arguments. Save options depend on the selected saver. """ filename = Vips.filename_get_filename(vips_filename) option_string = Vips.filename_get_options(vips_filename) saver = Vips.Foreign.find_save(filename) if saver == None: raise Error('No known saver for "%s".' % filename) logger.debug('Image.write_to_file: saver = %s' % saver) _call_base(saver, [filename], kwargs, self, option_string) def write_to_buffer(self, format_string, **kwargs): """Write an Image to memory. Return the image as a binary string, encoded in the selected format. Save options can be given in the format_string, for example ".jpg[Q=90]". Save options depend on the selected saver. """ filename = Vips.filename_get_filename(format_string) option_string = Vips.filename_get_options(format_string) saver = Vips.Foreign.find_save_buffer(filename) if saver == None: raise Error('No known saver for "%s".' % filename) logger.debug('Image.write_to_buffer: saver = %s' % saver) return _call_base(saver, [], kwargs, self, option_string) # we can use Vips.Image.write_to_memory() directly # support with in the most trivial way def __enter__(self): return self def __exit__(self, type, value, traceback): pass # operator overloads def __getattr__(self, name): logger.debug('Image.__getattr__ %s' % name) # look up in props first, eg. x.props.width if name in dir(self.props): return getattr(self.props, name) @add_doc(generate_docstring(name)) def call_function(*args, **kwargs): return _call_instance(self, name, args, kwargs) return call_function def __add__(self, other): if isinstance(other, Vips.Image): return self.add(other) else: return self.linear(1, other) def __radd__(self, other): return self.__add__(other) def __sub__(self, other): if isinstance(other, Vips.Image): return self.subtract(other) else: return self.linear(1, smap(lambda x: -1 * x, other)) def __rsub__(self, other): return self.linear(-1, other) def __mul__(self, other): if isinstance(other, Vips.Image): return self.multiply(other) else: return self.linear(other, 0) def __rmul__(self, other): return self.__mul__(other) # a / const has always been a float in vips, so div and truediv are the # same def __div__(self, other): if isinstance(other, Vips.Image): return self.divide(other) else: return self.linear(smap(lambda x: 1.0 / x, other), 0) def __rdiv__(self, other): return (self ** -1) * other def __truediv__(self, other): return self.__div__(other) def __rtruediv__(self, other): return self.__rdiv__(other) def __floordiv__(self, other): if isinstance(other, Vips.Image): return self.divide(other).floor() else: return self.linear(smap(lambda x: 1.0 / x, other), 0).floor() def __rfloordiv__(self, other): return ((self ** -1) * other).floor() def __mod__(self, other): if isinstance(other, Vips.Image): return self.remainder(other) else: return self.remainder_const(other) def __pow__(self, other): return _call_enum(self, "math2", Vips.OperationMath2.POW, other) def __rpow__(self, other): return _call_enum(self, "math2", Vips.OperationMath2.WOP, other) def __abs__(self): return self.abs() def __lshift__(self, other): return _call_enum(self, "boolean", Vips.OperationBoolean.LSHIFT, other) def __rshift__(self, other): return _call_enum(self, "boolean", Vips.OperationBoolean.RSHIFT, other) def __and__(self, other): return _call_enum(self, "boolean", Vips.OperationBoolean.AND, other) def __rand__(self, other): return self.__and__(other) def __or__(self, other): return _call_enum(self, "boolean", Vips.OperationBoolean.OR, other) def __ror__(self, other): return self.__or__(other) def __xor__(self, other): return _call_enum(self, "boolean", Vips.OperationBoolean.EOR, other) def __rxor__(self, other): return self.__xor__(other) def __neg__(self): return -1 * self def __pos__(self): return self def __invert__(self): return self ^ -1 def __gt__(self, other): return _call_enum(self, "relational", Vips.OperationRelational.MORE, other) def __ge__(self, other): return _call_enum(self, "relational", Vips.OperationRelational.MOREEQ, other) def __lt__(self, other): return _call_enum(self, "relational", Vips.OperationRelational.LESS, other) def __le__(self, other): return _call_enum(self, "relational", Vips.OperationRelational.LESSEQ, other) def __eq__(self, other): # _eq version allows comparison to None return _call_enum_eq(self, "relational", Vips.OperationRelational.EQUAL, other) def __ne__(self, other): # _eq version allows comparison to None return _call_enum_eq(self, "relational", Vips.OperationRelational.NOTEQ, other) def __getitem__(self, arg): if isinstance(arg, slice): i = 0 if arg.start != None: i = arg.start n = self.bands - i if arg.stop != None: if arg.stop < 0: n = self.bands + arg.stop - i else: n = arg.stop - i elif isinstance(arg, int): i = arg n = 1 else: raise TypeError if i < 0: i = self.bands + i if i < 0 or i >= self.bands: raise IndexError return self.extract_band(i, n = n) def __call__(self, x, y): return self.getpoint(x, y) # the cast operators int(), long() and float() must return numeric types, # so we can't define them for images # a few useful things def get_value(self, field): """Get a named item from an Image. Fetch an item of metadata and convert it to a Python-friendly format. For example, VipsBlob values will be converted to bytes(). """ value = self.get(field) logger.debug('read out %s from %s' % (value, self)) return unpack(value) def set_value(self, field, value): """Set a named item on an Image. Values are converted from Python types to something libvips can swallow. For example, bytes() can be used to set VipsBlob fields. """ gtype = self.get_typeof(field) logger.debug('%s.%s = %s' % (self, field, value)) logger.debug('%s.%s needs a %s' % (self, field, gtype)) # if there's a thing of this name already, convert to that type if gtype != GObject.TYPE_INVALID: # blob-ize if GObject.type_is_a(gtype, vips_type_blob): if not isinstance(value, Vips.Blob): value = Vips.Blob.new(None, value) # image-ize if GObject.type_is_a(gtype, vips_type_image): if not isinstance(value, Vips.Image): value = imageize(self, value) # array-ize some types, if necessary value = arrayize(gtype, value) self.set(field, value) def floor(self): """Return the largest integral value not greater than the argument.""" return self.round(Vips.OperationRound.FLOOR) def ceil(self): """Return the smallest integral value not less than the argument.""" return self.round(Vips.OperationRound.CEIL) def rint(self): """Return the nearest integral value.""" return self.round(Vips.OperationRound.RINT) def bandand(self): """AND image bands together.""" return self.bandbool(Vips.OperationBoolean.AND) def bandor(self): """OR image bands together.""" return self.bandbool(Vips.OperationBoolean.OR) def bandeor(self): """EOR image bands together.""" return self.bandbool(Vips.OperationBoolean.EOR) def bandsplit(self): """Split an n-band image into n separate images.""" return [x for x in self] def bandjoin(self, other): """Append a set of images or constants bandwise.""" if not isinstance(other, list): other = [other] # if [other] is all numbers, we can use bandjoin_const non_number = next((x for x in other if not isinstance(x, numbers.Number)), None) if non_number == None: return self.bandjoin_const(other) else: return _call_base("bandjoin", [[self] + other], {}) def bandrank(self, other, **kwargs): """Band-wise rank filter a set of images.""" if not isinstance(other, list): other = [other] return _call_base("bandrank", [[self] + other], kwargs) def maxpos(self): """Return the coordinates of the image maximum.""" v, opts = self.max(x = True, y = True) x = opts['x'] y = opts['y'] return v, x, y def minpos(self): """Return the coordinates of the image minimum.""" v, opts = self.min(x = True, y = True) x = opts['x'] y = opts['y'] return v, x, y def real(self): """Return the real part of a complex image.""" return self.complexget(Vips.OperationComplexget.REAL) def imag(self): """Return the imaginary part of a complex image.""" return self.complexget(Vips.OperationComplexget.IMAG) def polar(self): """Return an image converted to polar coordinates.""" return run_cmplx(lambda x: x.complex(Vips.OperationComplex.POLAR), self) def rect(self): """Return an image converted to rectangular coordinates.""" return run_cmplx(lambda x: x.complex(Vips.OperationComplex.RECT), self) def conj(self): """Return the complex conjugate of an image.""" return self.complex(Vips.OperationComplex.CONJ) def sin(self): """Return the sine of an image in degrees.""" return self.math(Vips.OperationMath.SIN) def cos(self): """Return the cosine of an image in degrees.""" return self.math(Vips.OperationMath.COS) def tan(self): """Return the tangent of an image in degrees.""" return self.math(Vips.OperationMath.TAN) def asin(self): """Return the inverse sine of an image in degrees.""" return self.math(Vips.OperationMath.ASIN) def acos(self): """Return the inverse cosine of an image in degrees.""" return self.math(Vips.OperationMath.ACOS) def atan(self): """Return the inverse tangent of an image in degrees.""" return self.math(Vips.OperationMath.ATAN) def log(self): """Return the natural log of an image.""" return self.math(Vips.OperationMath.LOG) def log10(self): """Return the log base 10 of an image.""" return self.math(Vips.OperationMath.LOG10) def exp(self): """Return e ** pixel.""" return self.math(Vips.OperationMath.EXP) def exp10(self): """Return 10 ** pixel.""" return self.math(Vips.OperationMath.EXP10) def erode(self, mask): """Erode with a structuring element.""" return self.morph(mask, Vips.OperationMorphology.ERODE) def dilate(self, mask): """Dilate with a structuring element.""" return self.morph(mask, Vips.OperationMorphology.DILATE) def median(self, size): """size x size median filter.""" return self.rank(size, size, (size * size) / 2) def fliphor(self): """Flip horizontally.""" return self.flip(Vips.Direction.HORIZONTAL) def flipver(self): """Flip vertically.""" return self.flip(Vips.Direction.VERTICAL) def rot90(self): """Rotate 90 degrees clockwise.""" return self.rot(Vips.Angle.D90) def rot180(self): """Rotate 180 degrees.""" return self.rot(Vips.Angle.D180) def rot270(self): """Rotate 270 degrees clockwise.""" return self.rot(Vips.Angle.D270) # we need different imageize rules for this operator ... we need to # imageize th and el to match each other first @add_doc(generate_docstring("ifthenelse")) def ifthenelse(self, th, el, **kwargs): for match_image in [th, el, self]: if isinstance(match_image, Vips.Image): break if not isinstance(th, Vips.Image): th = imageize(match_image, th) if not isinstance(el, Vips.Image): el = imageize(match_image, el) return _call_base("ifthenelse", [th, el], kwargs, self) # add operators which needs to be class methods # use find_class_methods.py to generate this list # don't include "bandjoin" or "bandrank", they need to be wrapped by hand, # see above class_methods = [ "analyzeload", "arrayjoin", "black", "csvload", "eye", "fitsload", "fractsurf", "gaussmat", "gaussnoise", "gifload", "gifload_buffer", "grey", "identity", "jpegload", "jpegload_buffer", "logmat", "magickload", "magickload_buffer", "mask_butterworth", "mask_butterworth_band", "mask_butterworth_ring", "mask_fractal", "mask_gaussian", "mask_gaussian_band", "mask_gaussian_ring", "mask_ideal", "mask_ideal_band", "mask_ideal_ring", "matload", "matrixload", "openexrload", "openslideload", "pdfload", "pdfload_buffer", "perlin", "pngload", "pngload_buffer", "ppmload", "radload", "rawload", "sines", "sum", "svgload", "svgload_buffer", "system", "text", "thumbnail", "thumbnail_buffer", "tiffload", "tiffload_buffer", "tonelut", "vipsload", "webpload", "webpload_buffer", "worley", "xyz", "zone"] def generate_class_method(name): @classmethod @add_doc(generate_docstring(name)) def class_method(cls, *args, **kwargs): return _call_base(name, args, kwargs) return class_method for nickname in class_methods: logger.debug('adding %s as a class method' % nickname) # some may be missing in this vips, eg. we might not have "webpload" try: method = generate_class_method(nickname) setattr(Vips.Image, nickname, method) except Error: pass Image = override(Image) __all__.append('Image')