Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit eaab4c7

Browse files
committed
Get regression test framework working under Python 3.x by removing the dependency on PIL.
1 parent db25391 commit eaab4c7

3 files changed

Lines changed: 212 additions & 56 deletions

File tree

lib/matplotlib/testing/compare.py

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import matplotlib
77
from matplotlib.testing.noseclasses import ImageComparisonFailure
8+
from matplotlib.testing import image_util
9+
from matplotlib import _png
810
import math
911
import operator
1012
import os
@@ -177,18 +179,6 @@ def compare_images( expected, actual, tol, in_decorator=False ):
177179
True. (default=False)
178180
'''
179181

180-
try:
181-
from PIL import Image, ImageOps, ImageFilter
182-
except ImportError as e:
183-
msg = "Image Comparison requires the Python Imaging Library to " \
184-
"be installed. To run tests without using PIL, then use " \
185-
"the '--without-tag=PIL' command-line option.\n" \
186-
"Importing PIL failed with the following error:\n%s" % e
187-
if in_decorator:
188-
raise NotImplementedError(e)
189-
else:
190-
return msg
191-
192182
verify(actual)
193183

194184
# Convert the image to png
@@ -197,17 +187,22 @@ def compare_images( expected, actual, tol, in_decorator=False ):
197187
actual, expected = convert(actual), convert(expected)
198188

199189
# open the image files and remove the alpha channel (if it exists)
200-
expectedImage = Image.open( expected ).convert("RGB")
201-
actualImage = Image.open( actual ).convert("RGB")
190+
expectedImage = _png.read_png_uint8( expected )
191+
actualImage = _png.read_png_uint8( actual )
202192

203193
# normalize the images
204-
expectedImage = ImageOps.autocontrast( expectedImage, 2 )
205-
actualImage = ImageOps.autocontrast( actualImage, 2 )
194+
expectedImage = image_util.autocontrast( expectedImage, 2 )
195+
actualImage = image_util.autocontrast( actualImage, 2 )
206196

207197
# compare the resulting image histogram functions
208-
h1 = expectedImage.histogram()
209-
h2 = actualImage.histogram()
210-
rms = math.sqrt( reduce(operator.add, map(lambda a,b: (a-b)**2, h1, h2)) / len(h1) )
198+
rms = 0
199+
for i in xrange(0, 3):
200+
h1p = expectedImage[:,:,i]
201+
h2p = actualImage[:,:,i]
202+
h1h = np.histogram(h1p, bins=256)[0]
203+
h2h = np.histogram(h2p, bins=256)[0]
204+
rms += np.sum(np.power((h1h-h2h), 2))
205+
rms = np.sqrt(rms / (256 * 3))
211206

212207
diff_image = os.path.join(os.path.dirname(actual),
213208
'failed-diff-'+os.path.basename(actual))
@@ -245,14 +240,25 @@ def compare_images( expected, actual, tol, in_decorator=False ):
245240
return msg
246241

247242
def save_diff_image( expected, actual, output ):
248-
from PIL import Image
249-
expectedImage = np.array(Image.open( expected ).convert("RGB")).astype(np.float)
250-
actualImage = np.array(Image.open( actual ).convert("RGB")).astype(np.float)
251-
assert expectedImage.ndim==expectedImage.ndim
252-
assert expectedImage.shape==expectedImage.shape
243+
expectedImage = _png.read_png( expected )
244+
actualImage = _png.read_png( actual )
245+
assert expectedImage.ndim==actualImage.ndim
246+
assert expectedImage.shape==actualImage.shape
253247
absDiffImage = abs(expectedImage-actualImage)
248+
254249
# expand differences in luminance domain
255-
absDiffImage *= 10
256-
save_image_np = np.clip(absDiffImage,0,255).astype(np.uint8)
257-
save_image = Image.fromarray(save_image_np)
258-
save_image.save(output)
250+
absDiffImage *= 255 * 10
251+
save_image_np = np.clip(absDiffImage, 0, 255).astype(np.uint8)
252+
height, width, depth = save_image_np.shape
253+
254+
# The PDF renderer doesn't produce an alpha channel, but the
255+
# matplotlib PNG writer requires one, so expand the array
256+
if depth == 3:
257+
with_alpha = np.empty((height, width, 4), dtype=np.uint8)
258+
with_alpha[:,:,0:3] = save_image_np
259+
save_image_np = with_alpha
260+
261+
# Hard-code the alpha channel to fully solid
262+
save_image_np[:,:,3] = 255
263+
264+
_png.write_png(save_image_np.tostring(), width, height, output)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# This module contains some functionality from the Python Imaging
2+
# Library, that has been ported to use Numpy arrays rather than PIL
3+
# Image objects.
4+
5+
# The Python Imaging Library is
6+
7+
# Copyright (c) 1997-2009 by Secret Labs AB
8+
# Copyright (c) 1995-2009 by Fredrik Lundh
9+
10+
# By obtaining, using, and/or copying this software and/or its
11+
# associated documentation, you agree that you have read, understood,
12+
# and will comply with the following terms and conditions:
13+
14+
# Permission to use, copy, modify, and distribute this software and its
15+
# associated documentation for any purpose and without fee is hereby
16+
# granted, provided that the above copyright notice appears in all
17+
# copies, and that both that copyright notice and this permission notice
18+
# appear in supporting documentation, and that the name of Secret Labs
19+
# AB or the author not be used in advertising or publicity pertaining to
20+
# distribution of the software without specific, written prior
21+
# permission.
22+
23+
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO
24+
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
25+
# FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR
26+
# ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
27+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
28+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
29+
# OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
30+
31+
import numpy as np
32+
33+
# TODO: Vectorize this
34+
def autocontrast(image, cutoff=0):
35+
"""
36+
Maximize image contrast, based on histogram. This completely
37+
ignores the alpha channel.
38+
"""
39+
assert image.dtype == np.uint8
40+
41+
output_image = np.empty((image.shape[0], image.shape[1], 3), np.uint8)
42+
43+
for i in xrange(0, 3):
44+
plane = image[:,:,i]
45+
output_plane = output_image[:,:,i]
46+
h = np.histogram(plane, bins=256)[0]
47+
if cutoff:
48+
# cut off pixels from both ends of the histogram
49+
# get number of pixels
50+
n = 0
51+
for ix in xrange(256):
52+
n = n + h[ix]
53+
# remove cutoff% pixels from the low end
54+
cut = n * cutoff / 100
55+
for lo in range(256):
56+
if cut > h[lo]:
57+
cut = cut - h[lo]
58+
h[lo] = 0
59+
else:
60+
h[lo] = h[lo] - cut
61+
cut = 0
62+
if cut <= 0:
63+
break
64+
# remove cutoff% samples from the hi end
65+
cut = n * cutoff / 100
66+
for hi in xrange(255, -1, -1):
67+
if cut > h[hi]:
68+
cut = cut - h[hi]
69+
h[hi] = 0
70+
else:
71+
h[hi] = h[hi] - cut
72+
cut = 0
73+
if cut <= 0:
74+
break
75+
76+
# find lowest/highest samples after preprocessing
77+
for lo in xrange(256):
78+
if h[lo]:
79+
break
80+
for hi in xrange(255, -1, -1):
81+
if h[hi]:
82+
break
83+
84+
if hi <= lo:
85+
output_plane[:,:] = plane
86+
else:
87+
scale = 255.0 / (hi - lo)
88+
offset = -lo * scale
89+
lut = np.arange(256, dtype=np.float)
90+
lut *= scale
91+
lut += offset
92+
lut = lut.clip(0, 255)
93+
lut = lut.astype(np.uint8)
94+
95+
output_plane[:,:] = lut[plane]
96+
97+
return output_image

src/_png.cpp

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,22 @@ class _png_module : public Py::ExtensionModule<_png_module>
4242
{
4343
add_varargs_method("write_png", &_png_module::write_png,
4444
"write_png(buffer, width, height, fileobj, dpi=None)");
45-
add_varargs_method("read_png", &_png_module::read_png,
45+
add_varargs_method("read_png", &_png_module::read_png_float,
4646
"read_png(fileobj)");
47+
add_varargs_method("read_png_float", &_png_module::read_png_float,
48+
"read_png_float(fileobj)");
49+
add_varargs_method("read_png_uint8", &_png_module::read_png_uint8,
50+
"read_png_uint8(fileobj)");
4751
initialize("Module to write PNG files");
4852
}
4953

5054
virtual ~_png_module() {}
5155

5256
private:
5357
Py::Object write_png(const Py::Tuple& args);
54-
Py::Object read_png(const Py::Tuple& args);
58+
Py::Object read_png_uint8(const Py::Tuple& args);
59+
Py::Object read_png_float(const Py::Tuple& args);
60+
PyObject* _read_png(const Py::Object& py_fileobj, const bool float_result);
5561
};
5662

5763
static void write_png_data(png_structp png_ptr, png_bytep data, png_size_t length)
@@ -286,16 +292,13 @@ static void read_png_data(png_structp png_ptr, png_bytep data, png_size_t length
286292
_read_png_data(py_file_obj, data, length);
287293
}
288294

289-
Py::Object
290-
_png_module::read_png(const Py::Tuple& args)
295+
PyObject*
296+
_png_module::_read_png(const Py::Object& py_fileobj, const bool float_result)
291297
{
292-
293-
args.verify_length(1);
294298
png_byte header[8]; // 8 is the maximum size that can be checked
295299
FILE* fp = NULL;
296300
bool close_file = false;
297301

298-
Py::Object py_fileobj = Py::Object(args[0]);
299302
#if PY3K
300303
int fd = PyObject_AsFileDescriptor(py_fileobj.ptr());
301304
PyErr_Clear();
@@ -457,35 +460,70 @@ _png_module::read_png(const Py::Tuple& args)
457460
//For gray, return an x by y array, not an x by y by 1
458461
int num_dims = (info_ptr->color_type & PNG_COLOR_MASK_COLOR) ? 3 : 2;
459462

460-
double max_value = (1 << ((bit_depth < 8) ? 8 : bit_depth)) - 1;
461-
PyArrayObject *A = (PyArrayObject *) PyArray_SimpleNew(
462-
num_dims, dimensions, PyArray_FLOAT);
463+
PyArrayObject *A = NULL;
464+
if (float_result) {
465+
double max_value = (1 << ((bit_depth < 8) ? 8 : bit_depth)) - 1;
463466

464-
if (A == NULL)
465-
{
466-
throw Py::MemoryError("Could not allocate image array");
467-
}
467+
A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions, NPY_FLOAT);
468468

469-
for (png_uint_32 y = 0; y < height; y++)
470-
{
471-
png_byte* row = row_pointers[y];
472-
for (png_uint_32 x = 0; x < width; x++)
469+
if (A == NULL)
473470
{
474-
size_t offset = y * A->strides[0] + x * A->strides[1];
475-
if (bit_depth == 16)
471+
throw Py::MemoryError("Could not allocate image array");
472+
}
473+
474+
for (png_uint_32 y = 0; y < height; y++)
475+
{
476+
png_byte* row = row_pointers[y];
477+
for (png_uint_32 x = 0; x < width; x++)
476478
{
477-
png_uint_16* ptr = &reinterpret_cast<png_uint_16*>(row)[x * dimensions[2]];
478-
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
479+
size_t offset = y * A->strides[0] + x * A->strides[1];
480+
if (bit_depth == 16)
481+
{
482+
png_uint_16* ptr = &reinterpret_cast<png_uint_16*>(row)[x * dimensions[2]];
483+
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
484+
{
485+
*(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) / max_value;
486+
}
487+
}
488+
else
479489
{
480-
*(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) / max_value;
490+
png_byte* ptr = &(row[x * dimensions[2]]);
491+
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
492+
{
493+
*(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) / max_value;
494+
}
481495
}
482496
}
483-
else
497+
}
498+
} else {
499+
A = (PyArrayObject *) PyArray_SimpleNew(num_dims, dimensions, NPY_UBYTE);
500+
501+
if (A == NULL)
502+
{
503+
throw Py::MemoryError("Could not allocate image array");
504+
}
505+
506+
for (png_uint_32 y = 0; y < height; y++)
507+
{
508+
png_byte* row = row_pointers[y];
509+
for (png_uint_32 x = 0; x < width; x++)
484510
{
485-
png_byte* ptr = &(row[x * dimensions[2]]);
486-
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
511+
size_t offset = y * A->strides[0] + x * A->strides[1];
512+
if (bit_depth == 16)
513+
{
514+
png_uint_16* ptr = &reinterpret_cast<png_uint_16*>(row)[x * dimensions[2]];
515+
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
516+
{
517+
*(png_byte*)(A->data + offset + p*A->strides[2]) = ptr[p] >> 8;
518+
}
519+
}
520+
else
487521
{
488-
*(float*)(A->data + offset + p*A->strides[2]) = (float)(ptr[p]) / max_value;
522+
png_byte* ptr = &(row[x * dimensions[2]]);
523+
for (png_uint_32 p = 0; p < (png_uint_32)dimensions[2]; p++)
524+
{
525+
*(png_byte*)(A->data + offset + p*A->strides[2]) = ptr[p];
526+
}
489527
}
490528
}
491529
}
@@ -507,7 +545,22 @@ _png_module::read_png(const Py::Tuple& args)
507545
delete [] row_pointers[row];
508546
}
509547
delete [] row_pointers;
510-
return Py::asObject((PyObject*)A);
548+
549+
return (PyObject*)A;
550+
}
551+
552+
Py::Object
553+
_png_module::read_png_float(const Py::Tuple& args)
554+
{
555+
args.verify_length(1);
556+
return Py::asObject(_read_png(args[0], true));
557+
}
558+
559+
Py::Object
560+
_png_module::read_png_uint8(const Py::Tuple& args)
561+
{
562+
args.verify_length(1);
563+
return Py::asObject(_read_png(args[0], false));
511564
}
512565

513566
extern "C"

0 commit comments

Comments
 (0)