Merge branch 'master' into add-nifti-support
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
.pytest_cache
|
||||
compile
|
||||
.pytest_cache
|
||||
a.out
|
||||
*.log
|
||||
*.trs
|
||||
|
@ -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
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
12
m4/gif.m4
@ -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)
|
||||
|
12
m4/jpeg.m4
@ -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)
|
||||
|
12
m4/png.m4
@ -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)
|
||||
|
12
m4/tiff.m4
@ -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)
|
||||
|
12
m4/webp.m4
@ -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)
|
||||
|
12
m4/zlib.m4
@ -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
@ -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.
|
Before Width: | Height: | Size: 127 KiB |
BIN
test/images/avg152T1_LR_nifti.nii.gz
Normal file
0
test/test-suite/__init__.py
Normal file
4
test/test-suite/conftest.py
Normal file
@ -0,0 +1,4 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'helpers'))
|
232
test/test-suite/helpers/helpers.py
Normal 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))
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/test-suite/images/MARBLES.BMP
Normal file
After Width: | Height: | Size: 4.1 MiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 660 KiB After Width: | Height: | Size: 660 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 436 B After Width: | Height: | Size: 436 B |
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 297 KiB |
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 176 KiB |
641
test/test-suite/test_arithmetic.py
Normal 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()
|
155
test/test-suite/test_colour.py
Normal 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()
|
767
test/test-suite/test_conversion.py
Normal 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()
|
206
test/test-suite/test_convolution.py
Normal 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()
|
439
test/test-suite/test_create.py
Normal 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()
|
101
test/test-suite/test_draw.py
Normal 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()
|
787
test/test-suite/test_foreign.py
Normal 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()
|
114
test/test-suite/test_histogram.py
Normal 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()
|
93
test/test-suite/test_iofuncs.py
Normal 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()
|
55
test/test-suite/test_morphology.py
Normal 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()
|
203
test/test-suite/test_resample.py
Normal 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()
|
@ -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
|
||||
|