Merge branch 'master' into add-nifti-support

This commit is contained in:
John Cupitt 2018-07-20 13:57:51 +01:00
commit 3201b4373d
56 changed files with 4227 additions and 160 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.pytest_cache
compile
.pytest_cache
a.out
*.log
*.trs

View File

@ -31,7 +31,13 @@
- add Mitchell kernel
- pyramid builders have a choice of 2x2 shrinkers [harukizaemon]
- add `palette` option to pngsave [felixbuenemann]
<<<<<<< HEAD
- add basic nifti load/save support
=======
- support writing string-valued fields via libexif
- paste in the test suite from pyvips
- get EXIF tag names from tag plus ifd [@Nan619]
>>>>>>> master
12/3/18 started 8.6.4
- better fitting of fonts with overhanging edges [Adrià]

160
README.md
View File

@ -8,28 +8,25 @@ similar libraries, [libvips runs quickly and uses little
memory](https://github.com/jcupitt/libvips/wiki/Speed-and-memory-use).
libvips is licensed under the LGPL 2.1+.
It has around 300 operations covering arithmetic, histograms,
convolutions, morphological operations, frequency filtering, colour,
resampling, statistics and others. It supports a large range of numeric
formats, from 8-bit int to 128-bit complex. It supports a good range of
image formats, including JPEG, TIFF, PNG, WebP, FITS, Matlab, OpenEXR,
PDF, SVG, HDR, PPM, CSV, GIF, Analyze, DeepZoom, and OpenSlide. It can
also load images via ImageMagick or GraphicsMagick.
It has around 300 operations covering arithmetic, histograms, convolutions,
morphological operations, frequency filtering, colour, resampling, statistics
and others. It supports a large range of numeric formats, from 8-bit int
to 128-bit complex. It supports a good range of image formats, including
JPEG, TIFF, PNG, WebP, FITS, Matlab, OpenEXR, PDF, SVG, HDR, PPM, CSV, GIF,
Analyze, DeepZoom, and OpenSlide. It can also load images via ImageMagick
or GraphicsMagick.
It has APIs for
[C](http://jcupitt.github.io/libvips/API/current/using-from-c.html)
and
[C](http://jcupitt.github.io/libvips/API/current/using-from-c.html) and
[C++](http://jcupitt.github.io/libvips/API/current/using-from-cpp.html)
and a [command-line
interface](http://jcupitt.github.io/libvips/API/current/using-cli.html).
Bindings are available for
[Python](https://pypi.python.org/pypi/pyvips),
Bindings are available for [Python](https://pypi.python.org/pypi/pyvips),
[Ruby](https://rubygems.org/gems/ruby-vips),
[PHP](https://github.com/jcupitt/php-vips),
[Go](https://github.com/davidbyttow/govips),
[Lua](https://github.com/jcupitt/lua-vips),
JavaScript and others. There is full
[documentation](http://jcupitt.github.io/libvips/API/current).
[Lua](https://github.com/jcupitt/lua-vips), JavaScript and others. There is
full [documentation](http://jcupitt.github.io/libvips/API/current).
There are several GUIs as well, see the [VIPS
website](http://jcupitt.github.io/libvips).
@ -44,105 +41,120 @@ https://github.com/jcupitt/libvips/releases
Untar, then in the libvips directory you should just be able to do:
$ ./configure
$ ./configure
Check the summary at the end of `configure` carefully.
libvips must have `build-essential`, `pkg-config`, `glib2.0-dev`,
`libexpat1-dev`.
Check the summary at the end of `configure` carefully. libvips must have
`build-essential`, `pkg-config`, `glib2.0-dev`, `libexpat1-dev`.
You'll need the dev packages for the file format support you
want. For basic jpeg and tiff support, you'll need `libtiff5-dev`,
`libjpeg-turbo8-dev`, and `libgsf-1-dev`. See the **Dependencies** section
below for a full list of the things that libvips can be configured to use.
You'll need the dev packages for the file format support you want. For basic
jpeg and tiff support, you'll need `libtiff5-dev`, `libjpeg-turbo8-dev`,
and `libgsf-1-dev`. See the **Dependencies** section below for a full list
of the things that libvips can be configured to use.
Once `configure` is looking OK, compile and install with the usual:
$ make
$ sudo make install
$ make
$ sudo make install
By default this will install files to `/usr/local`.
We have detailed guides on the wiki for [building on
Windows](https://github.com/jcupitt/libvips/wiki/Build-for-Windows)
and [building on OS
X](https://github.com/jcupitt/libvips/wiki/Build-for-macOS).
Windows](https://github.com/jcupitt/libvips/wiki/Build-for-Windows) and
[building on OS X](https://github.com/jcupitt/libvips/wiki/Build-for-macOS).
# Testing
Do a basic test of your build with:
$ make check
Run the libvips test suite with:
$ pytest
Run a specific test with:
$ pytest test/test-suite/test_foreign.py -k test_tiff
YOu will need to install a variety of Python packages for this, including
pyvips, the libvips Python binding.
# Building libvips from git
Checkout the latest sources with:
$ git clone git://github.com/jcupitt/libvips.git
$ git clone git://github.com/jcupitt/libvips.git
Building from git needs more packages, you'll need at least `swig`, `gtk-doc`
and `gobject-introspection`, see the dependencies section below. For example:
$ brew install gtk-doc swig
$ brew install gtk-doc swig
Then build the build system with:
$ ./autogen.sh
$ ./autogen.sh
Debug build:
$ CFLAGS="-g -Wall" CXXFLAGS="-g -Wall" \
./configure --prefix=/home/john/vips --enable-debug
$ make
$ make install
$ CFLAGS="-g -Wall" CXXFLAGS="-g -Wall" \
./configure --prefix=/home/john/vips --enable-debug
$ make
$ make install
Leak check:
$ export G_DEBUG=gc-friendly
$ valgrind --suppressions=libvips.supp \
--leak-check=yes \
vips ... > vips-vg.log 2>&1
$ export G_DEBUG=gc-friendly
$ valgrind --suppressions=libvips.supp \
--leak-check=yes \
vips ... > vips-vg.log 2>&1
Memory error debug:
$ valgrind --vgdb=yes --vgdb-error=0 vips ...
$ valgrind --vgdb=yes --vgdb-error=0 vips ...
valgrind threading check:
$ valgrind --tool=helgrind vips ... > vips-vg.log 2>&1
$ valgrind --tool=helgrind vips ... > vips-vg.log 2>&1
Clang build:
$ CC=clang CXX=clang++ ./configure --prefix=/home/john/vips
$ CC=clang CXX=clang++ ./configure --prefix=/home/john/vips
Clang static analysis:
$ scan-build ./configure --disable-introspection --disable-debug
$ scan-build -o scan -v make
$ scan-view scan/2013-11-22-2
$ scan-build ./configure --disable-introspection --disable-debug
$ scan-build -o scan -v make
$ scan-view scan/2013-11-22-2
Clang dynamic analysis:
$ FLAGS="-O1 -g -fsanitize=address"
$ FLAGS="$FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls"
$ CC=clang CXX=clang++ LD=clang \
CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" LDFLAGS=-fsanitize=address \
./configure --prefix=/home/john/vips
$ FLAGS="-O1 -g -fsanitize=address"
$ FLAGS="$FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls"
$ CC=clang CXX=clang++ LD=clang \
CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" LDFLAGS=-fsanitize=address \
./configure --prefix=/home/john/vips
$ FLAGS="-O1 -g -fsanitize=thread"
$ FLAGS="$FLAGS -fPIC"
$ FLAGS="$FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls"
$ CC=clang CXX=clang++ LD=clang \
CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \
LDFLAGS="-fsanitize=thread -fPIC" \
./configure --prefix=/home/john/vips \
--without-magick \
--disable-introspection
$ G_DEBUG=gc-friendly vips copy ~/pics/k2.jpg x.jpg >& log
$ FLAGS="-O1 -g -fsanitize=thread"
$ FLAGS="$FLAGS -fPIC"
$ FLAGS="$FLAGS -fno-omit-frame-pointer -fno-optimize-sibling-calls"
$ CC=clang CXX=clang++ LD=clang \
CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \
LDFLAGS="-fsanitize=thread -fPIC" \
./configure --prefix=/home/john/vips \
--without-magick \
--disable-introspection
$ G_DEBUG=gc-friendly vips copy ~/pics/k2.jpg x.jpg >& log
Build with the GCC auto-vectorizer and diagnostics (or just -O3):
$ FLAGS="-O2 -march=native -ffast-math"
$ FLAGS="$FLAGS -ftree-vectorize -fdump-tree-vect-details"
$ CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \
./configure --prefix=/home/john/vips
$ FLAGS="-O2 -march=native -ffast-math"
$ FLAGS="$FLAGS -ftree-vectorize -fdump-tree-vect-details"
$ CFLAGS="$FLAGS" CXXFLAGS="$FLAGS" \
./configure --prefix=/home/john/vips
Static analysis with:
$ cppcheck --force --enable=style . &> cppcheck.log
$ cppcheck --force --enable=style . &> cppcheck.log
# Dependencies
@ -160,19 +172,19 @@ them in the default path and in `$prefix`. If you have installed your own
versions of these libraries in a different location, libvips will not see
them. Use switches to libvips configure like:
./configure --prefix=/Users/john/vips \
--with-giflib-includes=/opt/local/include \
--with-giflib-libraries=/opt/local/lib \
--with-tiff-includes=/opt/local/include \
--with-tiff-libraries=/opt/local/lib \
--with-jpeg-includes=/opt/local/include \
--with-jpeg-libraries=/opt/local/lib
./configure --prefix=/Users/john/vips \
--with-giflib-includes=/opt/local/include \
--with-giflib-libraries=/opt/local/lib \
--with-tiff-includes=/opt/local/include \
--with-tiff-libraries=/opt/local/lib \
--with-jpeg-includes=/opt/local/include \
--with-jpeg-libraries=/opt/local/lib
or perhaps:
CFLAGS="-g -Wall -I/opt/local/include -L/opt/local/lib" \
CXXFLAGS="-g -Wall -I/opt/local/include -L/opt/local/lib" \
./configure --without-python --prefix=/Users/john/vips
CFLAGS="-g -Wall -I/opt/local/include -L/opt/local/lib" \
CXXFLAGS="-g -Wall -I/opt/local/include -L/opt/local/lib" \
./configure --without-python --prefix=/Users/john/vips
to get libvips to see your builds.

View File

@ -7,6 +7,10 @@
* - only read orientation from ifd0
* 1/2/18
* - remove exif thumbnail if "jpeg-thumbnail-data" has been removed
* 3/7/18
* - add support for writing string-valued fields
* 9/7/18 [@Nan619]
* - get tag name from tag plus ifd
*/
/*
@ -307,6 +311,21 @@ typedef struct _VipsExifParams {
ExifData *ed;
} VipsExifParams;
/* tags do not uniquely set tag names: the same tag can have different
* names in different ifds.
*
* As long as this entry has been linked to an ifd, get the tag name.
*/
static const char *
vips_exif_entry_get_name( ExifEntry *entry )
{
if( !entry->parent )
return( NULL );
return( exif_tag_get_name_in_ifd( entry->tag,
exif_entry_get_ifd( entry ) ) );
}
static void
vips_exif_attach_entry( ExifEntry *entry, VipsExifParams *params )
{
@ -316,7 +335,7 @@ vips_exif_attach_entry( ExifEntry *entry, VipsExifParams *params )
char value_txt[256];
VipsBuf value = VIPS_BUF_STATIC( value_txt );
if( !(tag_name = exif_tag_get_name( entry->tag )) )
if( !(tag_name = vips_exif_entry_get_name( entry )) )
return;
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
@ -538,7 +557,7 @@ vips_exif_set_int( ExifData *ed,
offset = component * sizeof_component;
VIPS_DEBUG_MSG( "vips_exif_set_int: %s = %d\n",
exif_tag_get_name( entry->tag ), value );
vips_exif_entry_get_name( entry ), value );
if( entry->format == EXIF_FORMAT_SHORT )
exif_set_short( entry->data + offset, bo, value );
@ -616,7 +635,7 @@ vips_exif_set_rational( ExifData *ed,
offset = component * sizeof_component;
VIPS_DEBUG_MSG( "vips_exif_set_rational: %s = \"%s\"\n",
exif_tag_get_name( entry->tag ), value );
vips_exif_entry_get_name( entry ), value );
if( entry->format == EXIF_FORMAT_RATIONAL ) {
ExifRational rv;
@ -670,7 +689,7 @@ vips_exif_set_double( ExifData *ed,
offset = component * sizeof_component;
VIPS_DEBUG_MSG( "vips_exif_set_double: %s = %g\n",
exif_tag_get_name( entry->tag ), value );
vips_exif_entry_get_name( entry ), value );
if( entry->format == EXIF_FORMAT_RATIONAL ) {
ExifRational rv;
@ -706,6 +725,183 @@ vips_exif_set_double( ExifData *ed,
typedef void (*write_fn)( ExifData *ed,
ExifEntry *entry, unsigned long component, void *data );
/* String-valued tags need special treatment, sadly.
*
* Strings are written in three ways:
*
* 1. As ASCII, but with an 8-byte preamble giving the encoding (it's always
* ASCII though) and the format undefined.
* 2. As plain ASCII, with the format giving the encoding.
* 3. As UTF16 in the MS tags.
*/
static gboolean
tag_is_encoding( ExifTag tag )
{
return( tag == EXIF_TAG_USER_COMMENT );
}
static gboolean
tag_is_ascii( ExifTag tag )
{
return( tag == EXIF_TAG_MAKE ||
tag == EXIF_TAG_MODEL ||
tag == EXIF_TAG_IMAGE_DESCRIPTION ||
tag == EXIF_TAG_ARTIST ||
tag == EXIF_TAG_SOFTWARE ||
tag == EXIF_TAG_COPYRIGHT ||
tag == EXIF_TAG_DATE_TIME ||
tag == EXIF_TAG_DATE_TIME_ORIGINAL ||
tag == EXIF_TAG_DATE_TIME_DIGITIZED ||
tag == EXIF_TAG_SUB_SEC_TIME ||
tag == EXIF_TAG_SUB_SEC_TIME_ORIGINAL ||
tag == EXIF_TAG_SUB_SEC_TIME_DIGITIZED );
}
static gboolean
tag_is_utf16( ExifTag tag )
{
return( tag == EXIF_TAG_XP_TITLE ||
tag == EXIF_TAG_XP_COMMENT ||
tag == EXIF_TAG_XP_AUTHOR ||
tag == EXIF_TAG_XP_KEYWORDS ||
tag == EXIF_TAG_XP_SUBJECT );
}
/* Set a libexif-formatted string entry.
*/
static void
vips_exif_alloc_string( ExifEntry *entry, unsigned long components )
{
ExifMem *mem;
g_assert( !entry->data );
/* The string in the entry must be allocated with the same allocator
* that was used to allocate the entry itself. We can't do this
* because the allocator is private :( so we must assume the entry was
* created with the default one.
*/
mem = exif_mem_new_default();
/* EXIF_FORMAT_UNDEFINED is correct for EXIF_TAG_USER_COMMENT, our
* caller should change this if it wishes.
*/
entry->data = exif_mem_alloc( mem, components );
entry->size = components;
entry->components = components;
entry->format = EXIF_FORMAT_UNDEFINED;
VIPS_FREEF( exif_mem_unref, mem );
}
/* The final " (xx, yy, zz, kk)" part of the string (if present) was
* added by us in _to_s(), we must remove it before setting the string
* back again.
*
* It may not be there if the user has changed the string.
*/
static char *
drop_tail( const char *data )
{
char *str;
char *p;
str = g_strdup( data );
p = str + strlen( str );
if( p > str &&
*(p = g_utf8_prev_char( p )) == ')' &&
(p = g_utf8_strrchr( p, -1, (gunichar) '(')) &&
p > str &&
*(p = g_utf8_prev_char( p )) == ' ' )
*p = '\0';
return( str );
}
/* special header required for EXIF_TAG_USER_COMMENT.
*/
#define ASCII_COMMENT "ASCII\0\0\0"
/* Write a libvips NULL-terminated utf-8 string into a entry tagged with a
* encoding. UserComment is like this, for example.
*/
static void
vips_exif_set_string_encoding( ExifData *ed,
ExifEntry *entry, unsigned long component, const char *data )
{
char *str;
char *ascii;
int len;
str = drop_tail( data );
/* libexif can only really save ASCII to things like UserComment.
*/
ascii = g_str_to_ascii( str, NULL );
/* libexif comment strings are not NULL-terminated, and have an
* encoding tag (always ASCII) in the first 8 bytes.
*/
len = strlen( ascii );
vips_exif_alloc_string( entry, sizeof( ASCII_COMMENT ) - 1 + len );
memcpy( entry->data, ASCII_COMMENT, sizeof( ASCII_COMMENT ) - 1 );
memcpy( entry->data + sizeof( ASCII_COMMENT ) - 1, ascii, len );
g_free( ascii );
g_free( str );
}
/* Write a libvips NULL-terminated utf-8 string into an ASCII entry. Tags like
* ImageDescription work like this.
*/
static void
vips_exif_set_string_ascii( ExifData *ed,
ExifEntry *entry, unsigned long component, const char *data )
{
char *str;
char *ascii;
int len;
str = drop_tail( data );
ascii = g_str_to_ascii( str, NULL );
/* ASCII strings are NULL-terminated.
*/
len = strlen( ascii );
vips_exif_alloc_string( entry, len + 1 );
memcpy( entry->data, ascii, len + 1 );
entry->format = EXIF_FORMAT_ASCII;
g_free( ascii );
g_free( str );
}
/* Write a libvips NULL-terminated utf-8 string into a utf16 entry.
*/
static void
vips_exif_set_string_utf16( ExifData *ed,
ExifEntry *entry, unsigned long component, const char *data )
{
char *str;
gunichar2 *utf16;
glong len;
str = drop_tail( data );
utf16 = g_utf8_to_utf16( str, -1, NULL, &len, NULL );
/* libexif utf16 strings are NULL-terminated.
*/
vips_exif_alloc_string( entry, (len + 1) * 2 );
memcpy( entry->data, utf16, (len + 1) * 2 );
entry->format = EXIF_FORMAT_BYTE;
g_free( utf16 );
g_free( str );
}
/* Write a tag. Update what's there, or make a new one.
*/
static void
@ -722,12 +918,22 @@ vips_exif_set_tag( ExifData *ed, int ifd, ExifTag tag, write_fn fn, void *data )
/* tag must be set before calling exif_content_add_entry.
*/
entry->tag = tag;
exif_content_add_entry( ed->ifd[ifd], entry );
exif_entry_initialize( entry, tag );
exif_entry_unref( entry );
fn( ed, entry, 0, data );
/* libexif makes us have a special path for string-valued
* fields :(
*/
if( tag_is_encoding( tag ) )
vips_exif_set_string_encoding( ed, entry, 0, data );
else if( tag_is_ascii( tag ) )
vips_exif_set_string_ascii( ed, entry, 0, data );
else if( tag_is_utf16( tag ) )
vips_exif_set_string_utf16( ed, entry, 0, data );
else {
exif_entry_initialize( entry, tag );
fn( ed, entry, 0, data );
}
}
}
@ -869,53 +1075,73 @@ vips_exif_set_thumbnail( ExifData *ed, VipsImage *im )
return( 0 );
}
/* See also vips_exif_to_s() ... keep in sync.
/* Skip any spaces.
*/
static const char *
skip_space( const char *p )
{
while( p && *p == ' ' )
p += 1;
return( p );
}
/* Skip to the end of this non-space sequence.
*/
static const char *
skip_nonspace( const char *p )
{
while( p && *p && *p != ' ' )
p += 1;
return( p );
}
/* See also vips_exif_to_s() ... keep in sync. Only the numeric types are
* handled here, since they can be updated. For string types, we have to
* destroy and recreate, see above.
*/
static void
vips_exif_from_s( ExifData *ed, ExifEntry *entry, const char *value )
{
unsigned long i;
const char *p;
int v;
if( entry->format != EXIF_FORMAT_SHORT &&
entry->format != EXIF_FORMAT_SSHORT &&
entry->format != EXIF_FORMAT_LONG &&
entry->format != EXIF_FORMAT_SLONG &&
entry->format != EXIF_FORMAT_RATIONAL &&
entry->format != EXIF_FORMAT_SRATIONAL )
return;
if( entry->components >= 10 )
return;
if( entry->format == EXIF_FORMAT_SHORT ||
entry->format == EXIF_FORMAT_SSHORT ||
entry->format == EXIF_FORMAT_LONG ||
entry->format == EXIF_FORMAT_SLONG ) {
if( entry->components >= 10 )
return;
/* Skip any leading spaces.
*/
p = value;
while( *p == ' ' )
p += 1;
p = value;
for( i = 0; i < entry->components; i++ ) {
if( !(p = skip_space( p )) )
break;
for( i = 0; i < entry->components; i++ ) {
if( entry->format == EXIF_FORMAT_SHORT ||
entry->format == EXIF_FORMAT_SSHORT ||
entry->format == EXIF_FORMAT_LONG ||
entry->format == EXIF_FORMAT_SLONG ) {
int value = atof( p );
v = atof( p );
vips_exif_set_int( ed, entry, i, &v );
vips_exif_set_int( ed, entry, i, &value );
p = skip_nonspace( p );
}
else if( entry->format == EXIF_FORMAT_RATIONAL ||
entry->format == EXIF_FORMAT_SRATIONAL )
}
else if( entry->format == EXIF_FORMAT_RATIONAL ||
entry->format == EXIF_FORMAT_SRATIONAL ) {
if( entry->components >= 10 )
return;
p = value;
for( i = 0; i < entry->components; i++ ) {
if( !(p = skip_space( p )) )
break;
vips_exif_set_rational( ed, entry, i, (void *) p );
/* Skip to the next set of spaces, then to the beginning of
* the next item.
*/
while( *p && *p != ' ' )
p += 1;
while( *p == ' ' )
p += 1;
if( !*p )
break;
p = skip_nonspace( p );
}
}
}
static void
@ -982,7 +1208,7 @@ vips_exif_exif_entry( ExifEntry *entry, VipsExifRemove *ve )
char vips_name_txt[256];
VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt );
if( !(tag_name = exif_tag_get_name( entry->tag )) )
if( !(tag_name = vips_exif_entry_get_name( entry )) )
return;
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
@ -1001,6 +1227,14 @@ vips_exif_exif_entry( ExifEntry *entry, VipsExifRemove *ve )
if( strcmp( tag_name, "Orientation" ) == 0 &&
vips_image_get_typeof( ve->image, VIPS_META_ORIENTATION ) )
ve->to_remove = g_slist_prepend( ve->to_remove, entry );
/* If this is a string tag, we must also remove it ready for
* recreation, see the comment below.
*/
if( tag_is_encoding( entry->tag ) ||
tag_is_ascii( entry->tag ) ||
tag_is_utf16( entry->tag ) )
ve->to_remove = g_slist_prepend( ve->to_remove, entry );
}
static void *
@ -1012,7 +1246,7 @@ vips_exif_exif_remove( ExifEntry *entry, VipsExifRemove *ve )
char vips_name_txt[256];
VipsBuf vips_name = VIPS_BUF_STATIC( vips_name_txt );
tag_name = exif_tag_get_name( entry->tag );
tag_name = vips_exif_entry_get_name( entry );
vips_buf_appendf( &vips_name, "exif-ifd%d-%s",
exif_entry_get_ifd( entry ), tag_name );
@ -1044,14 +1278,17 @@ vips_exif_update( ExifData *ed, VipsImage *image )
VIPS_DEBUG_MSG( "vips_exif_update: \n" );
/* Walk the image and add any exif- that's set in image metadata.
*/
vips_image_map( image, vips_exif_image_field, ed );
/* If this exif came from the image (rather than being an exif block we
* have made afresh), then any fields which are in the block but not on
* the image must have been deliberately removed. Remove them from the
* block as well.
*
* If there are any string-valued fields (eg. comment etc.) which
* exist as libvips metadata tags, we must also remove those from the
* exif block.
*
* libexif does not allow you to change string lengths, you must make
* new tags, so we have to remove ready to re-add.
*/
if( vips_image_get_typeof( image, VIPS_META_EXIF_NAME ) ) {
ve.image = image;
@ -1060,6 +1297,10 @@ vips_exif_update( ExifData *ed, VipsImage *image )
(ExifDataForeachContentFunc) vips_exif_exif_content,
&ve );
}
/* Walk the image and add any exif- that's set in image metadata.
*/
vips_image_map( image, vips_exif_image_field, ed );
}
/* Examine the metadata tags on the image and update the EXIF block.

View File

@ -176,6 +176,8 @@
* 24/10/17
* - no error on page-height not a factor of image height, just don't
* write multipage
* 2/7/18
* - copy EXTRASAMPLES to pyramid layers
*/
/*
@ -563,7 +565,9 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
if( wtiff->compression == COMPRESSION_JPEG )
TIFFSetField( tif, TIFFTAG_JPEGQUALITY, wtiff->jpqual );
if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
if( (wtiff->compression == VIPS_FOREIGN_TIFF_COMPRESSION_DEFLATE ||
wtiff->compression == VIPS_FOREIGN_TIFF_COMPRESSION_LZW) &&
wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
TIFFSetField( tif, TIFFTAG_PREDICTOR, wtiff->predictor );
/* Don't write mad resolutions (eg. zero), it confuses some programs.
@ -1566,6 +1570,7 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in )
tdata_t buf;
ttile_t tile;
ttile_t n;
uint16 *a;
/* All the fields we might have set.
*/
@ -1586,8 +1591,8 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in )
CopyField( TIFFTAG_ROWSPERSTRIP, i32 );
CopyField( TIFFTAG_SUBFILETYPE, i32 );
if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
TIFFSetField( out, TIFFTAG_PREDICTOR, wtiff->predictor );
if( TIFFGetField( in, TIFFTAG_EXTRASAMPLES, &i16, &a ) )
TIFFSetField( out, TIFFTAG_EXTRASAMPLES, i16, a );
/* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it.
* Set explicitly from Wtiff.

View File

@ -40,9 +40,9 @@ if test "$GIFLIB_INCLUDES" = ""; then
], [
# gif_lib.h is not in the standard search path, try
# $prefix
giflib_save_INCLUDES="$INCLUDES"
giflib_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <gif_lib.h>],[int a;],[
GIFLIB_INCLUDES="-I${prefix}/include"
@ -50,17 +50,17 @@ if test "$GIFLIB_INCLUDES" = ""; then
GIFLIB_INCLUDES="no"
])
INCLUDES=$giflib_save_INCLUDES
CFLAGS=$giflib_save_CFLAGS
])
fi
# Now for the libraries
if test "$GIFLIB_LIBS" = ""; then
giflib_save_LIBS="$LIBS"
giflib_save_INCLUDES="$INCLUDES"
giflib_save_CFLAGS="$CFLAGS"
LIBS="-lgif $LIBS"
INCLUDES="$GIFLIB_INCLUDES $INCLUDES"
CFLAGS="$GIFLIB_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <gif_lib.h>],[DGifSlurp(0)], [
@ -78,7 +78,7 @@ if test "$GIFLIB_LIBS" = ""; then
])
LIBS="$giflib_save_LIBS"
INCLUDES="$giflib_save_INCLUDES"
CFLAGS="$giflib_save_CFLAGS"
fi
AC_SUBST(GIFLIB_LIBS)

View File

@ -41,9 +41,9 @@ if test "$JPEG_INCLUDES" = ""; then
], [
# jpeglib.h is not in the standard search path, try
# $prefix
jpeg_save_INCLUDES="$INCLUDES"
jpeg_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <stdio.h>
#include <jpeglib.h>],[int a;],[
@ -52,17 +52,17 @@ if test "$JPEG_INCLUDES" = ""; then
JPEG_INCLUDES="no"
])
INCLUDES=$jpeg_save_INCLUDES
CFLAGS=$jpeg_save_CFLAGS
])
fi
# Now for the libraries
if test "$JPEG_LIBS" = ""; then
jpeg_save_LIBS="$LIBS"
jpeg_save_INCLUDES="$INCLUDES"
jpeg_save_CFLAGS="$CFLAGS"
LIBS="-ljpeg $LIBS"
INCLUDES="$JPEG_INCLUDES $INCLUDES"
CFLAGS="$JPEG_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <stdio.h>
@ -84,7 +84,7 @@ if test "$JPEG_LIBS" = ""; then
])
LIBS="$jpeg_save_LIBS"
INCLUDES="$jpeg_save_INCLUDES"
CFLAGS="$jpeg_save_CFLAGS"
fi
AC_SUBST(JPEG_LIBS)

View File

@ -40,9 +40,9 @@ if test "$PNG_INCLUDES" = ""; then
], [
# png.h is not in the standard search path, try
# $prefix
png_save_INCLUDES="$INCLUDES"
png_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <png.h>],[int a;],[
PNG_INCLUDES="-I${prefix}/include"
@ -50,17 +50,17 @@ if test "$PNG_INCLUDES" = ""; then
PNG_INCLUDES="no"
])
INCLUDES=$png_save_INCLUDES
CFLAGS=$png_save_CFLAGS
])
fi
# Now for the libraries
if test "$PNG_LIBS" = ""; then
png_save_LIBS="$LIBS"
png_save_INCLUDES="$INCLUDES"
png_save_CFLAGS="$CFLAGS"
LIBS="-lpng $LIBS"
INCLUDES="$PNG_INCLUDES $INCLUDES"
CFLAGS="$PNG_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <png.h>],[png_access_version_number()], [
@ -78,7 +78,7 @@ if test "$PNG_LIBS" = ""; then
])
LIBS="$png_save_LIBS"
INCLUDES="$png_save_INCLUDES"
CFLAGS="$png_save_CFLAGS"
fi
AC_SUBST(PNG_LIBS)

View File

@ -40,9 +40,9 @@ if test "$TIFF_INCLUDES" = ""; then
], [
# tiff.h is not in the standard search path, try
# $prefix
tiff_save_INCLUDES="$INCLUDES"
tiff_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <tiff.h>],[int a;],[
TIFF_INCLUDES="-I${prefix}/include"
@ -50,17 +50,17 @@ if test "$TIFF_INCLUDES" = ""; then
TIFF_INCLUDES="no"
])
INCLUDES=$tiff_save_INCLUDES
CFLAGS=$tiff_save_CFLAGS
])
fi
# Now for the libraries
if test "$TIFF_LIBS" = ""; then
tiff_save_LIBS="$LIBS"
tiff_save_INCLUDES="$INCLUDES"
tiff_save_CFLAGS="$CFLAGS"
LIBS="-ltiff -lm $LIBS"
INCLUDES="$TIFF_INCLUDES $INCLUDES"
CFLAGS="$TIFF_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <tiff.h>],[TIFFGetVersion()], [
@ -78,7 +78,7 @@ if test "$TIFF_LIBS" = ""; then
])
LIBS="$tiff_save_LIBS"
INCLUDES="$tiff_save_INCLUDES"
CFLAGS="$tiff_save_CFLAGS"
fi
AC_SUBST(TIFF_LIBS)

View File

@ -41,9 +41,9 @@ if test "$LIBWEBP_INCLUDES" = ""; then
], [
# webp/decode.h is not in the standard search path, try
# $prefix
libwebp_save_INCLUDES="$INCLUDES"
libwebp_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <webp/decode.h>],[int a;],[
LIBWEBP_INCLUDES="-I${prefix}/include"
@ -51,17 +51,17 @@ if test "$LIBWEBP_INCLUDES" = ""; then
LIBWEBP_INCLUDES="no"
])
INCLUDES=$libwebp_save_INCLUDES
CFLAGS=$libwebp_save_CFLAGS
])
fi
# Now for the libraries
if test "$LIBWEBP_LIBS" = ""; then
libwebp_save_LIBS="$LIBS"
libwebp_save_INCLUDES="$INCLUDES"
libwebp_save_CFLAGS="$CFLAGS"
LIBS="-lwebp $LIBS"
INCLUDES="$LIBWEBP_INCLUDES $INCLUDES"
CFLAGS="$LIBWEBP_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <webp/decode.h>],[WebPInitDecoderConfig(0)], [
@ -79,7 +79,7 @@ if test "$LIBWEBP_LIBS" = ""; then
])
LIBS="$libwebp_save_LIBS"
INCLUDES="$libwebp_save_INCLUDES"
CFLAGS="$libwebp_save_CFLAGS"
fi
AC_SUBST(LIBWEBP_LIBS)

View File

@ -42,9 +42,9 @@ if test "$ZLIB_INCLUDES" = ""; then
], [
# zlib.h is not in the standard search path, try
# $prefix
zlib_save_INCLUDES="$INCLUDES"
zlib_save_CFLAGS="$CFLAGS"
INCLUDES="-I${prefix}/include $INCLUDES"
CFLAGS="-I${prefix}/include $CFLAGS"
AC_TRY_COMPILE([#include <stdio.h>
#include <zlib.h>],[int a;],[
@ -53,17 +53,17 @@ if test "$ZLIB_INCLUDES" = ""; then
ZLIB_INCLUDES="no"
])
INCLUDES=$zlib_save_INCLUDES
CFLAGS=$zlib_save_CFLAGS
])
fi
# Now for the libraries
if test "$ZLIB_LIBS" = ""; then
zlib_save_LIBS="$LIBS"
zlib_save_INCLUDES="$INCLUDES"
zlib_save_CFLAGS="$CFLAGS"
LIBS="-lz $LIBS"
INCLUDES="$ZLIB_INCLUDES $INCLUDES"
CFLAGS="$ZLIB_INCLUDES $CFLAGS"
# Try the standard search path first
AC_TRY_LINK([#include <stdio.h>
@ -85,7 +85,7 @@ if test "$ZLIB_LIBS" = ""; then
])
LIBS="$zlib_save_LIBS"
INCLUDES="$zlib_save_INCLUDES"
CFLAGS="$zlib_save_CFLAGS"
fi
AC_SUBST(ZLIB_LIBS)

5
test/README.md Normal file
View File

@ -0,0 +1,5 @@
# libvips test suite
This is in two parts: a few simple bash scripts in this directory are run on
"make check", and a fancier Python test suite that's run by Travis on each
commit.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

View File

View File

@ -0,0 +1,4 @@
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers'))

View File

@ -0,0 +1,232 @@
# vim: set fileencoding=utf-8 :
# test helpers
import os
import tempfile
import pytest
import pyvips
IMAGES = os.path.join(os.path.dirname(__file__), os.pardir, 'images')
JPEG_FILE = os.path.join(IMAGES, "йцук.jpg")
SRGB_FILE = os.path.join(IMAGES, "sRGB.icm")
MATLAB_FILE = os.path.join(IMAGES, "sample.mat")
PNG_FILE = os.path.join(IMAGES, "sample.png")
TIF_FILE = os.path.join(IMAGES, "sample.tif")
OME_FILE = os.path.join(IMAGES, "multi-channel-z-series.ome.tif")
ANALYZE_FILE = os.path.join(IMAGES, "t00740_tr1_segm.hdr")
GIF_FILE = os.path.join(IMAGES, "cramps.gif")
WEBP_FILE = os.path.join(IMAGES, "1.webp")
EXR_FILE = os.path.join(IMAGES, "sample.exr")
FITS_FILE = os.path.join(IMAGES, "WFPC2u5780205r_c0fx.fits")
OPENSLIDE_FILE = os.path.join(IMAGES, "CMU-1-Small-Region.svs")
PDF_FILE = os.path.join(IMAGES, "ISO_12233-reschart.pdf")
CMYK_PDF_FILE = os.path.join(IMAGES, "cmyktest.pdf")
SVG_FILE = os.path.join(IMAGES, "vips-profile.svg")
SVGZ_FILE = os.path.join(IMAGES, "vips-profile.svgz")
SVG_GZ_FILE = os.path.join(IMAGES, "vips-profile.svg.gz")
GIF_ANIM_FILE = os.path.join(IMAGES, "cogs.gif")
DICOM_FILE = os.path.join(IMAGES, "dicom_test_image.dcm")
BMP_FILE = os.path.join(IMAGES, "MARBLES.BMP")
unsigned_formats = [pyvips.BandFormat.UCHAR,
pyvips.BandFormat.USHORT,
pyvips.BandFormat.UINT]
signed_formats = [pyvips.BandFormat.CHAR,
pyvips.BandFormat.SHORT,
pyvips.BandFormat.INT]
float_formats = [pyvips.BandFormat.FLOAT,
pyvips.BandFormat.DOUBLE]
complex_formats = [pyvips.BandFormat.COMPLEX,
pyvips.BandFormat.DPCOMPLEX]
int_formats = unsigned_formats + signed_formats
noncomplex_formats = int_formats + float_formats
all_formats = int_formats + float_formats + complex_formats
colour_colourspaces = [pyvips.Interpretation.XYZ,
pyvips.Interpretation.LAB,
pyvips.Interpretation.LCH,
pyvips.Interpretation.CMC,
pyvips.Interpretation.LABS,
pyvips.Interpretation.SCRGB,
pyvips.Interpretation.HSV,
pyvips.Interpretation.SRGB,
pyvips.Interpretation.YXY]
coded_colourspaces = [pyvips.Interpretation.LABQ]
mono_colourspaces = [pyvips.Interpretation.B_W]
sixteenbit_colourspaces = [pyvips.Interpretation.GREY16,
pyvips.Interpretation.RGB16]
all_colourspaces = colour_colourspaces + mono_colourspaces + \
coded_colourspaces + sixteenbit_colourspaces
max_value = {pyvips.BandFormat.UCHAR: 0xff,
pyvips.BandFormat.USHORT: 0xffff,
pyvips.BandFormat.UINT: 0xffffffff,
pyvips.BandFormat.CHAR: 0x7f,
pyvips.BandFormat.SHORT: 0x7fff,
pyvips.BandFormat.INT: 0x7fffffff,
pyvips.BandFormat.FLOAT: 1.0,
pyvips.BandFormat.DOUBLE: 1.0,
pyvips.BandFormat.COMPLEX: 1.0,
pyvips.BandFormat.DPCOMPLEX: 1.0}
sizeof_format = {pyvips.BandFormat.UCHAR: 1,
pyvips.BandFormat.USHORT: 2,
pyvips.BandFormat.UINT: 4,
pyvips.BandFormat.CHAR: 1,
pyvips.BandFormat.SHORT: 2,
pyvips.BandFormat.INT: 4,
pyvips.BandFormat.FLOAT: 4,
pyvips.BandFormat.DOUBLE: 8,
pyvips.BandFormat.COMPLEX: 8,
pyvips.BandFormat.DPCOMPLEX: 16}
rot45_angles = [pyvips.Angle45.D0,
pyvips.Angle45.D45,
pyvips.Angle45.D90,
pyvips.Angle45.D135,
pyvips.Angle45.D180,
pyvips.Angle45.D225,
pyvips.Angle45.D270,
pyvips.Angle45.D315]
rot45_angle_bonds = [pyvips.Angle45.D0,
pyvips.Angle45.D315,
pyvips.Angle45.D270,
pyvips.Angle45.D225,
pyvips.Angle45.D180,
pyvips.Angle45.D135,
pyvips.Angle45.D90,
pyvips.Angle45.D45]
rot_angles = [pyvips.Angle.D0,
pyvips.Angle.D90,
pyvips.Angle.D180,
pyvips.Angle.D270]
rot_angle_bonds = [pyvips.Angle.D0,
pyvips.Angle.D270,
pyvips.Angle.D180,
pyvips.Angle.D90]
# an expanding zip ... if either of the args is a scalar or a one-element list,
# duplicate it down the other side
def zip_expand(x, y):
# handle singleton list case
if isinstance(x, list) and len(x) == 1:
x = x[0]
if isinstance(y, list) and len(y) == 1:
y = y[0]
if isinstance(x, list) and isinstance(y, list):
return list(zip(x, y))
elif isinstance(x, list):
return [[i, y] for i in x]
elif isinstance(y, list):
return [[x, j] for j in y]
else:
return [[x, y]]
# run a 1-ary function on a thing -- loop over elements if the
# thing is a list
def run_fn(fn, x):
if isinstance(x, list):
return [fn(i) for i in x]
else:
return fn(x)
# make a temp filename with the specified suffix and in the
# specified directory
def temp_filename(directory, suffix):
temp_name = next(tempfile._get_candidate_names())
filename = os.path.join(directory, temp_name + suffix)
return filename
# test for an operator exists
def have(name):
return pyvips.type_find("VipsOperation", name) != 0
def skip_if_no(operation_name):
return pytest.mark.skipif(not have(operation_name),
reason='no {}, skipping test'.format(operation_name))
# run a 2-ary function on two things -- loop over elements pairwise if the
# things are lists
def run_fn2(fn, x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return fn(x, y)
elif isinstance(x, list) or isinstance(y, list):
return [fn(i, j) for i, j in zip_expand(x, y)]
else:
return fn(x, y)
# test a pair of things which can be lists for approx. equality
def assert_almost_equal_objects(a, b, threshold=0.0001, msg=''):
# print 'assertAlmostEqualObjects %s = %s' % (a, b)
assert all([pytest.approx(x, abs=threshold) == y
for x, y in zip_expand(a, b)]), msg
# test a pair of things which can be lists for equality
def assert_equal_objects(a, b, msg=''):
# print 'assertEqualObjects %s = %s' % (a, b)
assert all([x == y for x, y in zip_expand(a, b)]), msg
# test a pair of things which can be lists for difference less than a
# threshold
def assert_less_threshold(a, b, diff):
assert all([abs(x - y) < diff for x, y in zip_expand(a, b)])
# run a function on an image and on a single pixel, the results
# should match
def run_cmp(message, im, x, y, fn):
a = im(x, y)
v1 = fn(a)
im2 = fn(im)
v2 = im2(x, y)
assert_almost_equal_objects(v1, v2, msg=message)
# run a function on an image,
# 50,50 and 10,10 should have different values on the test image
def run_image(message, im, fn):
run_cmp(message, im, 50, 50, fn)
run_cmp(message, im, 10, 10, fn)
# run a function on (image, constant), and on (constant, image).
# 50,50 and 10,10 should have different values on the test image
def run_const(message, fn, im, c):
run_cmp(message, im, 50, 50, lambda x: run_fn2(fn, x, c))
run_cmp(message, im, 50, 50, lambda x: run_fn2(fn, c, x))
run_cmp(message, im, 10, 10, lambda x: run_fn2(fn, x, c))
run_cmp(message, im, 10, 10, lambda x: run_fn2(fn, c, x))
# run a function on a pair of images and on a pair of pixels, the results
# should match
def run_cmp2(message, left, right, x, y, fn):
a = left(x, y)
b = right(x, y)
v1 = fn(a, b)
after = fn(left, right)
v2 = after(x, y)
assert_almost_equal_objects(v1, v2, msg=message)
# run a function on a pair of images
# 50,50 and 10,10 should have different values on the test image
def run_image2(message, left, right, fn):
run_cmp2(message, left, right, 50, 50,
lambda x, y: run_fn2(fn, x, y))
run_cmp2(message, left, right, 10, 10,
lambda x, y: run_fn2(fn, x, y))

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

View File

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 660 KiB

After

Width:  |  Height:  |  Size: 660 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

Before

Width:  |  Height:  |  Size: 436 B

After

Width:  |  Height:  |  Size: 436 B

View File

Before

Width:  |  Height:  |  Size: 297 KiB

After

Width:  |  Height:  |  Size: 297 KiB

View File

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 176 KiB

View File

@ -0,0 +1,641 @@
# vim: set fileencoding=utf-8 :
import math
import pytest
import pyvips
from helpers import unsigned_formats, float_formats, noncomplex_formats, \
all_formats, run_fn, run_image2, run_const, run_cmp, \
assert_almost_equal_objects
class TestArithmetic:
def run_arith(self, fn, fmt=all_formats):
[run_image2(fn.__name__ + ' image', x.cast(y), x.cast(z), fn)
for x in self.all_images for y in fmt for z in fmt]
def run_arith_const(self, fn, fmt=all_formats):
[run_const(fn.__name__ + ' scalar', fn, x.cast(y), 2)
for x in self.all_images for y in fmt]
[run_const(fn.__name__ + ' vector', fn, self.colour.cast(y),
[1, 2, 3])
for y in fmt]
# run a function on an image,
# 50,50 and 10,10 should have different values on the test image
def run_imageunary(self, message, im, fn):
run_cmp(message, im, 50, 50, lambda x: run_fn(fn, x))
run_cmp(message, im, 10, 10, lambda x: run_fn(fn, x))
def run_unary(self, images, fn, fmt=all_formats):
[self.run_imageunary(fn.__name__ + ' image', x.cast(y), fn)
for x in images for y in fmt]
@classmethod
def setup_class(cls):
im = pyvips.Image.mask_ideal(100, 100, 0.5,
reject=True, optical=True)
cls.colour = im * [1, 2, 3] + [2, 3, 4]
cls.mono = cls.colour.extract_band(1)
cls.all_images = [cls.mono, cls.colour]
# test all operator overloads we define
def test_add(self):
def add(x, y):
return x + y
self.run_arith_const(add)
self.run_arith(add)
def test_sub(self):
def sub(x, y):
return x - y
self.run_arith_const(sub)
self.run_arith(sub)
def test_mul(self):
def mul(x, y):
return x * y
self.run_arith_const(mul)
self.run_arith(mul)
def test_div(self):
def div(x, y):
return x / y
# (const / image) needs (image ** -1), which won't work for complex
self.run_arith_const(div, fmt=noncomplex_formats)
self.run_arith(div)
def test_floordiv(self):
def my_floordiv(x, y):
return x // y
# (const // image) needs (image ** -1), which won't work for complex
self.run_arith_const(my_floordiv, fmt=noncomplex_formats)
self.run_arith(my_floordiv, fmt=noncomplex_formats)
def test_pow(self):
def my_pow(x, y):
return x ** y
# (image ** x) won't work for complex images ... just test non-complex
self.run_arith_const(my_pow, fmt=noncomplex_formats)
self.run_arith(my_pow, fmt=noncomplex_formats)
def test_and(self):
def my_and(x, y):
# python doesn't allow bools on float
if isinstance(x, float):
x = int(x)
if isinstance(y, float):
y = int(y)
return x & y
self.run_arith_const(my_and, fmt=noncomplex_formats)
self.run_arith(my_and, fmt=noncomplex_formats)
def test_or(self):
def my_or(x, y):
# python doesn't allow bools on float
if isinstance(x, float):
x = int(x)
if isinstance(y, float):
y = int(y)
return x | y
self.run_arith_const(my_or, fmt=noncomplex_formats)
self.run_arith(my_or, fmt=noncomplex_formats)
def test_xor(self):
def my_xor(x, y):
# python doesn't allow bools on float
if isinstance(x, float):
x = int(x)
if isinstance(y, float):
y = int(y)
return x ^ y
self.run_arith_const(my_xor, fmt=noncomplex_formats)
self.run_arith(my_xor, fmt=noncomplex_formats)
def test_more(self):
def more(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x > y
else:
if x > y:
return 255
else:
return 0
self.run_arith_const(more)
self.run_arith(more)
def test_moreeq(self):
def moreeq(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x >= y
else:
if x >= y:
return 255
else:
return 0
self.run_arith_const(moreeq)
self.run_arith(moreeq)
def test_less(self):
def less(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x < y
else:
if x < y:
return 255
else:
return 0
self.run_arith_const(less)
self.run_arith(less)
def test_lesseq(self):
def lesseq(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x <= y
else:
if x <= y:
return 255
else:
return 0
self.run_arith_const(lesseq)
self.run_arith(lesseq)
def test_equal(self):
def equal(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x == y
else:
if x == y:
return 255
else:
return 0
self.run_arith_const(equal)
self.run_arith(equal)
def test_noteq(self):
def noteq(x, y):
if isinstance(x, pyvips.Image) or isinstance(y, pyvips.Image):
return x != y
else:
if x != y:
return 255
else:
return 0
self.run_arith_const(noteq)
self.run_arith(noteq)
def test_abs(self):
def my_abs(x):
return abs(x)
im = -self.colour
self.run_unary([im], my_abs)
def test_lshift(self):
def my_lshift(x):
# python doesn't allow float << int
if isinstance(x, float):
x = int(x)
return x << 2
# we don't support constant << image, treat as a unary
self.run_unary(self.all_images, my_lshift, fmt=noncomplex_formats)
def test_rshift(self):
def my_rshift(x):
# python doesn't allow float >> int
if isinstance(x, float):
x = int(x)
return x >> 2
# we don't support constant >> image, treat as a unary
self.run_unary(self.all_images, my_rshift, fmt=noncomplex_formats)
def test_mod(self):
def my_mod(x):
return x % 2
# we don't support constant % image, treat as a unary
self.run_unary(self.all_images, my_mod, fmt=noncomplex_formats)
def test_pos(self):
def my_pos(x):
return +x
self.run_unary(self.all_images, my_pos)
def test_neg(self):
def my_neg(x):
return -x
self.run_unary(self.all_images, my_neg)
def test_invert(self):
def my_invert(x):
if isinstance(x, float):
x = int(x)
return ~x & 0xff
# ~image is trimmed to image max so it's hard to test for all formats
# just test uchar
self.run_unary(self.all_images, my_invert,
fmt=[pyvips.BandFormat.UCHAR])
# test the rest of VipsArithmetic
def test_avg(self):
im = pyvips.Image.black(50, 100)
test = im.insert(im + 100, 50, 0, expand=True)
for fmt in all_formats:
assert pytest.approx(test.cast(fmt).avg()) == 50
def test_deviate(self):
im = pyvips.Image.black(50, 100)
test = im.insert(im + 100, 50, 0, expand=True)
for fmt in noncomplex_formats:
assert pytest.approx(test.cast(fmt).deviate(), abs=0.01) == 50
def test_polar(self):
im = pyvips.Image.black(100, 100) + 100
im = im.complexform(im)
im = im.polar()
assert pytest.approx(im.real().avg()) == 100 * 2 ** 0.5
assert pytest.approx(im.imag().avg()) == 45
def test_rect(self):
im = pyvips.Image.black(100, 100)
im = (im + 100 * 2 ** 0.5).complexform(im + 45)
im = im.rect()
assert pytest.approx(im.real().avg()) == 100
assert pytest.approx(im.imag().avg()) == 100
def test_conjugate(self):
im = pyvips.Image.black(100, 100) + 100
im = im.complexform(im)
im = im.conj()
assert pytest.approx(im.real().avg()) == 100
assert pytest.approx(im.imag().avg()) == -100
def test_histfind(self):
im = pyvips.Image.black(50, 100)
test = im.insert(im + 10, 50, 0, expand=True)
for fmt in all_formats:
hist = test.cast(fmt).hist_find()
assert_almost_equal_objects(hist(0, 0), [5000])
assert_almost_equal_objects(hist(10, 0), [5000])
assert_almost_equal_objects(hist(5, 0), [0])
test = test * [1, 2, 3]
for fmt in all_formats:
hist = test.cast(fmt).hist_find(band=0)
assert_almost_equal_objects(hist(0, 0), [5000])
assert_almost_equal_objects(hist(10, 0), [5000])
assert_almost_equal_objects(hist(5, 0), [0])
hist = test.cast(fmt).hist_find(band=1)
assert_almost_equal_objects(hist(0, 0), [5000])
assert_almost_equal_objects(hist(20, 0), [5000])
assert_almost_equal_objects(hist(5, 0), [0])
def test_histfind_indexed(self):
im = pyvips.Image.black(50, 100)
test = im.insert(im + 10, 50, 0, expand=True)
index = test // 10
for x in noncomplex_formats:
for y in [pyvips.BandFormat.UCHAR, pyvips.BandFormat.USHORT]:
a = test.cast(x)
b = index.cast(y)
hist = a.hist_find_indexed(b)
assert_almost_equal_objects(hist(0, 0), [0])
assert_almost_equal_objects(hist(1, 0), [50000])
def test_histfind_ndim(self):
im = pyvips.Image.black(100, 100) + [1, 2, 3]
for fmt in noncomplex_formats:
hist = im.cast(fmt).hist_find_ndim()
assert_almost_equal_objects(hist(0, 0)[0], 10000)
assert_almost_equal_objects(hist(5, 5)[5], 0)
hist = im.cast(fmt).hist_find_ndim(bins=1)
assert_almost_equal_objects(hist(0, 0)[0], 10000)
assert hist.width == 1
assert hist.height == 1
assert hist.bands == 1
def test_hough_circle(self):
test = pyvips.Image.black(100, 100).draw_circle(100, 50, 50, 40)
for fmt in all_formats:
im = test.cast(fmt)
hough = im.hough_circle(min_radius=35, max_radius=45)
v, x, y = hough.maxpos()
vec = hough(x, y)
r = vec.index(v) + 35
assert pytest.approx(x) == 50
assert pytest.approx(y) == 50
assert pytest.approx(r) == 40
@pytest.mark.skipif(not pyvips.base.at_least_libvips(8, 7),
reason="requires libvips >= 8.7")
def test_hough_line(self):
# hough_line changed the way it codes parameter space in 8.7 ... don't
# test earlier versions
test = pyvips.Image.black(100, 100).draw_line(100, 10, 90, 90, 10)
for fmt in all_formats:
im = test.cast(fmt)
hough = im.hough_line()
v, x, y = hough.maxpos()
angle = 180.0 * x // hough.width
distance = test.height * y // hough.height
assert pytest.approx(angle) == 45
assert pytest.approx(distance) == 70
def test_sin(self):
def my_sin(x):
if isinstance(x, pyvips.Image):
return x.sin()
else:
return math.sin(math.radians(x))
self.run_unary(self.all_images, my_sin, fmt=noncomplex_formats)
def test_cos(self):
def my_cos(x):
if isinstance(x, pyvips.Image):
return x.cos()
else:
return math.cos(math.radians(x))
self.run_unary(self.all_images, my_cos, fmt=noncomplex_formats)
def test_tan(self):
def my_tan(x):
if isinstance(x, pyvips.Image):
return x.tan()
else:
return math.tan(math.radians(x))
self.run_unary(self.all_images, my_tan, fmt=noncomplex_formats)
def test_asin(self):
def my_asin(x):
if isinstance(x, pyvips.Image):
return x.asin()
else:
return math.degrees(math.asin(x))
im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0
self.run_unary([im], my_asin, fmt=noncomplex_formats)
def test_acos(self):
def my_acos(x):
if isinstance(x, pyvips.Image):
return x.acos()
else:
return math.degrees(math.acos(x))
im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0
self.run_unary([im], my_acos, fmt=noncomplex_formats)
def test_atan(self):
def my_atan(x):
if isinstance(x, pyvips.Image):
return x.atan()
else:
return math.degrees(math.atan(x))
im = (pyvips.Image.black(100, 100) + [1, 2, 3]) / 3.0
self.run_unary([im], my_atan, fmt=noncomplex_formats)
def test_log(self):
def my_log(x):
if isinstance(x, pyvips.Image):
return x.log()
else:
return math.log(x)
self.run_unary(self.all_images, my_log, fmt=noncomplex_formats)
def test_log10(self):
def my_log10(x):
if isinstance(x, pyvips.Image):
return x.log10()
else:
return math.log10(x)
self.run_unary(self.all_images, my_log10, fmt=noncomplex_formats)
def test_exp(self):
def my_exp(x):
if isinstance(x, pyvips.Image):
return x.exp()
else:
return math.exp(x)
self.run_unary(self.all_images, my_exp, fmt=noncomplex_formats)
def test_exp10(self):
def my_exp10(x):
if isinstance(x, pyvips.Image):
return x.exp10()
else:
return math.pow(10, x)
self.run_unary(self.all_images, my_exp10, fmt=noncomplex_formats)
def test_floor(self):
def my_floor(x):
if isinstance(x, pyvips.Image):
return x.floor()
else:
return math.floor(x)
self.run_unary(self.all_images, my_floor)
def test_ceil(self):
def my_ceil(x):
if isinstance(x, pyvips.Image):
return x.ceil()
else:
return math.ceil(x)
self.run_unary(self.all_images, my_ceil)
def test_rint(self):
def my_rint(x):
if isinstance(x, pyvips.Image):
return x.rint()
else:
return round(x)
self.run_unary(self.all_images, my_rint)
def test_sign(self):
def my_sign(x):
if isinstance(x, pyvips.Image):
return x.sign()
else:
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0
self.run_unary(self.all_images, my_sign)
def test_max(self):
test = pyvips.Image.black(100, 100).draw_rect(100, 40, 50, 1, 1)
for fmt in all_formats:
v = test.cast(fmt).max()
assert pytest.approx(v) == 100
v, x, y = test.cast(fmt).maxpos()
assert pytest.approx(v) == 100
assert pytest.approx(x) == 40
assert pytest.approx(y) == 50
def test_min(self):
test = (pyvips.Image.black(100, 100) + 100).draw_rect(0, 40, 50, 1, 1)
for fmt in all_formats:
v = test.cast(fmt).min()
assert pytest.approx(v) == 0
v, x, y = test.cast(fmt).minpos()
assert pytest.approx(v) == 0
assert pytest.approx(x) == 40
assert pytest.approx(y) == 50
def test_measure(self):
im = pyvips.Image.black(50, 50)
test = im.insert(im + 10, 50, 0, expand=True)
for x in noncomplex_formats:
a = test.cast(x)
matrix = a.measure(2, 1)
[p1] = matrix(0, 0)
[p2] = matrix(0, 1)
assert pytest.approx(p1) == 0
assert pytest.approx(p2) == 10
def test_find_trim(self):
if pyvips.type_find("VipsOperation", "find_trim") != 0:
im = pyvips.Image.black(50, 60) + 100
test = im.embed(10, 20, 200, 300, extend="white")
for x in unsigned_formats + float_formats:
a = test.cast(x)
left, top, width, height = a.find_trim()
assert left == 10
assert top == 20
assert width == 50
assert height == 60
test_rgb = test.bandjoin([test, test])
left, top, width, height = test_rgb.find_trim(background=[255, 255,
255])
assert left == 10
assert top == 20
assert width == 50
assert height == 60
def test_profile(self):
test = pyvips.Image.black(100, 100).draw_rect(100, 40, 50, 1, 1)
for fmt in noncomplex_formats:
columns, rows = test.cast(fmt).profile()
v, x, y = columns.minpos()
assert pytest.approx(v) == 50
assert pytest.approx(x) == 40
assert pytest.approx(y) == 0
v, x, y = rows.minpos()
assert pytest.approx(v) == 40
assert pytest.approx(x) == 0
assert pytest.approx(y) == 50
def test_project(self):
im = pyvips.Image.black(50, 50)
test = im.insert(im + 10, 50, 0, expand=True)
for fmt in noncomplex_formats:
columns, rows = test.cast(fmt).project()
assert_almost_equal_objects(columns(10, 0), [0])
assert_almost_equal_objects(columns(70, 0), [50 * 10])
assert_almost_equal_objects(rows(0, 10), [50 * 10])
def test_stats(self):
im = pyvips.Image.black(50, 50)
test = im.insert(im + 10, 50, 0, expand=True)
for x in noncomplex_formats:
a = test.cast(x)
matrix = a.stats()
assert_almost_equal_objects(matrix(0, 0), [a.min()])
assert_almost_equal_objects(matrix(1, 0), [a.max()])
assert_almost_equal_objects(matrix(2, 0), [50 * 50 * 10])
assert_almost_equal_objects(matrix(3, 0), [50 * 50 * 100])
assert_almost_equal_objects(matrix(4, 0), [a.avg()])
assert_almost_equal_objects(matrix(5, 0), [a.deviate()])
assert_almost_equal_objects(matrix(0, 1), [a.min()])
assert_almost_equal_objects(matrix(1, 1), [a.max()])
assert_almost_equal_objects(matrix(2, 1), [50 * 50 * 10])
assert_almost_equal_objects(matrix(3, 1), [50 * 50 * 100])
assert_almost_equal_objects(matrix(4, 1), [a.avg()])
assert_almost_equal_objects(matrix(5, 1), [a.deviate()])
def test_sum(self):
for fmt in all_formats:
im = pyvips.Image.black(50, 50)
im2 = [(im + x).cast(fmt) for x in range(0, 100, 10)]
im3 = pyvips.Image.sum(im2)
assert pytest.approx(im3.max()) == sum(range(0, 100, 10))
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,155 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import JPEG_FILE, SRGB_FILE, colour_colourspaces, \
mono_colourspaces, assert_almost_equal_objects
class TestColour:
def test_colourspace(self):
# mid-grey in Lab ... put 42 in the extra band, it should be copied
# unmodified
test = pyvips.Image.black(100, 100) + [50, 0, 0, 42]
test = test.copy(interpretation=pyvips.Interpretation.LAB)
# a long series should come in a circle
im = test
for col in colour_colourspaces + [pyvips.Interpretation.LAB]:
im = im.colourspace(col)
assert im.interpretation == col
for i in range(0, 4):
min_l = im.extract_band(i).min()
max_h = im.extract_band(i).max()
assert pytest.approx(min_l) == max_h
pixel = im(10, 10)
assert pytest.approx(pixel[3], 0.01) == 42
# alpha won't be equal for RGB16, but it should be preserved if we go
# there and back
im = im.colourspace(pyvips.Interpretation.RGB16)
im = im.colourspace(pyvips.Interpretation.LAB)
before = test(10, 10)
after = im(10, 10)
assert_almost_equal_objects(before, after, threshold=0.1)
# go between every pair of colour spaces
for start in colour_colourspaces:
for end in colour_colourspaces:
im = test.colourspace(start)
im2 = im.colourspace(end)
im3 = im2.colourspace(pyvips.Interpretation.LAB)
before = test(10, 10)
after = im3(10, 10)
assert_almost_equal_objects(before, after, threshold=0.1)
# test Lab->XYZ on mid-grey
# checked against http://www.brucelindbloom.com
im = test.colourspace(pyvips.Interpretation.XYZ)
after = im(10, 10)
assert_almost_equal_objects(after, [17.5064, 18.4187, 20.0547, 42])
# grey->colour->grey should be equal
for mono_fmt in mono_colourspaces:
test_grey = test.colourspace(mono_fmt)
im = test_grey
for col in colour_colourspaces + [mono_fmt]:
im = im.colourspace(col)
assert im.interpretation == col
[before, alpha_before] = test_grey(10, 10)
[after, alpha_after] = im(10, 10)
assert abs(alpha_after - alpha_before) < 1
if mono_fmt == pyvips.Interpretation.GREY16:
# GREY16 can wind up rather different due to rounding
assert abs(after - before) < 30
else:
# but 8-bit we should hit exactly
assert abs(after - before) < 1
# test results from Bruce Lindbloom's calculator:
# http://www.brucelindbloom.com
def test_dE00(self):
# put 42 in the extra band, it should be copied unmodified
reference = pyvips.Image.black(100, 100) + [50, 10, 20, 42]
reference = reference.copy(interpretation=pyvips.Interpretation.LAB)
sample = pyvips.Image.black(100, 100) + [40, -20, 10]
sample = sample.copy(interpretation=pyvips.Interpretation.LAB)
difference = reference.dE00(sample)
result, alpha = difference(10, 10)
assert pytest.approx(result, 0.001) == 30.238
assert pytest.approx(alpha, 0.001) == 42.0
def test_dE76(self):
# put 42 in the extra band, it should be copied unmodified
reference = pyvips.Image.black(100, 100) + [50, 10, 20, 42]
reference = reference.copy(interpretation=pyvips.Interpretation.LAB)
sample = pyvips.Image.black(100, 100) + [40, -20, 10]
sample = sample.copy(interpretation=pyvips.Interpretation.LAB)
difference = reference.dE76(sample)
result, alpha = difference(10, 10)
assert pytest.approx(result, 0.001) == 33.166
assert pytest.approx(alpha, 0.001) == 42.0
# the vips CMC calculation is based on distance in a colorspace
# derived from the CMC formula, so it won't match exactly ...
# see vips_LCh2CMC() for details
def test_dECMC(self):
reference = pyvips.Image.black(100, 100) + [50, 10, 20, 42]
reference = reference.copy(interpretation=pyvips.Interpretation.LAB)
sample = pyvips.Image.black(100, 100) + [55, 11, 23]
sample = sample.copy(interpretation=pyvips.Interpretation.LAB)
difference = reference.dECMC(sample)
result, alpha = difference(10, 10)
assert abs(result - 4.97) < 0.5
assert pytest.approx(alpha, 0.001) == 42.0
def test_icc(self):
test = pyvips.Image.new_from_file(JPEG_FILE)
im = test.icc_import().icc_export()
assert im.dE76(test).max() < 6
im = test.icc_import()
im2 = im.icc_export(depth=16)
assert im2.format == pyvips.BandFormat.USHORT
im3 = im2.icc_import()
assert (im - im3).abs().max() < 3
im = test.icc_import(intent=pyvips.Intent.ABSOLUTE)
im2 = im.icc_export(intent=pyvips.Intent.ABSOLUTE)
assert im2.dE76(test).max() < 6
im = test.icc_import()
im2 = im.icc_export(output_profile=SRGB_FILE)
im3 = im.colourspace(pyvips.Interpretation.SRGB)
assert im2.dE76(im3).max() < 6
before_profile = test.get("icc-profile-data")
im = test.icc_transform(SRGB_FILE)
after_profile = im.get("icc-profile-data")
im2 = test.icc_import()
im3 = im2.colourspace(pyvips.Interpretation.SRGB)
assert im.dE76(im3).max() < 6
assert len(before_profile) != len(after_profile)
im = test.icc_import(input_profile=SRGB_FILE)
im2 = test.icc_import()
assert 6 < im.dE76(im2).max()
im = test.icc_import(pcs=pyvips.PCS.XYZ)
assert im.interpretation == pyvips.Interpretation.XYZ
im = test.icc_import()
assert im.interpretation == pyvips.Interpretation.LAB
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,767 @@
# vim: set fileencoding=utf-8 :
from functools import reduce
import pytest
import pyvips
from helpers import JPEG_FILE, unsigned_formats, \
signed_formats, float_formats, int_formats, \
noncomplex_formats, all_formats, max_value, \
sizeof_format, rot45_angles, rot45_angle_bonds, \
rot_angles, rot_angle_bonds, run_cmp, run_cmp2, \
assert_almost_equal_objects
class TestConversion:
# run a function on an image,
# 50,50 and 10,10 should have different values on the test image
# don't loop over band elements
def run_image_pixels(self, message, im, fn):
run_cmp(message, im, 50, 50, fn)
run_cmp(message, im, 10, 10, fn)
# run a function on a pair of images
# 50,50 and 10,10 should have different values on the test image
# don't loop over band elements
def run_image_pixels2(self, message, left, right, fn):
run_cmp2(message, left, right, 50, 50, fn)
run_cmp2(message, left, right, 10, 10, fn)
def run_unary(self, images, fn, fmt=all_formats):
[self.run_image_pixels(fn.__name__ + (' %s' % y), x.cast(y), fn)
for x in images for y in fmt]
def run_binary(self, images, fn, fmt=all_formats):
[self.run_image_pixels2(fn.__name__ + (' %s %s' % (y, z)),
x.cast(y), x.cast(z), fn)
for x in images for y in fmt for z in fmt]
@classmethod
def setup_class(cls):
im = pyvips.Image.mask_ideal(100, 100, 0.5,
reject=True, optical=True)
cls.colour = (im * [1, 2, 3] + [2, 3, 4]).copy(interpretation="srgb")
cls.mono = cls.colour[1].copy(interpretation="b-w")
cls.all_images = [cls.mono, cls.colour]
cls.image = pyvips.Image.jpegload(JPEG_FILE)
def test_band_and(self):
def band_and(x):
if isinstance(x, pyvips.Image):
return x.bandand()
else:
return [reduce(lambda a, b: int(a) & int(b), x)]
self.run_unary(self.all_images, band_and, fmt=int_formats)
def test_band_or(self):
def band_or(x):
if isinstance(x, pyvips.Image):
return x.bandor()
else:
return [reduce(lambda a, b: int(a) | int(b), x)]
self.run_unary(self.all_images, band_or, fmt=int_formats)
def test_band_eor(self):
def band_eor(x):
if isinstance(x, pyvips.Image):
return x.bandeor()
else:
return [reduce(lambda a, b: int(a) ^ int(b), x)]
self.run_unary(self.all_images, band_eor, fmt=int_formats)
def test_bandjoin(self):
def bandjoin(x, y):
if isinstance(x, pyvips.Image) and isinstance(y, pyvips.Image):
return x.bandjoin(y)
else:
return x + y
self.run_binary(self.all_images, bandjoin)
def test_bandjoin_const(self):
x = self.colour.bandjoin(1)
assert x.bands == 4
assert x[3].avg() == 1
x = self.colour.bandjoin([1, 2])
assert x.bands == 5
assert x[3].avg() == 1
assert x[4].avg() == 2
def test_bandmean(self):
def bandmean(x):
if isinstance(x, pyvips.Image):
return x.bandmean()
else:
return [sum(x) // len(x)]
self.run_unary(self.all_images, bandmean, fmt=noncomplex_formats)
def test_bandrank(self):
def median(x, y):
joined = [[a, b] for a, b in zip(x, y)]
# .sort() isn't a function, so we have to run this as a separate
# pass
[z.sort() for z in joined]
return [z[len(z) // 2] for z in joined]
def bandrank(x, y):
if isinstance(x, pyvips.Image) and isinstance(y, pyvips.Image):
return x.bandrank([y])
else:
return median(x, y)
self.run_binary(self.all_images, bandrank, fmt=noncomplex_formats)
# we can mix images and constants, and set the index arg
a = self.mono.bandrank([2], index=0)
b = (self.mono < 2).ifthenelse(self.mono, 2)
assert (a - b).abs().min() == 0
def test_cache(self):
def cache(x):
if isinstance(x, pyvips.Image):
return x.cache()
else:
return x
self.run_unary(self.all_images, cache)
def test_copy(self):
x = self.colour.copy(interpretation=pyvips.Interpretation.LAB)
assert x.interpretation == pyvips.Interpretation.LAB
x = self.colour.copy(xres=42)
assert x.xres == 42
x = self.colour.copy(yres=42)
assert x.yres == 42
x = self.colour.copy(xoffset=42)
assert x.xoffset == 42
x = self.colour.copy(yoffset=42)
assert x.yoffset == 42
x = self.colour.copy(coding=pyvips.Coding.NONE)
assert x.coding == pyvips.Coding.NONE
def test_bandfold(self):
x = self.mono.bandfold()
assert x.width == 1
assert x.bands == self.mono.width
y = x.bandunfold()
assert y.width == self.mono.width
assert y.bands == 1
assert x.avg() == y.avg()
x = self.mono.bandfold(factor=2)
assert x.width == self.mono.width / 2
assert x.bands == 2
y = x.bandunfold(factor=2)
assert y.width == self.mono.width
assert y.bands == 1
assert x.avg() == y.avg()
def test_byteswap(self):
x = self.mono.cast("ushort")
y = x.byteswap().byteswap()
assert x.width == y.width
assert x.height == y.height
assert x.bands == y.bands
assert x.avg() == y.avg()
def test_embed(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
im = test.embed(20, 20,
self.colour.width + 40,
self.colour.height + 40)
pixel = im(10, 10)
assert_almost_equal_objects(pixel, [0, 0, 0])
pixel = im(30, 30)
assert_almost_equal_objects(pixel, [2, 3, 4])
pixel = im(im.width - 10, im.height - 10)
assert_almost_equal_objects(pixel, [0, 0, 0])
im = test.embed(20, 20,
self.colour.width + 40,
self.colour.height + 40,
extend=pyvips.Extend.COPY)
pixel = im(10, 10)
assert_almost_equal_objects(pixel, [2, 3, 4])
pixel = im(im.width - 10, im.height - 10)
assert_almost_equal_objects(pixel, [2, 3, 4])
im = test.embed(20, 20,
self.colour.width + 40,
self.colour.height + 40,
extend=pyvips.Extend.BACKGROUND,
background=[7, 8, 9])
pixel = im(10, 10)
assert_almost_equal_objects(pixel, [7, 8, 9])
pixel = im(im.width - 10, im.height - 10)
assert_almost_equal_objects(pixel, [7, 8, 9])
im = test.embed(20, 20,
self.colour.width + 40,
self.colour.height + 40,
extend=pyvips.Extend.WHITE)
pixel = im(10, 10)
# uses 255 in all bytes of ints, 255.0 for float
pixel = [int(x) & 0xff for x in pixel]
assert_almost_equal_objects(pixel, [255, 255, 255])
pixel = im(im.width - 10, im.height - 10)
pixel = [int(x) & 0xff for x in pixel]
assert_almost_equal_objects(pixel, [255, 255, 255])
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "gravity") == 0,
reason="no gravity in this vips, skipping test")
def test_gravity(self):
im = pyvips.Image.black(1, 1) + 255
positions = [
['centre', 1, 1],
['north', 1, 0],
['south', 1, 2],
['east', 2, 1],
['west', 0, 1],
['north-east', 2, 0],
['south-east', 2, 2],
['south-west', 0, 2],
['north-west', 0, 0]
]
for direction, x, y in positions:
im2 = im.gravity(direction, 3, 3)
assert_almost_equal_objects(im2(x, y), [255])
assert_almost_equal_objects(im2.avg(), 255.0 / 9.0)
def test_extract(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
pixel = test(30, 30)
assert_almost_equal_objects(pixel, [2, 3, 4])
sub = test.extract_area(25, 25, 10, 10)
pixel = sub(5, 5)
assert_almost_equal_objects(pixel, [2, 3, 4])
sub = test.extract_band(1, n=2)
pixel = sub(30, 30)
assert_almost_equal_objects(pixel, [3, 4])
def test_slice(self):
test = self.colour
bands = [x.avg() for x in test]
x = test[0].avg()
assert x == bands[0]
x = test[-1].avg()
assert_almost_equal_objects(x, bands[2])
x = [i.avg() for i in test[1:3]]
assert_almost_equal_objects(x, bands[1:3])
x = [i.avg() for i in test[1:-1]]
assert_almost_equal_objects(x, bands[1:-1])
x = [i.avg() for i in test[:2]]
assert_almost_equal_objects(x, bands[:2])
x = [i.avg() for i in test[1:]]
assert_almost_equal_objects(x, bands[1:])
x = [i.avg() for i in test[-1]]
assert_almost_equal_objects(x, bands[-1])
def test_crop(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
pixel = test(30, 30)
assert_almost_equal_objects(pixel, [2, 3, 4])
sub = test.crop(25, 25, 10, 10)
pixel = sub(5, 5)
assert_almost_equal_objects(pixel, [2, 3, 4])
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "smartcrop") == 0,
reason="no smartcrop, skipping test")
def test_smartcrop(self):
test = self.image.smartcrop(100, 100)
assert test.width == 100
assert test.height == 100
def test_falsecolour(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
im = test.falsecolour()
assert im.width == test.width
assert im.height == test.height
assert im.bands == 3
pixel = im(30, 30)
assert_almost_equal_objects(pixel, [20, 0, 41])
def test_flatten(self):
for fmt in unsigned_formats + [pyvips.BandFormat.SHORT,
pyvips.BandFormat.INT] + float_formats:
mx = 255
alpha = mx / 2.0
nalpha = mx - alpha
test = self.colour.bandjoin(alpha).cast(fmt)
pixel = test(30, 30)
predict = [int(x) * alpha / mx for x in pixel[:-1]]
im = test.flatten()
assert im.bands == 3
pixel = im(30, 30)
for x, y in zip(pixel, predict):
# we use float arithetic for int and uint, so the rounding
# differs ... don't require huge accuracy
assert abs(x - y) < 2
im = test.flatten(background=[100, 100, 100])
pixel = test(30, 30)
predict = [int(x) * alpha / mx + (100 * nalpha) / mx
for x in pixel[:-1]]
assert im.bands == 3
pixel = im(30, 30)
for x, y in zip(pixel, predict):
assert abs(x - y) < 2
def test_premultiply(self):
for fmt in unsigned_formats + [pyvips.BandFormat.SHORT,
pyvips.BandFormat.INT] + float_formats:
mx = 255
alpha = mx / 2.0
test = self.colour.bandjoin(alpha).cast(fmt)
pixel = test(30, 30)
predict = [int(x) * alpha / mx for x in pixel[:-1]] + [alpha]
im = test.premultiply()
assert im.bands == test.bands
pixel = im(30, 30)
for x, y in zip(pixel, predict):
# we use float arithetic for int and uint, so the rounding
# differs ... don't require huge accuracy
assert abs(x - y) < 2
@pytest.mark.skipif(pyvips.type_find("VipsConversion", "composite") == 0,
reason="no composite support, skipping test")
def test_composite(self):
# 50% transparent image
overlay = self.colour.bandjoin(128)
base = self.colour + 100
comp = base.composite(overlay, "over")
assert_almost_equal_objects(comp(0, 0), [51.8, 52.8, 53.8, 255],
threshold=0.1)
def test_unpremultiply(self):
for fmt in unsigned_formats + [pyvips.BandFormat.SHORT,
pyvips.BandFormat.INT] + float_formats:
mx = 255
alpha = mx / 2.0
test = self.colour.bandjoin(alpha).cast(fmt)
pixel = test(30, 30)
predict = [int(x) / (alpha / mx) for x in pixel[:-1]] + [alpha]
im = test.unpremultiply()
assert im.bands == test.bands
pixel = im(30, 30)
for x, y in zip(pixel, predict):
# we use float arithetic for int and uint, so the rounding
# differs ... don't require huge accuracy
assert abs(x - y) < 2
def test_flip(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
result = test.fliphor()
result = result.flipver()
result = result.fliphor()
result = result.flipver()
diff = (test - result).abs().max()
assert diff == 0
def test_gamma(self):
exponent = 2.4
for fmt in noncomplex_formats:
mx = max_value[fmt]
test = (self.colour + mx / 2.0).cast(fmt)
norm = mx ** exponent / mx
result = test.gamma()
before = test(30, 30)
after = result(30, 30)
predict = [x ** exponent / norm for x in before]
for a, b in zip(after, predict):
# ie. less than 1% error, rounding on 7-bit images
# means this is all we can expect
assert abs(a - b) < mx / 100.0
exponent = 1.2
for fmt in noncomplex_formats:
mx = max_value[fmt]
test = (self.colour + mx / 2.0).cast(fmt)
norm = mx ** exponent / mx
result = test.gamma(exponent=1.0 / 1.2)
before = test(30, 30)
after = result(30, 30)
predict = [x ** exponent / norm for x in before]
for a, b in zip(after, predict):
# ie. less than 1% error, rounding on 7-bit images
# means this is all we can expect
assert abs(a - b) < mx / 100.0
def test_grid(self):
test = self.colour.replicate(1, 12)
assert test.width == self.colour.width
assert test.height == self.colour.height * 12
for fmt in all_formats:
im = test.cast(fmt)
result = im.grid(test.width, 3, 4)
assert result.width == self.colour.width * 3
assert result.height == self.colour.height * 4
before = im(10, 10)
after = result(10 + test.width * 2, 10 + test.width * 2)
assert_almost_equal_objects(before, after)
before = im(50, 50)
after = result(50 + test.width * 2, 50 + test.width * 2)
assert_almost_equal_objects(before, after)
def test_ifthenelse(self):
test = self.mono > 3
for x in all_formats:
for y in all_formats:
t = (self.colour + 10).cast(x)
e = self.colour.cast(y)
r = test.ifthenelse(t, e)
assert r.width == self.colour.width
assert r.height == self.colour.height
assert r.bands == self.colour.bands
predict = e(10, 10)
result = r(10, 10)
assert_almost_equal_objects(result, predict)
predict = t(50, 50)
result = r(50, 50)
assert_almost_equal_objects(result, predict)
test = self.colour > 3
for x in all_formats:
for y in all_formats:
t = (self.mono + 10).cast(x)
e = self.mono.cast(y)
r = test.ifthenelse(t, e)
assert r.width == self.colour.width
assert r.height == self.colour.height
assert r.bands == self.colour.bands
cp = test(10, 10)
tp = t(10, 10) * 3
ep = e(10, 10) * 3
predict = [te if ce != 0 else ee
for ce, te, ee in zip(cp, tp, ep)]
result = r(10, 10)
assert_almost_equal_objects(result, predict)
cp = test(50, 50)
tp = t(50, 50) * 3
ep = e(50, 50) * 3
predict = [te if ce != 0 else ee
for ce, te, ee in zip(cp, tp, ep)]
result = r(50, 50)
assert_almost_equal_objects(result, predict)
test = self.colour > 3
for x in all_formats:
for y in all_formats:
t = (self.mono + 10).cast(x)
e = self.mono.cast(y)
r = test.ifthenelse(t, e, blend=True)
assert r.width == self.colour.width
assert r.height == self.colour.height
assert r.bands == self.colour.bands
result = r(10, 10)
assert_almost_equal_objects(result, [3, 3, 13])
test = self.mono > 3
r = test.ifthenelse([1, 2, 3], self.colour)
assert r.width == self.colour.width
assert r.height == self.colour.height
assert r.bands == self.colour.bands
assert r.format == self.colour.format
assert r.interpretation == self.colour.interpretation
result = r(10, 10)
assert_almost_equal_objects(result, [2, 3, 4])
result = r(50, 50)
assert_almost_equal_objects(result, [1, 2, 3])
test = self.mono
r = test.ifthenelse([1, 2, 3], self.colour, blend=True)
assert r.width == self.colour.width
assert r.height == self.colour.height
assert r.bands == self.colour.bands
assert r.format == self.colour.format
assert r.interpretation == self.colour.interpretation
result = r(10, 10)
assert_almost_equal_objects(result, [2, 3, 4], threshold=0.1)
result = r(50, 50)
assert_almost_equal_objects(result, [3.0, 4.9, 6.9], threshold=0.1)
def test_insert(self):
for x in all_formats:
for y in all_formats:
main = self.mono.cast(x)
sub = self.colour.cast(y)
r = main.insert(sub, 10, 10)
assert r.width == main.width
assert r.height == main.height
assert r.bands == sub.bands
a = r(10, 10)
b = sub(0, 0)
assert_almost_equal_objects(a, b)
a = r(0, 0)
b = main(0, 0) * 3
assert_almost_equal_objects(a, b)
for x in all_formats:
for y in all_formats:
main = self.mono.cast(x)
sub = self.colour.cast(y)
r = main.insert(sub, 10, 10, expand=True, background=100)
assert r.width == main.width + 10
assert r.height == main.height + 10
assert r.bands == sub.bands
a = r(r.width - 5, 5)
assert_almost_equal_objects(a, [100, 100, 100])
def test_arrayjoin(self):
max_width = 0
max_height = 0
max_bands = 0
for image in self.all_images:
if image.width > max_width:
max_width = image.width
if image.height > max_height:
max_height = image.height
if image.bands > max_bands:
max_bands = image.bands
im = pyvips.Image.arrayjoin(self.all_images)
assert im.width == max_width * len(self.all_images)
assert im.height == max_height
assert im.bands == max_bands
im = pyvips.Image.arrayjoin(self.all_images, across=1)
assert im.width == max_width
assert im.height == max_height * len(self.all_images)
assert im.bands == max_bands
im = pyvips.Image.arrayjoin(self.all_images, shim=10)
assert im.width == max_width * len(self.all_images) + 10 * (len(self.all_images) - 1) # noqa: E501
assert im.height == max_height
assert im.bands == max_bands
def test_msb(self):
for fmt in unsigned_formats:
mx = max_value[fmt]
size = sizeof_format[fmt]
test = (self.colour + mx / 8.0).cast(fmt)
im = test.msb()
before = test(10, 10)
predict = [int(x) >> ((size - 1) * 8) for x in before]
result = im(10, 10)
assert_almost_equal_objects(result, predict)
before = test(50, 50)
predict = [int(x) >> ((size - 1) * 8) for x in before]
result = im(50, 50)
assert_almost_equal_objects(result, predict)
for fmt in signed_formats:
mx = max_value[fmt]
size = sizeof_format[fmt]
test = (self.colour + mx / 8.0).cast(fmt)
im = test.msb()
before = test(10, 10)
predict = [128 + (int(x) >> ((size - 1) * 8)) for x in before]
result = im(10, 10)
assert_almost_equal_objects(result, predict)
before = test(50, 50)
predict = [128 + (int(x) >> ((size - 1) * 8)) for x in before]
result = im(50, 50)
assert_almost_equal_objects(result, predict)
for fmt in unsigned_formats:
mx = max_value[fmt]
size = sizeof_format[fmt]
test = (self.colour + mx / 8.0).cast(fmt)
im = test.msb(band=1)
before = [test(10, 10)[1]]
predict = [int(x) >> ((size - 1) * 8) for x in before]
result = im(10, 10)
assert_almost_equal_objects(result, predict)
before = [test(50, 50)[1]]
predict = [int(x) >> ((size - 1) * 8) for x in before]
result = im(50, 50)
assert_almost_equal_objects(result, predict)
def test_recomb(self):
array = [[0.2, 0.5, 0.3]]
def recomb(x):
if isinstance(x, pyvips.Image):
return x.recomb(array)
else:
sum = 0
for i, c in zip(array[0], x):
sum += i * c
return [sum]
self.run_unary([self.colour], recomb, fmt=noncomplex_formats)
def test_replicate(self):
for fmt in all_formats:
im = self.colour.cast(fmt)
test = im.replicate(10, 10)
assert test.width == self.colour.width * 10
assert test.height == self.colour.height * 10
before = im(10, 10)
after = test(10 + im.width * 2, 10 + im.width * 2)
assert_almost_equal_objects(before, after)
before = im(50, 50)
after = test(50 + im.width * 2, 50 + im.width * 2)
assert_almost_equal_objects(before, after)
def test_rot45(self):
# test has a quarter-circle in the bottom right
test = self.colour.crop(0, 0, 51, 51)
for fmt in all_formats:
im = test.cast(fmt)
im2 = im.rot45()
before = im(50, 50)
after = im2(25, 50)
assert_almost_equal_objects(before, after)
for a, b in zip(rot45_angles, rot45_angle_bonds):
im2 = im.rot45(angle=a)
after = im2.rot45(angle=b)
diff = (after - im).abs().max()
assert diff == 0
def test_rot(self):
# test has a quarter-circle in the bottom right
test = self.colour.crop(0, 0, 51, 51)
for fmt in all_formats:
im = test.cast(fmt)
im2 = im.rot(pyvips.Angle.D90)
before = im(50, 50)
after = im2(0, 50)
assert_almost_equal_objects(before, after)
for a, b in zip(rot_angles, rot_angle_bonds):
im2 = im.rot(a)
after = im2.rot(b)
diff = (after - im).abs().max()
assert diff == 0
def test_scaleimage(self):
for fmt in noncomplex_formats:
test = self.colour.cast(fmt)
im = test.scaleimage()
assert im.max() == 255
assert im.min() == 0
im = test.scaleimage(log=True)
assert im.max() == 255
def test_subsample(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
im = test.subsample(3, 3)
assert im.width == test.width // 3
assert im.height == test.height // 3
before = test(60, 60)
after = im(20, 20)
assert_almost_equal_objects(before, after)
def test_zoom(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
im = test.zoom(3, 3)
assert im.width == test.width * 3
assert im.height == test.height * 3
before = test(50, 50)
after = im(150, 150)
assert_almost_equal_objects(before, after)
def test_wrap(self):
for fmt in all_formats:
test = self.colour.cast(fmt)
im = test.wrap()
assert im.width == test.width
assert im.height == test.height
before = test(0, 0)
after = im(50, 50)
assert_almost_equal_objects(before, after)
before = test(50, 50)
after = im(0, 0)
assert_almost_equal_objects(before, after)
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,206 @@
# vim: set fileencoding=utf-8 :
import operator
import pytest
from functools import reduce
import pyvips
from helpers import noncomplex_formats, run_fn2, run_fn, \
assert_almost_equal_objects, assert_less_threshold
# point convolution
def conv(image, mask, x_position, y_position):
s = 0.0
for x in range(0, mask.width):
for y in range(0, mask.height):
m = mask(x, y)
i = image(x + x_position, y + y_position)
p = run_fn2(operator.mul, m, i)
s = run_fn2(operator.add, s, p)
return run_fn2(operator.truediv, s, mask.scale)
def compass(image, mask, x_position, y_position, n_rot, fn):
acc = []
for i in range(0, n_rot):
result = conv(image, mask, x_position, y_position)
result = run_fn(abs, result)
acc.append(result)
mask = mask.rot45()
return reduce(lambda a, b: run_fn2(fn, a, b), acc)
class TestConvolution:
@classmethod
def setup_class(cls):
im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True)
cls.colour = im * [1, 2, 3] + [2, 3, 4]
cls.colour = cls.colour.copy(interpretation=pyvips.Interpretation.SRGB)
cls.mono = cls.colour.extract_band(1)
cls.mono = cls.mono.copy(interpretation=pyvips.Interpretation.B_W)
cls.all_images = [cls.mono, cls.colour]
cls.sharp = pyvips.Image.new_from_array([[-1, -1, -1],
[-1, 16, -1],
[-1, -1, -1]], scale=8)
cls.blur = pyvips.Image.new_from_array([[1, 1, 1],
[1, 1, 1],
[1, 1, 1]], scale=9)
cls.line = pyvips.Image.new_from_array([[1, 1, 1],
[-2, -2, -2],
[1, 1, 1]])
cls.sobel = pyvips.Image.new_from_array([[1, 2, 1],
[0, 0, 0],
[-1, -2, -1]])
cls.all_masks = [cls.sharp, cls.blur, cls.line, cls.sobel]
def test_conv(self):
for im in self.all_images:
for msk in self.all_masks:
for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
convolved = im.conv(msk, precision=prec)
result = convolved(25, 50)
true = conv(im, msk, 24, 49)
assert_almost_equal_objects(result, true)
result = convolved(50, 50)
true = conv(im, msk, 49, 49)
assert_almost_equal_objects(result, true)
# don't test conva, it's still not done
def dont_est_conva(self):
for im in self.all_images:
for msk in self.all_masks:
print("msk:")
msk.matrixprint()
print("im.bands = %s" % im.bands)
convolved = im.conv(msk,
precision=pyvips.Precision.APPROXIMATE)
result = convolved(25, 50)
true = conv(im, msk, 24, 49)
print("result = %s, true = %s" % (result, true))
assert_less_threshold(result, true, 5)
result = convolved(50, 50)
true = conv(im, msk, 49, 49)
print("result = %s, true = %s" % (result, true))
assert_less_threshold(result, true, 5)
def test_compass(self):
for im in self.all_images:
for msk in self.all_masks:
for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
for times in range(1, 4):
convolved = im.compass(msk,
times=times,
angle=pyvips.Angle45.D45,
combine=pyvips.Combine.MAX,
precision=prec)
result = convolved(25, 50)
true = compass(im, msk, 24, 49, times, max)
assert_almost_equal_objects(result, true)
for im in self.all_images:
for msk in self.all_masks:
for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
for times in range(1, 4):
convolved = im.compass(msk,
times=times,
angle=pyvips.Angle45.D45,
combine=pyvips.Combine.SUM,
precision=prec)
result = convolved(25, 50)
true = compass(im, msk, 24, 49, times, operator.add)
assert_almost_equal_objects(result, true)
def test_convsep(self):
for im in self.all_images:
for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
gmask = pyvips.Image.gaussmat(2, 0.1,
precision=prec)
gmask_sep = pyvips.Image.gaussmat(2, 0.1,
separable=True,
precision=prec)
assert gmask.width == gmask.height
assert gmask_sep.width == gmask.width
assert gmask_sep.height == 1
a = im.conv(gmask, precision=prec)
b = im.convsep(gmask_sep, precision=prec)
a_point = a(25, 50)
b_point = b(25, 50)
assert_almost_equal_objects(a_point, b_point, threshold=0.1)
def test_fastcor(self):
for im in self.all_images:
for fmt in noncomplex_formats:
small = im.crop(20, 45, 10, 10).cast(fmt)
cor = im.fastcor(small)
v, x, y = cor.minpos()
assert v == 0
assert x == 25
assert y == 50
def test_spcor(self):
for im in self.all_images:
for fmt in noncomplex_formats:
small = im.crop(20, 45, 10, 10).cast(fmt)
cor = im.spcor(small)
v, x, y = cor.maxpos()
assert v == 1.0
assert x == 25
assert y == 50
def test_gaussblur(self):
for im in self.all_images:
for prec in [pyvips.Precision.INTEGER, pyvips.Precision.FLOAT]:
for i in range(5, 10):
sigma = i / 5.0
gmask = pyvips.Image.gaussmat(sigma, 0.2,
precision=prec)
a = im.conv(gmask, precision=prec)
b = im.gaussblur(sigma, min_ampl=0.2, precision=prec)
a_point = a(25, 50)
b_point = b(25, 50)
assert_almost_equal_objects(a_point, b_point,
threshold=0.1)
def test_sharpen(self):
for im in self.all_images:
for fmt in noncomplex_formats:
# old vipses used "radius", check that that still works
sharp = im.sharpen(radius=5)
for sigma in [0.5, 1, 1.5, 2]:
im = im.cast(fmt)
sharp = im.sharpen(sigma=sigma)
# hard to test much more than this
assert im.width == sharp.width
assert im.height == sharp.height
# if m1 and m2 are zero, sharpen should do nothing
sharp = im.sharpen(sigma=sigma, m1=0, m2=0)
sharp = sharp.colourspace(im.interpretation)
# print("testing sig = %g" % sigma)
# print("testing fmt = %s" % fmt)
# print("max diff = %g" % (im - sharp).abs().max())
assert (im - sharp).abs().max() == 0
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,439 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import assert_almost_equal_objects
class TestCreate:
def test_black(self):
im = pyvips.Image.black(100, 100)
assert im.width == 100
assert im.height == 100
assert im.format == pyvips.BandFormat.UCHAR
assert im.bands == 1
for i in range(0, 100):
pixel = im(i, i)
assert len(pixel) == 1
assert pixel[0] == 0
im = pyvips.Image.black(100, 100, bands=3)
assert im.width == 100
assert im.height == 100
assert im.format == pyvips.BandFormat.UCHAR
assert im.bands == 3
for i in range(0, 100):
pixel = im(i, i)
assert len(pixel) == 3
assert_almost_equal_objects(pixel, [0, 0, 0])
def test_buildlut(self):
M = pyvips.Image.new_from_array([[0, 0],
[255, 100]])
lut = M.buildlut()
assert lut.width == 256
assert lut.height == 1
assert lut.bands == 1
p = lut(0, 0)
assert p[0] == 0.0
p = lut(255, 0)
assert p[0] == 100.0
p = lut(10, 0)
assert p[0] == 100 * 10.0 / 255.0
M = pyvips.Image.new_from_array([[0, 0, 100],
[255, 100, 0],
[128, 10, 90]])
lut = M.buildlut()
assert lut.width == 256
assert lut.height == 1
assert lut.bands == 2
p = lut(0, 0)
assert_almost_equal_objects(p, [0.0, 100.0])
p = lut(64, 0)
assert_almost_equal_objects(p, [5.0, 95.0])
def test_eye(self):
im = pyvips.Image.eye(100, 90)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert im.max() == 1.0
assert im.min() == -1.0
im = pyvips.Image.eye(100, 90, uchar=True)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
assert im.max() == 255.0
assert im.min() == 0.0
def test_fractsurf(self):
im = pyvips.Image.fractsurf(100, 90, 2.5)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
def test_gaussmat(self):
im = pyvips.Image.gaussmat(1, 0.1)
assert im.width == 5
assert im.height == 5
assert im.bands == 1
assert im.format == pyvips.BandFormat.DOUBLE
assert im.max() == 20
total = im.avg() * im.width * im.height
scale = im.get("scale")
assert total == scale
p = im(im.width / 2, im.height / 2)
assert p[0] == 20.0
im = pyvips.Image.gaussmat(1, 0.1,
separable=True, precision="float")
assert im.width == 5
assert im.height == 1
assert im.bands == 1
assert im.format == pyvips.BandFormat.DOUBLE
assert im.max() == 1.0
total = im.avg() * im.width * im.height
scale = im.get("scale")
assert total == scale
p = im(im.width / 2, im.height / 2)
assert p[0] == 1.0
def test_gaussnoise(self):
im = pyvips.Image.gaussnoise(100, 90)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
im = pyvips.Image.gaussnoise(100, 90, sigma=10, mean=100)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
sigma = im.deviate()
mean = im.avg()
assert pytest.approx(sigma, 0.2) == 10
assert pytest.approx(mean, 0.2) == 100
def test_grey(self):
im = pyvips.Image.grey(100, 90)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
p = im(0, 0)
assert p[0] == 0.0
p = im(99, 0)
assert p[0] == 1.0
p = im(0, 89)
assert p[0] == 0.0
p = im(99, 89)
assert p[0] == 1.0
im = pyvips.Image.grey(100, 90, uchar=True)
assert im.width == 100
assert im.height == 90
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
p = im(0, 0)
assert p[0] == 0
p = im(99, 0)
assert p[0] == 255
p = im(0, 89)
assert p[0] == 0
p = im(99, 89)
assert p[0] == 255
def test_identity(self):
im = pyvips.Image.identity()
assert im.width == 256
assert im.height == 1
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
p = im(0, 0)
assert p[0] == 0.0
p = im(255, 0)
assert p[0] == 255.0
p = im(128, 0)
assert p[0] == 128.0
im = pyvips.Image.identity(ushort=True)
assert im.width == 65536
assert im.height == 1
assert im.bands == 1
assert im.format == pyvips.BandFormat.USHORT
p = im(0, 0)
assert p[0] == 0
p = im(99, 0)
assert p[0] == 99
p = im(65535, 0)
assert p[0] == 65535
def test_invertlut(self):
lut = pyvips.Image.new_from_array([[0.1, 0.2, 0.3, 0.1],
[0.2, 0.4, 0.4, 0.2],
[0.7, 0.5, 0.6, 0.3]])
im = lut.invertlut()
assert im.width == 256
assert im.height == 1
assert im.bands == 3
assert im.format == pyvips.BandFormat.DOUBLE
p = im(0, 0)
assert_almost_equal_objects(p, [0, 0, 0])
p = im(255, 0)
assert_almost_equal_objects(p, [1, 1, 1])
p = im(0.2 * 255, 0)
assert pytest.approx(p[0], 0.1) == 0.1
p = im(0.3 * 255, 0)
assert pytest.approx(p[1], 0.1) == 0.1
p = im(0.1 * 255, 0)
assert pytest.approx(p[2], 0.1) == 0.1
def test_logmat(self):
im = pyvips.Image.logmat(1, 0.1)
assert im.width == 7
assert im.height == 7
assert im.bands == 1
assert im.format == pyvips.BandFormat.DOUBLE
assert im.max() == 20
total = im.avg() * im.width * im.height
scale = im.get("scale")
assert total == scale
p = im(im.width / 2, im.height / 2)
assert p[0] == 20.0
im = pyvips.Image.logmat(1, 0.1,
separable=True, precision="float")
assert im.width == 7
assert im.height == 1
assert im.bands == 1
assert im.format == pyvips.BandFormat.DOUBLE
assert im.max() == 1.0
total = im.avg() * im.width * im.height
scale = im.get("scale")
assert total == scale
p = im(im.width / 2, im.height / 2)
assert p[0] == 1.0
def test_mask_butterworth_band(self):
im = pyvips.Image.mask_butterworth_band(128, 128, 2,
0.5, 0.5, 0.7,
0.1)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.max(), 0.01) == 1
p = im(32, 32)
assert p[0] == 1.0
im = pyvips.Image.mask_butterworth_band(128, 128, 2,
0.5, 0.5, 0.7,
0.1, uchar=True, optical=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
assert im.max() == 255
p = im(32, 32)
assert p[0] == 255.0
p = im(64, 64)
assert p[0] == 255.0
im = pyvips.Image.mask_butterworth_band(128, 128, 2,
0.5, 0.5, 0.7,
0.1, uchar=True, optical=True,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
assert im.max() == 255
p = im(32, 32)
assert p[0] == 255.0
p = im(64, 64)
assert p[0] != 255
def test_mask_butterworth(self):
im = pyvips.Image.mask_butterworth(128, 128, 2, 0.7, 0.1,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.min(), 0.01) == 0
p = im(0, 0)
assert p[0] == 0.0
v, x, y = im.maxpos()
assert x == 64
assert y == 64
im = pyvips.Image.mask_butterworth(128, 128, 2, 0.7, 0.1,
optical=True, uchar=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
assert pytest.approx(im.min(), 0.01) == 0
p = im(64, 64)
assert p[0] == 255
def test_mask_butterworth_ring(self):
im = pyvips.Image.mask_butterworth_ring(128, 128, 2, 0.7, 0.1, 0.5,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
p = im(45, 0)
assert pytest.approx(p[0], 0.0001) == 1.0
v, x, y = im.minpos()
assert x == 64
assert y == 64
def test_mask_fractal(self):
im = pyvips.Image.mask_fractal(128, 128, 2.3)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
def test_mask_gaussian_band(self):
im = pyvips.Image.mask_gaussian_band(128, 128, 0.5, 0.5, 0.7, 0.1)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.max(), 0.01) == 1
p = im(32, 32)
assert p[0] == 1.0
def test_mask_gaussian(self):
im = pyvips.Image.mask_gaussian(128, 128, 0.7, 0.1,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.min(), 0.01) == 0
p = im(0, 0)
assert p[0] == 0.0
def test_mask_gaussian_ring(self):
im = pyvips.Image.mask_gaussian_ring(128, 128, 0.7, 0.1, 0.5,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
p = im(45, 0)
assert pytest.approx(p[0], 0.001) == 1.0
def test_mask_ideal_band(self):
im = pyvips.Image.mask_ideal_band(128, 128, 0.5, 0.5, 0.7)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.max(), 0.01) == 1
p = im(32, 32)
assert p[0] == 1.0
def test_mask_ideal(self):
im = pyvips.Image.mask_ideal(128, 128, 0.7,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
assert pytest.approx(im.min(), 0.01) == 0
p = im(0, 0)
assert p[0] == 0.0
def test_mask_gaussian_ring_2(self):
im = pyvips.Image.mask_ideal_ring(128, 128, 0.7, 0.5,
nodc=True)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
p = im(45, 0)
assert pytest.approx(p[0], 0.001) == 1.0
def test_sines(self):
im = pyvips.Image.sines(128, 128)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "text") == 0,
reason="no text, skipping test")
def test_text(self):
im = pyvips.Image.text("Hello, world!")
assert im.width > 10
assert im.height > 10
assert im.bands == 1
assert im.format == pyvips.BandFormat.UCHAR
assert im.max() == 255
assert im.min() == 0
def test_tonelut(self):
im = pyvips.Image.tonelut()
assert im.bands == 1
assert im.format == pyvips.BandFormat.USHORT
assert im.width == 32768
assert im.height == 1
assert im.hist_ismonotonic()
def test_xyz(self):
im = pyvips.Image.xyz(128, 128)
assert im.bands == 2
assert im.format == pyvips.BandFormat.UINT
assert im.width == 128
assert im.height == 128
p = im(45, 35)
assert_almost_equal_objects(p, [45, 35])
def test_zone(self):
im = pyvips.Image.zone(128, 128)
assert im.width == 128
assert im.height == 128
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "worley") == 0,
reason="no worley, skipping test")
def test_worley(self):
im = pyvips.Image.worley(512, 512)
assert im.width == 512
assert im.height == 512
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
@pytest.mark.skipif(pyvips.type_find("VipsOperation", "perlin") == 0,
reason="no perlin, skipping test")
def test_perlin(self):
im = pyvips.Image.perlin(512, 512)
assert im.width == 512
assert im.height == 512
assert im.bands == 1
assert im.format == pyvips.BandFormat.FLOAT
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,101 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
class TestDraw:
def test_draw_circle(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(100, 50, 50, 25)
pixel = im(25, 50)
assert len(pixel) == 1
assert pixel[0] == 100
pixel = im(26, 50)
assert len(pixel) == 1
assert pixel[0] == 0
im = pyvips.Image.black(100, 100)
im = im.draw_circle(100, 50, 50, 25, fill=True)
pixel = im(25, 50)
assert len(pixel) == 1
assert pixel[0] == 100
pixel = im(26, 50)
assert pixel[0] == 100
pixel = im(24, 50)
assert pixel[0] == 0
def test_draw_flood(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(100, 50, 50, 25)
im = im.draw_flood(100, 50, 50)
im2 = pyvips.Image.black(100, 100)
im2 = im2.draw_circle(100, 50, 50, 25, fill=True)
diff = (im - im2).abs().max()
assert diff == 0
def test_draw_image(self):
im = pyvips.Image.black(51, 51)
im = im.draw_circle(100, 25, 25, 25, fill=True)
im2 = pyvips.Image.black(100, 100)
im2 = im2.draw_image(im, 25, 25)
im3 = pyvips.Image.black(100, 100)
im3 = im3.draw_circle(100, 50, 50, 25, fill=True)
diff = (im2 - im3).abs().max()
assert diff == 0
def test_draw_line(self):
im = pyvips.Image.black(100, 100)
im = im.draw_line(100, 0, 0, 100, 0)
pixel = im(0, 0)
assert len(pixel) == 1
assert pixel[0] == 100
pixel = im(0, 1)
assert len(pixel) == 1
assert pixel[0] == 0
def test_draw_mask(self):
mask = pyvips.Image.black(51, 51)
mask = mask.draw_circle(128, 25, 25, 25, fill=True)
im = pyvips.Image.black(100, 100)
im = im.draw_mask(200, mask, 25, 25)
im2 = pyvips.Image.black(100, 100)
im2 = im2.draw_circle(100, 50, 50, 25, fill=True)
diff = (im - im2).abs().max()
assert diff == 0
def test_draw_rect(self):
im = pyvips.Image.black(100, 100)
im = im.draw_rect(100, 25, 25, 50, 50, fill=True)
im2 = pyvips.Image.black(100, 100)
for y in range(25, 75):
im2 = im2.draw_line(100, 25, y, 74, y)
diff = (im - im2).abs().max()
assert diff == 0
def test_draw_smudge(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(100, 50, 50, 25, fill=True)
im2 = im.draw_smudge(10, 10, 50, 50)
im3 = im.crop(10, 10, 50, 50)
im4 = im2.draw_image(im3, 10, 10)
diff = (im4 - im).abs().max()
assert diff == 0
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,787 @@
# vim: set fileencoding=utf-8 :
import gc
import os
import shutil
import tempfile
import pytest
import pyvips
from helpers import JPEG_FILE, SRGB_FILE, \
MATLAB_FILE, PNG_FILE, TIF_FILE, OME_FILE, ANALYZE_FILE, \
GIF_FILE, WEBP_FILE, EXR_FILE, FITS_FILE, OPENSLIDE_FILE, \
PDF_FILE, SVG_FILE, SVGZ_FILE, SVG_GZ_FILE, GIF_ANIM_FILE, \
DICOM_FILE, BMP_FILE, temp_filename, assert_almost_equal_objects, have, \
skip_if_no
class TestForeign:
tempdir = None
@classmethod
def setup_class(cls):
cls.tempdir = tempfile.mkdtemp()
cls.colour = pyvips.Image.jpegload(JPEG_FILE)
cls.mono = cls.colour.extract_band(1)
# we remove the ICC profile: the RGB one will no longer be appropriate
cls.mono.remove("icc-profile-data")
cls.rad = cls.colour.float2rad()
cls.rad.remove("icc-profile-data")
cls.cmyk = cls.colour.bandjoin(cls.mono)
cls.cmyk = cls.cmyk.copy(interpretation=pyvips.Interpretation.CMYK)
cls.cmyk.remove("icc-profile-data")
im = pyvips.Image.new_from_file(GIF_FILE)
cls.onebit = im > 128
@classmethod
def teardown_class(cls):
shutil.rmtree(cls.tempdir, ignore_errors=True)
# we have test files for formats which have a clear standard
def file_loader(self, loader, test_file, validate):
im = pyvips.Operation.call(loader, test_file)
validate(im)
im = pyvips.Image.new_from_file(test_file)
validate(im)
def buffer_loader(self, loader, test_file, validate):
with open(test_file, 'rb') as f:
buf = f.read()
im = pyvips.Operation.call(loader, buf)
validate(im)
im = pyvips.Image.new_from_buffer(buf, "")
validate(im)
def save_load(self, format, im):
x = pyvips.Image.new_temp_file(format)
im.write(x)
assert im.width == x.width
assert im.height == x.height
assert im.bands == x.bands
max_diff = (im - x).abs().max()
assert max_diff == 0
def save_load_file(self, format, options, im, thresh):
# yuk!
# but we can't set format parameters for pyvips.Image.new_temp_file()
filename = temp_filename(self.tempdir, format)
im.write_to_file(filename + options)
x = pyvips.Image.new_from_file(filename)
assert im.width == x.width
assert im.height == x.height
assert im.bands == x.bands
max_diff = (im - x).abs().max()
assert max_diff <= thresh
x = None
def save_load_buffer(self, saver, loader, im, max_diff=0, **kwargs):
buf = pyvips.Operation.call(saver, im, **kwargs)
x = pyvips.Operation.call(loader, buf)
assert im.width == x.width
assert im.height == x.height
assert im.bands == x.bands
assert (im - x).abs().max() <= max_diff
def save_buffer_tempfile(self, saver, suf, im, max_diff=0):
filename = temp_filename(self.tempdir, suf)
buf = pyvips.Operation.call(saver, im)
f = open(filename, 'wb')
f.write(buf)
f.close()
x = pyvips.Image.new_from_file(filename)
assert im.width == x.width
assert im.height == x.height
assert im.bands == x.bands
assert (im - x).abs().max() <= max_diff
def test_vips(self):
self.save_load_file(".v", "", self.colour, 0)
# check we can save and restore metadata
filename = temp_filename(self.tempdir, ".v")
self.colour.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
before_exif = self.colour.get("exif-data")
after_exif = x.get("exif-data")
assert len(before_exif) == len(after_exif)
for i in range(len(before_exif)):
assert before_exif[i] == after_exif[i]
x = None
@skip_if_no("jpegload")
def test_jpeg(self):
def jpeg_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [6, 5, 3])
profile = im.get("icc-profile-data")
assert len(profile) == 1352
assert im.width == 1024
assert im.height == 768
assert im.bands == 3
self.file_loader("jpegload", JPEG_FILE, jpeg_valid)
self.save_load("%s.jpg", self.mono)
self.save_load("%s.jpg", self.colour)
self.buffer_loader("jpegload_buffer", JPEG_FILE, jpeg_valid)
self.save_load_buffer("jpegsave_buffer", "jpegload_buffer",
self.colour, 80)
# see if we have exif parsing: our test image has this field
x = pyvips.Image.new_from_file(JPEG_FILE)
if x.get_typeof("exif-ifd0-Orientation") != 0:
# we need a copy of the image to set the new metadata on
# otherwise we get caching problems
# can set, save and load new orientation
x = pyvips.Image.new_from_file(JPEG_FILE)
x = x.copy()
x.set("orientation", 2)
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("orientation")
assert y == 2
# can remove orientation, save, load again, orientation
# has reset
x.remove("orientation")
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("orientation")
assert y == 1
# autorotate load works
x = pyvips.Image.new_from_file(JPEG_FILE)
x = x.copy()
x.set("orientation", 6)
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x1 = pyvips.Image.new_from_file(filename)
x2 = pyvips.Image.new_from_file(filename, autorotate=True)
assert x1.width == x2.height
assert x1.height == x2.width
# can set, save and reload ASCII string fields
x = pyvips.Image.new_from_file(JPEG_FILE)
x = x.copy()
x.set_type(pyvips.GValue.gstr_type,
"exif-ifd0-ImageDescription", "hello world")
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("exif-ifd0-ImageDescription")
# can't use == since the string will have an extra " (xx, yy, zz)"
# format area at the end
assert y.startswith("hello world")
# can set, save and reload UTF16 string fields ... pyvips is
# utf8, but it will be coded as utf16 and back for the XP* fields
x = pyvips.Image.new_from_file(JPEG_FILE)
x = x.copy()
x.set_type(pyvips.GValue.gstr_type, "exif-ifd0-XPComment", "йцук")
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("exif-ifd0-XPComment")
# can't use == since the string will have an extra " (xx, yy, zz)"
# format area at the end
assert y.startswith("йцук")
# can set/save/load UserComment, a tag which has the
# encoding in the first 8 bytes ... though libexif only supports
# ASCII for this
x = pyvips.Image.new_from_file(JPEG_FILE)
x = x.copy()
x.set_type(pyvips.GValue.gstr_type,
"exif-ifd2-UserComment", "hello world")
filename = temp_filename(self.tempdir, '.jpg')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("exif-ifd2-UserComment")
# can't use == since the string will have an extra " (xx, yy, zz)"
# format area at the end
assert y.startswith("hello world")
@skip_if_no("pngload")
def test_png(self):
def png_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0])
assert im.width == 290
assert im.height == 442
assert im.bands == 3
self.file_loader("pngload", PNG_FILE, png_valid)
self.buffer_loader("pngload_buffer", PNG_FILE, png_valid)
self.save_load_buffer("pngsave_buffer", "pngload_buffer", self.colour)
self.save_load("%s.png", self.mono)
self.save_load("%s.png", self.colour)
@skip_if_no("tiffload")
def test_tiff(self):
def tiff_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0])
assert im.width == 290
assert im.height == 442
assert im.bands == 3
self.file_loader("tiffload", TIF_FILE, tiff_valid)
self.buffer_loader("tiffload_buffer", TIF_FILE, tiff_valid)
if pyvips.at_least_libvips(8, 5):
self.save_load_buffer("tiffsave_buffer",
"tiffload_buffer",
self.colour)
self.save_load("%s.tif", self.mono)
self.save_load("%s.tif", self.colour)
self.save_load("%s.tif", self.cmyk)
self.save_load("%s.tif", self.onebit)
self.save_load_file(".tif", "[squash]", self.onebit, 0)
self.save_load_file(".tif", "[miniswhite]", self.onebit, 0)
self.save_load_file(".tif", "[squash,miniswhite]", self.onebit, 0)
self.save_load_file(".tif",
"[profile={0}]".format(SRGB_FILE),
self.colour, 0)
self.save_load_file(".tif", "[tile]", self.colour, 0)
self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0)
self.save_load_file(".tif",
"[tile,pyramid,compression=jpeg]", self.colour, 80)
self.save_load_file(".tif", "[bigtiff]", self.colour, 0)
self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80)
self.save_load_file(".tif",
"[tile,tile-width=256]", self.colour, 10)
filename = temp_filename(self.tempdir, '.tif')
x = pyvips.Image.new_from_file(TIF_FILE)
x = x.copy()
x.set("orientation", 2)
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("orientation")
assert y == 2
filename = temp_filename(self.tempdir, '.tif')
x = pyvips.Image.new_from_file(TIF_FILE)
x = x.copy()
x.set("orientation", 2)
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("orientation")
assert y == 2
x.remove("orientation")
filename = temp_filename(self.tempdir, '.tif')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename)
y = x.get("orientation")
assert y == 1
filename = temp_filename(self.tempdir, '.tif')
x = pyvips.Image.new_from_file(TIF_FILE)
x = x.copy()
x.set("orientation", 6)
x.write_to_file(filename)
x1 = pyvips.Image.new_from_file(filename)
x2 = pyvips.Image.new_from_file(filename, autorotate=True)
assert x1.width == x2.height
assert x1.height == x2.width
# OME support in 8.5
x = pyvips.Image.new_from_file(OME_FILE)
assert x.width == 439
assert x.height == 167
page_height = x.height
x = pyvips.Image.new_from_file(OME_FILE, n=-1)
assert x.width == 439
assert x.height == page_height * 15
x = pyvips.Image.new_from_file(OME_FILE, page=1, n=-1)
assert x.width == 439
assert x.height == page_height * 14
x = pyvips.Image.new_from_file(OME_FILE, page=1, n=2)
assert x.width == 439
assert x.height == page_height * 2
x = pyvips.Image.new_from_file(OME_FILE, n=-1)
assert x(0, 166)[0] == 96
assert x(0, 167)[0] == 0
assert x(0, 168)[0] == 1
filename = temp_filename(self.tempdir, '.tif')
x.write_to_file(filename)
x = pyvips.Image.new_from_file(filename, n=-1)
assert x.width == 439
assert x.height == page_height * 15
assert x(0, 166)[0] == 96
assert x(0, 167)[0] == 0
assert x(0, 168)[0] == 1
# pyr save to buffer added in 8.6
x = pyvips.Image.new_from_file(TIF_FILE)
buf = x.tiffsave_buffer(tile=True, pyramid=True)
filename = temp_filename(self.tempdir, '.tif')
x.tiffsave(filename, tile=True, pyramid=True)
with open(filename, 'rb') as f:
buf2 = f.read()
assert len(buf) == len(buf2)
a = pyvips.Image.new_from_buffer(buf, "", page=2)
b = pyvips.Image.new_from_buffer(buf2, "", page=2)
assert a.width == b.width
assert a.height == b.height
assert a.avg() == b.avg()
# region-shrink added in 8.7
x = pyvips.Image.new_from_file(TIF_FILE)
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mean")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="mode")
buf = x.tiffsave_buffer(tile=True, pyramid=True, region_shrink="median")
@skip_if_no("magickload")
def test_magickload(self):
def bmp_valid(im):
a = im(100, 100)
assert_almost_equal_objects(a, [227, 216, 201])
assert im.width == 1419
assert im.height == 1001
self.file_loader("magickload", BMP_FILE, bmp_valid)
self.buffer_loader("magickload_buffer", BMP_FILE, bmp_valid)
# we should have rgba for svg files
im = pyvips.Image.magickload(SVG_FILE)
assert im.bands == 4
# density should change size of generated svg
im = pyvips.Image.magickload(SVG_FILE, density='100')
width = im.width
height = im.height
im = pyvips.Image.magickload(SVG_FILE, density='200')
# This seems to fail on travis, no idea why, some problem in their IM
# perhaps
# assert im.width == width * 2
# assert im.height == height * 2
# all-frames should load every frame of the animation
# (though all-frames is deprecated)
im = pyvips.Image.magickload(GIF_ANIM_FILE)
width = im.width
height = im.height
im = pyvips.Image.magickload(GIF_ANIM_FILE, all_frames=True)
assert im.width == width
assert im.height == height * 5
# page/n let you pick a range of pages
# 'n' param added in 8.5
if pyvips.at_least_libvips(8, 5):
im = pyvips.Image.magickload(GIF_ANIM_FILE)
width = im.width
height = im.height
im = pyvips.Image.magickload(GIF_ANIM_FILE, page=1, n=2)
assert im.width == width
assert im.height == height * 2
page_height = im.get("page-height")
assert page_height == height
# should work for dicom
im = pyvips.Image.magickload(DICOM_FILE)
assert im.width == 128
assert im.height == 128
# some IMs are 3 bands, some are 1, can't really test
# assert im.bands == 1
# added in 8.7
if have("magicksave"):
self.save_load_file(".bmp", "", self.colour, 0)
self.save_load_buffer("magicksave_buffer", "magickload_buffer",
self.colour, 0, format="BMP")
self.save_load("%s.bmp", self.colour)
@skip_if_no("webpload")
def test_webp(self):
def webp_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [71, 166, 236])
assert im.width == 550
assert im.height == 368
assert im.bands == 3
self.file_loader("webpload", WEBP_FILE, webp_valid)
self.buffer_loader("webpload_buffer", WEBP_FILE, webp_valid)
self.save_load_buffer("webpsave_buffer", "webpload_buffer",
self.colour, 60)
self.save_load("%s.webp", self.colour)
# test lossless mode
im = pyvips.Image.new_from_file(WEBP_FILE)
buf = im.webpsave_buffer(lossless=True)
im2 = pyvips.Image.new_from_buffer(buf, "")
assert im.avg() == im2.avg()
# higher Q should mean a bigger buffer
b1 = im.webpsave_buffer(Q=10)
b2 = im.webpsave_buffer(Q=90)
assert len(b2) > len(b1)
# try saving an image with an ICC profile and reading it back ... if we
# can do it, our webp supports metadata load/save
buf = self.colour.webpsave_buffer()
im = pyvips.Image.new_from_buffer(buf, "")
if im.get_typeof("icc-profile-data") != 0:
# verify that the profile comes back unharmed
p1 = self.colour.get("icc-profile-data")
p2 = im.get("icc-profile-data")
assert p1 == p2
# add tests for exif, xmp, ipct
# the exif test will need us to be able to walk the header,
# we can't just check exif-data
# we can test that exif changes change the output of webpsave
# first make sure we have exif support
z = pyvips.Image.new_from_file(JPEG_FILE)
if z.get_typeof("exif-ifd0-Orientation") != 0:
x = self.colour.copy()
x.set("orientation", 6)
buf = x.webpsave_buffer()
y = pyvips.Image.new_from_buffer(buf, "")
assert y.get("orientation") == 6
@skip_if_no("analyzeload")
def test_analyzeload(self):
def analyze_valid(im):
a = im(10, 10)
assert pytest.approx(a[0]) == 3335
assert im.width == 128
assert im.height == 8064
assert im.bands == 1
self.file_loader("analyzeload", ANALYZE_FILE, analyze_valid)
@skip_if_no("matload")
def test_matload(self):
def matlab_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [38671.0, 33914.0, 26762.0])
assert im.width == 290
assert im.height == 442
assert im.bands == 3
self.file_loader("matload", MATLAB_FILE, matlab_valid)
@skip_if_no("openexrload")
def test_openexrload(self):
def exr_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [0.124512, 0.159668,
0.040375, 1.0],
threshold=0.00001)
assert im.width == 610
assert im.height == 406
assert im.bands == 4
self.file_loader("openexrload", EXR_FILE, exr_valid)
@skip_if_no("fitsload")
def test_fitsload(self):
def fits_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [-0.165013, -0.148553, 1.09122,
-0.942242], threshold=0.00001)
assert im.width == 200
assert im.height == 200
assert im.bands == 4
self.file_loader("fitsload", FITS_FILE, fits_valid)
self.save_load("%s.fits", self.mono)
@skip_if_no("openslideload")
def test_openslideload(self):
def openslide_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [244, 250, 243, 255])
assert im.width == 2220
assert im.height == 2967
assert im.bands == 4
self.file_loader("openslideload", OPENSLIDE_FILE, openslide_valid)
@skip_if_no("pdfload")
def test_pdfload(self):
def pdf_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [35, 31, 32, 255])
assert im.width == 1133
assert im.height == 680
assert im.bands == 4
self.file_loader("pdfload", PDF_FILE, pdf_valid)
self.buffer_loader("pdfload_buffer", PDF_FILE, pdf_valid)
im = pyvips.Image.new_from_file(PDF_FILE)
x = pyvips.Image.new_from_file(PDF_FILE, scale=2)
assert abs(im.width * 2 - x.width) < 2
assert abs(im.height * 2 - x.height) < 2
im = pyvips.Image.new_from_file(PDF_FILE)
x = pyvips.Image.new_from_file(PDF_FILE, dpi=144)
assert abs(im.width * 2 - x.width) < 2
assert abs(im.height * 2 - x.height) < 2
@skip_if_no("gifload")
def test_gifload(self):
def gif_valid(im):
a = im(10, 10)
assert_almost_equal_objects(a, [33])
assert im.width == 159
assert im.height == 203
assert im.bands == 1
self.file_loader("gifload", GIF_FILE, gif_valid)
self.buffer_loader("gifload_buffer", GIF_FILE, gif_valid)
# 'n' param added in 8.5
if pyvips.at_least_libvips(8, 5):
x1 = pyvips.Image.new_from_file(GIF_ANIM_FILE)
x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=2)
assert x2.height == 2 * x1.height
page_height = x2.get("page-height")
assert page_height == x1.height
x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, n=-1)
assert x2.height == 5 * x1.height
x2 = pyvips.Image.new_from_file(GIF_ANIM_FILE, page=1, n=-1)
assert x2.height == 4 * x1.height
@skip_if_no("svgload")
def test_svgload(self):
def svg_valid(im):
a = im(10, 10)
# some old rsvg versions are way, way off
assert abs(a[0] - 79) < 2
assert abs(a[1] - 79) < 2
assert abs(a[2] - 132) < 2
assert abs(a[3] - 255) < 2
assert im.width == 288
assert im.height == 470
assert im.bands == 4
self.file_loader("svgload", SVG_FILE, svg_valid)
self.buffer_loader("svgload_buffer", SVG_FILE, svg_valid)
self.file_loader("svgload", SVGZ_FILE, svg_valid)
self.buffer_loader("svgload_buffer", SVGZ_FILE, svg_valid)
self.file_loader("svgload", SVG_GZ_FILE, svg_valid)
im = pyvips.Image.new_from_file(SVG_FILE)
x = pyvips.Image.new_from_file(SVG_FILE, scale=2)
assert abs(im.width * 2 - x.width) < 2
assert abs(im.height * 2 - x.height) < 2
im = pyvips.Image.new_from_file(SVG_FILE)
x = pyvips.Image.new_from_file(SVG_FILE, dpi=144)
assert abs(im.width * 2 - x.width) < 2
assert abs(im.height * 2 - x.height) < 2
def test_csv(self):
self.save_load("%s.csv", self.mono)
def test_matrix(self):
self.save_load("%s.mat", self.mono)
@skip_if_no("ppmload")
def test_ppm(self):
self.save_load("%s.ppm", self.mono)
self.save_load("%s.ppm", self.colour)
@skip_if_no("radload")
def test_rad(self):
self.save_load("%s.hdr", self.colour)
self.save_buffer_tempfile("radsave_buffer", ".hdr",
self.rad, max_diff=0)
@skip_if_no("dzsave")
def test_dzsave(self):
# dzsave is hard to test, there are so many options
# test each option separately and hope they all function together
# correctly
# default deepzoom layout ... we must use png here, since we want to
# test the overlap for equality
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, suffix=".png")
# test horizontal overlap ... expect 256 step, overlap 1
x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png")
assert x.width == 255
y = pyvips.Image.new_from_file(filename + "_files/10/1_0.png")
assert y.width == 256
# the right two columns of x should equal the left two columns of y
left = x.crop(x.width - 2, 0, 2, x.height)
right = y.crop(0, 0, 2, y.height)
assert (left - right).abs().max() == 0
# test vertical overlap
assert x.height == 255
y = pyvips.Image.new_from_file(filename + "_files/10/0_1.png")
assert y.height == 256
# the bottom two rows of x should equal the top two rows of y
top = x.crop(0, x.height - 2, x.width, 2)
bottom = y.crop(0, 0, y.width, 2)
assert (top - bottom).abs().max() == 0
# there should be a bottom layer
x = pyvips.Image.new_from_file(filename + "_files/0/0_0.png")
assert x.width == 1
assert x.height == 1
# 10 should be the final layer
assert not os.path.isdir(filename + "_files/11")
# default google layout
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, layout="google")
# test bottom-right tile ... default is 256x256 tiles, overlap 0
x = pyvips.Image.new_from_file(filename + "/2/2/3.jpg")
assert x.width == 256
assert x.height == 256
assert not os.path.exists(filename + "/2/2/4.jpg")
assert not os.path.exists(filename + "/3")
x = pyvips.Image.new_from_file(filename + "/blank.png")
assert x.width == 256
assert x.height == 256
# google layout with overlap ... verify that we clip correctly
# overlap 1, 510x510 pixels, 256 pixel tiles, should be exactly 2x2
# tiles, though in fact the bottom and right edges will be white
filename = temp_filename(self.tempdir, '')
self.colour.crop(0, 0, 510, 510).dzsave(filename, layout="google",
overlap=1, depth="one")
x = pyvips.Image.new_from_file(filename + "/0/1/1.jpg")
assert x.width == 256
assert x.height == 256
assert not os.path.exists(filename + "/0/2/2.jpg")
# with 511x511, it'll fit exactly into 2x2 -- we we actually generate
# 3x3, since we output the overlaps
filename = temp_filename(self.tempdir, '')
self.colour.crop(0, 0, 511, 511).dzsave(filename, layout="google",
overlap=1, depth="one")
x = pyvips.Image.new_from_file(filename + "/0/2/2.jpg")
assert x.width == 256
assert x.height == 256
assert not os.path.exists(filename + "/0/3/3.jpg")
# default zoomify layout
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, layout="zoomify")
# 256x256 tiles, no overlap
assert os.path.exists(filename + "/ImageProperties.xml")
x = pyvips.Image.new_from_file(filename + "/TileGroup0/2-3-2.jpg")
assert x.width == 256
assert x.height == 256
# test zip output
filename = temp_filename(self.tempdir, '.zip')
self.colour.dzsave(filename)
assert os.path.exists(filename)
assert not os.path.exists(filename + "_files")
assert not os.path.exists(filename + ".dzi")
# test compressed zip output
filename2 = temp_filename(self.tempdir, '.zip')
self.colour.dzsave(filename2, compression=-1)
assert os.path.exists(filename2)
assert os.path.getsize(filename2) < os.path.getsize(filename)
# test suffix
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, suffix=".png")
x = pyvips.Image.new_from_file(filename + "_files/10/0_0.png")
assert x.width == 255
# test overlap
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, overlap=200)
y = pyvips.Image.new_from_file(filename + "_files/10/1_1.jpeg")
assert y.width == 654
# test tile-size
filename = temp_filename(self.tempdir, '')
self.colour.dzsave(filename, tile_size=512)
y = pyvips.Image.new_from_file(filename + "_files/10/0_0.jpeg")
assert y.width == 513
assert y.height == 513
# test save to memory buffer
filename = temp_filename(self.tempdir, '.zip')
base = os.path.basename(filename)
root, ext = os.path.splitext(base)
self.colour.dzsave(filename)
with open(filename, 'rb') as f:
buf1 = f.read()
buf2 = self.colour.dzsave_buffer(basename=root)
assert len(buf1) == len(buf2)
# we can't test the bytes are exactly equal -- the timestamps will
# be different
# added in 8.7
buf = self.colour.dzsave_buffer(region_shrink="mean")
buf = self.colour.dzsave_buffer(region_shrink="mode")
buf = self.colour.dzsave_buffer(region_shrink="median")
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,114 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import JPEG_FILE
class TestHistogram:
def test_hist_cum(self):
im = pyvips.Image.identity()
sum = im.avg() * 256
cum = im.hist_cum()
p = cum(255, 0)
assert p[0] == sum
def test_hist_equal(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.hist_equal()
assert im.width == im2.width
assert im.height == im2.height
assert im.avg() < im2.avg()
assert im.deviate() < im2.deviate()
def test_hist_ismonotonic(self):
im = pyvips.Image.identity()
assert im.hist_ismonotonic()
def test_hist_local(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.hist_local(10, 10)
assert im.width == im2.width
assert im.height == im2.height
assert im.avg() < im2.avg()
assert im.deviate() < im2.deviate()
if pyvips.at_least_libvips(8, 5):
im3 = im.hist_local(10, 10, max_slope=3)
assert im.width == im3.width
assert im.height == im3.height
assert im3.deviate() < im2.deviate()
def test_hist_match(self):
im = pyvips.Image.identity()
im2 = pyvips.Image.identity()
matched = im.hist_match(im2)
assert (im - matched).abs().max() == 0.0
def test_hist_norm(self):
im = pyvips.Image.identity()
im2 = im.hist_norm()
assert (im - im2).abs().max() == 0.0
def test_hist_plot(self):
im = pyvips.Image.identity()
im2 = im.hist_plot()
assert im2.width == 256
assert im2.height == 256
assert im2.format == pyvips.BandFormat.UCHAR
assert im2.bands == 1
def test_hist_map(self):
im = pyvips.Image.identity()
im2 = im.maplut(im)
assert (im - im2).abs().max() == 0.0
def test_percent(self):
im = pyvips.Image.new_from_file(JPEG_FILE).extract_band(1)
pc = im.percent(90)
msk = im <= pc
n_set = (msk.avg() * msk.width * msk.height) / 255.0
pc_set = 100 * n_set / (msk.width * msk.height)
assert pytest.approx(pc_set, 0.5) == 90
def test_hist_entropy(self):
im = pyvips.Image.new_from_file(JPEG_FILE).extract_band(1)
ent = im.hist_find().hist_entropy()
assert pytest.approx(ent, 0.01) == 4.37
def test_stdif(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.stdif(10, 10)
assert im.width == im2.width
assert im.height == im2.height
# new mean should be closer to target mean
assert abs(im.avg() - 128) > abs(im2.avg() - 128)
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,93 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import assert_equal_objects
class TestIofuncs:
# test the vips7 filename splitter ... this is very fragile and annoying
# code with lots of cases
def test_split7(self):
def split(path):
filename7 = pyvips.path_filename7(path)
mode7 = pyvips.path_mode7(path)
return [filename7, mode7]
cases = [
["c:\\silly:dir:name\\fr:ed.tif:jpeg:95,,,,c:\\icc\\srgb.icc",
["c:\\silly:dir:name\\fr:ed.tif",
"jpeg:95,,,,c:\\icc\\srgb.icc"]],
["I180:",
["I180",
""]],
["c:\\silly:",
["c:\\silly",
""]],
["c:\\program files\\x:hello",
["c:\\program files\\x",
"hello"]],
["C:\\fixtures\\2569067123_aca715a2ee_o.jpg",
["C:\\fixtures\\2569067123_aca715a2ee_o.jpg",
""]]
]
for case in cases:
assert_equal_objects(split(case[0]), case[1])
def test_new_from_image(self):
im = pyvips.Image.mask_ideal(100, 100, 0.5,
reject=True, optical=True)
im2 = im.new_from_image(12)
assert im2.width == im.width
assert im2.height == im.height
assert im2.interpretation == im.interpretation
assert im2.format == im.format
assert im2.xres == im.xres
assert im2.yres == im.yres
assert im2.xoffset == im.xoffset
assert im2.yoffset == im.yoffset
assert im2.bands == 1
assert im2.avg() == 12
im2 = im.new_from_image([1, 2, 3])
assert im2.bands == 3
assert im2.avg() == 2
def test_new_from_memory(self):
s = bytearray(200)
im = pyvips.Image.new_from_memory(s, 20, 10, 1, 'uchar')
assert im.width == 20
assert im.height == 10
assert im.format == 'uchar'
assert im.bands == 1
assert im.avg() == 0
im += 10
assert im.avg() == 10
@pytest.mark.skipif(not pyvips.at_least_libvips(8, 5),
reason="requires libvips >= 8.5")
def test_get_fields(self):
im = pyvips.Image.black(10, 10)
fields = im.get_fields()
# we might add more fields later
assert len(fields) > 10
assert fields[0] == 'width'
def test_write_to_memory(self):
s = bytearray(200)
im = pyvips.Image.new_from_memory(s, 20, 10, 1, 'uchar')
t = im.write_to_memory()
assert s == t
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,55 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
class TestMorphology:
def test_countlines(self):
im = pyvips.Image.black(100, 100)
im = im.draw_line(255, 0, 50, 100, 50)
n_lines = im.countlines(pyvips.Direction.HORIZONTAL)
assert n_lines == 1
def test_labelregions(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(255, 50, 50, 25, fill=True)
mask, opts = im.labelregions(segments=True)
assert opts['segments'] == 3
assert mask.max() == 2
def test_erode(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(255, 50, 50, 25, fill=True)
im2 = im.erode([[128, 255, 128],
[255, 255, 255],
[128, 255, 128]])
assert im.width == im2.width
assert im.height == im2.height
assert im.bands == im2.bands
assert im.avg() > im2.avg()
def test_dilate(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(255, 50, 50, 25, fill=True)
im2 = im.dilate([[128, 255, 128],
[255, 255, 255],
[128, 255, 128]])
assert im.width == im2.width
assert im.height == im2.height
assert im.bands == im2.bands
assert im2.avg() > im.avg()
def test_rank(self):
im = pyvips.Image.black(100, 100)
im = im.draw_circle(255, 50, 50, 25, fill=True)
im2 = im.rank(3, 3, 8)
assert im.width == im2.width
assert im.height == im2.height
assert im.bands == im2.bands
assert im2.avg() > im.avg()
if __name__ == '__main__':
pytest.main()

View File

@ -0,0 +1,203 @@
# vim: set fileencoding=utf-8 :
import pytest
import pyvips
from helpers import JPEG_FILE, all_formats, have
# Run a function expecting a complex image on a two-band image
def run_cmplx(fn, image):
if image.format == pyvips.BandFormat.FLOAT:
new_format = pyvips.BandFormat.COMPLEX
elif image.format == pyvips.BandFormat.DOUBLE:
new_format = pyvips.BandFormat.DPCOMPLEX
else:
raise pyvips.Error("run_cmplx: not float or double")
# tag as complex, run, revert tagging
cmplx = image.copy(bands=1, format=new_format)
cmplx_result = fn(cmplx)
return cmplx_result.copy(bands=2, format=image.format)
def to_polar(image):
"""Transform image coordinates to polar.
The image is transformed so that it is wrapped around a point in the
centre. Vertical straight lines become circles or segments of circles,
horizontal straight lines become radial spokes.
"""
# xy image, zero in the centre, scaled to fit image to a circle
xy = pyvips.Image.xyz(image.width, image.height)
xy -= [image.width / 2.0, image.height / 2.0]
scale = min(image.width, image.height) / float(image.width)
xy *= 2.0 / scale
# to polar, scale vertical axis to 360 degrees
index = run_cmplx(lambda x: x.polar(), xy)
index *= [1, image.height / 360.0]
return image.mapim(index)
def to_rectangular(image):
"""Transform image coordinates to rectangular.
The image is transformed so that it is unwrapped from a point in the
centre. Circles or segments of circles become vertical straight lines,
radial lines become horizontal lines.
"""
# xy image, vertical scaled to 360 degrees
xy = pyvips.Image.xyz(image.width, image.height)
xy *= [1, 360.0 / image.height]
# to rect, scale to image rect
index = run_cmplx(lambda x: x.rect(), xy)
scale = min(image.width, image.height) / float(image.width)
index *= scale / 2.0
index += [image.width / 2.0, image.height / 2.0]
return image.mapim(index)
class TestResample:
def test_affine(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
# vsqbs is non-interpolatory, don't test this way
for name in ["nearest", "bicubic", "bilinear", "nohalo", "lbb"]:
x = im
interpolate = pyvips.Interpolate.new(name)
for i in range(4):
x = x.affine([0, 1, 1, 0], interpolate=interpolate)
assert (x - im).abs().max() == 0
def test_reduce(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
# cast down to 0-127, the smallest range, so we aren't messed up by
# clipping
im = im.cast(pyvips.BandFormat.CHAR)
for fac in [1, 1.1, 1.5, 1.999]:
for fmt in all_formats:
for kernel in ["nearest", "linear",
"cubic", "lanczos2", "lanczos3"]:
x = im.cast(fmt)
r = x.reduce(fac, fac, kernel=kernel)
d = abs(r.avg() - im.avg())
assert d < 2
# try constant images ... should not change the constant
for const in [0, 1, 2, 254, 255]:
im = (pyvips.Image.black(10, 10) + const).cast("uchar")
for kernel in ["nearest", "linear",
"cubic", "lanczos2", "lanczos3"]:
# print "testing kernel =", kernel
# print "testing const =", const
shr = im.reduce(2, 2, kernel=kernel)
d = abs(shr.avg() - im.avg())
assert d == 0
def test_resize(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.resize(0.25)
assert im2.width == round(im.width / 4.0)
assert im2.height == round(im.height / 4.0)
# test geometry rounding corner case
im = pyvips.Image.black(100, 1)
x = im.resize(0.5)
assert x.width == 50
assert x.height == 1
def test_shrink(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.shrink(4, 4)
assert im2.width == round(im.width / 4.0)
assert im2.height == round(im.height / 4.0)
assert abs(im.avg() - im2.avg()) < 1
im2 = im.shrink(2.5, 2.5)
assert im2.width == round(im.width / 2.5)
assert im2.height == round(im.height / 2.5)
assert abs(im.avg() - im2.avg()) < 1
@pytest.mark.skipif(not pyvips.at_least_libvips(8, 5),
reason="requires libvips >= 8.5")
def test_thumbnail(self):
im = pyvips.Image.thumbnail(JPEG_FILE, 100)
assert im.width == 100
assert im.bands == 3
assert im.bands == 3
# the average shouldn't move too much
im_orig = pyvips.Image.new_from_file(JPEG_FILE)
assert abs(im_orig.avg() - im.avg()) < 1
# make sure we always get the right width
for width in range(1000, 1, -13):
im = pyvips.Image.thumbnail(JPEG_FILE, width)
assert im.width == width
# should fit one of width or height
im = pyvips.Image.thumbnail(JPEG_FILE, 100, height=300)
assert im.width == 100
assert im.height != 300
im = pyvips.Image.thumbnail(JPEG_FILE, 300, height=100)
assert im.width != 300
assert im.height == 100
# with @crop, should fit both width and height
im = pyvips.Image.thumbnail(JPEG_FILE, 100,
height=300, crop=True)
assert im.width == 100
assert im.height == 300
im1 = pyvips.Image.thumbnail(JPEG_FILE, 100)
with open(JPEG_FILE, 'rb') as f:
buf = f.read()
im2 = pyvips.Image.thumbnail_buffer(buf, 100)
assert abs(im1.avg() - im2.avg()) < 1
def test_similarity(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.similarity(angle=90)
im3 = im.affine([0, -1, 1, 0])
# rounding in calculating the affine transform from the angle stops
# this being exactly true
assert (im2 - im3).abs().max() < 50
def test_similarity_scale(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.similarity(scale=2)
im3 = im.affine([2, 0, 0, 2])
assert (im2 - im3).abs().max() == 0
# added in 8.7
def test_rotate(self):
if have("rotate"):
im = pyvips.Image.new_from_file(JPEG_FILE)
im2 = im.rotate(90)
im3 = im.affine([0, -1, 1, 0])
# rounding in calculating the affine transform from the angle stops
# this being exactly true
assert (im2 - im3).abs().max() < 50
def test_mapim(self):
im = pyvips.Image.new_from_file(JPEG_FILE)
p = to_polar(im)
r = to_rectangular(p)
# the left edge (which is squashed to the origin) will be badly
# distorted, but the rest should not be too bad
a = r.crop(50, 0, im.width - 50, im.height).gaussblur(2)
b = im.crop(50, 0, im.width - 50, im.height).gaussblur(2)
assert (a - b).abs().max() < 20
if __name__ == '__main__':
pytest.main()

View File

@ -2,7 +2,7 @@ top_srcdir=@TOP_SRCDIR@
PYTHON=@PYTHON@
# we need a different tmp for each script since make can run tests in parallel
tmp=$top_srcdir/test/tmp-$$
test_images=$top_srcdir/test/images
test_images=$top_srcdir/test/test-suite/images
image=$test_images/йцук.jpg
mkdir -p $tmp
vips=$top_srcdir/tools/vips