libvips/python/Vips.py
John Cupitt 5916e25c31 rename python bandjoin as ibandjoin
oh argh class and instance methods are in the same namespace, so we have
to rename the instance one as ibandjoin

also, start adding a test for arrayjoin
2015-12-12 12:29:20 +00:00

1121 lines
36 KiB
Python

# -*- 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")
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
pixel = (Vips.Image.black(1, 1) + value).cast(match_image.format)
image = pixel.embed(0, 0, match_image.width, match_image.height,
extend = Vips.Extend.COPY)
image = image.copy(interpretation = match_image.interpretation,
xres = match_image.xres,
yres = match_image.yres)
return image
# 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:
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
# remember to ignore deprecated ones
def get_args(self):
args = [Argument(self, x) for x in self.props]
args = [y for y in args
if not y.flags & Vips.ArgumentFlags.DEPRECATED]
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 input args
required_input = [x for x in args if x.flags & enm.INPUT and
x.flags & enm.REQUIRED 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
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 output arg
if x.flags & enm.OUTPUT and x.flags & enm.REQUIRED:
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
# 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()
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 + "("
result += ", ".join([x.name for x in required_input
if x != member_x])
if len(optional_input) > 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
# 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):
if isinstance(other, Vips.Image):
return self.math2(other, Vips.OperationMath2.POW)
else:
return self.math2_const(other, Vips.OperationMath2.POW)
def __rpow__(self, other):
return self.math2_const(other, Vips.OperationMath2.WOP)
def __abs__(self):
return self.abs()
def __lshift__(self, other):
if isinstance(other, Vips.Image):
return self.boolean(other, Vips.OperationBoolean.LSHIFT)
else:
return self.boolean_const(other, Vips.OperationBoolean.LSHIFT)
def __rshift__(self, other):
if isinstance(other, Vips.Image):
return self.boolean(other, Vips.OperationBoolean.RSHIFT)
else:
return self.boolean_const(other, Vips.OperationBoolean.RSHIFT)
def __and__(self, other):
if isinstance(other, Vips.Image):
return self.boolean(other, Vips.OperationBoolean.AND)
else:
return self.boolean_const(other, Vips.OperationBoolean.AND)
def __rand__(self, other):
return self.__and__(other)
def __or__(self, other):
if isinstance(other, Vips.Image):
return self.boolean(other, Vips.OperationBoolean.OR)
else:
return self.boolean_const(other, Vips.OperationBoolean.OR)
def __ror__(self, other):
return self.__or__(other)
def __xor__(self, other):
if isinstance(other, Vips.Image):
return self.boolean(other, Vips.OperationBoolean.EOR)
else:
return self.boolean_const(other, Vips.OperationBoolean.EOR)
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):
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.MORE)
else:
return self.relational_const(other, Vips.OperationRelational.MORE)
def __ge__(self, other):
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.MOREEQ)
else:
return self.relational_const(other, Vips.OperationRelational.MOREEQ)
def __lt__(self, other):
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.LESS)
else:
return self.relational_const(other, Vips.OperationRelational.LESS)
def __le__(self, other):
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.LESSEQ)
else:
return self.relational_const(other, Vips.OperationRelational.LESSEQ)
def __eq__(self, other):
# for == and != we need to allow comparison to None
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.EQUAL)
elif isinstance(other, list):
return self.relational_const(other, Vips.OperationRelational.EQUAL)
elif isinstance(other, numbers.Number):
return self.relational_const(other, Vips.OperationRelational.EQUAL)
else:
return False
def __ne__(self, other):
if isinstance(other, Vips.Image):
return self.relational(other, Vips.OperationRelational.NOTEQ)
elif isinstance(other, list):
return self.relational_const(other, Vips.OperationRelational.NOTEQ)
elif isinstance(other, numbers.Number):
return self.relational_const(other, Vips.OperationRelational.NOTEQ)
else:
return False
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('assigning %s to %s' % (value, self))
logger.debug('%s needs a %s' % (self, gtype))
# 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]
# bandjoin as an instance method ... it needs a different name,
# unfortunately
def ibandjoin(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 Vips.Image.bandjoin([self] + other)
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
class_methods = [
"system",
"sum",
"bandjoin",
"arrayjoin",
"bandrank",
"black",
"gaussnoise",
"text",
"xyz",
"gaussmat",
"logmat",
"eye",
"grey",
"zone",
"sines",
"mask_ideal",
"mask_ideal_ring",
"mask_ideal_band",
"mask_butterworth",
"mask_butterworth_ring",
"mask_butterworth_band",
"mask_gaussian",
"mask_gaussian_ring",
"mask_gaussian_band",
"mask_fractal",
"tonelut",
"identity",
"fractsurf",
"radload",
"ppmload",
"csvload",
"matrixload",
"analyzeload",
"rawload",
"vipsload",
"pngload",
"pngload_buffer",
"matload",
"jpegload",
"jpegload_buffer",
"webpload",
"webpload_buffer",
"tiffload",
"tiffload_buffer",
"openslideload",
"magickload",
"magickload_buffer",
"fitsload",
"openexrload"]
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')