mirror of
https://github.com/python-pillow/Pillow.git
synced 2024-12-25 17:36:18 +03:00
Added an image.entropy()
method
This calculates the entropy for the image, based on the histogram. Because this uses image histogram data directly, the existing C function underpinning the `image.histogram()` method was abstracted into a static function to parse extrema tuple arguments, and a new C function was added to calculate image entropy, making use of the new static extrema function. The extrema-parsing function was written by @homm, based on the macro abstraction I wrote, during the discussion of my first entropy-method pull request: https://git.io/fhodS The new `image.entropy()` method is based on `image.histogram()`, and will accept the same arguments to calculate the histogram data it will use to assess the entropy of the image. The algorithm and methodology is based on existing Python code: * https://git.io/fhmIU ... A test case in the `Tests/` directory, and doctest lines in `selftest.py`, have both been added and checked. Changes proposed in this pull request: * Added “math.h” include to _imaging.c * The addition of an `image.entropy()` method to the `Image` Python class, * The abstraction of the extrema-parsing logic of of the C function `_histogram` into a static function, and * The use of that static function in both the `_histogram` and `_entropy` C functions. * Minor documentation addenda in the docstrings for both the `image.entropy()` and `image.histogram()` methods were also added. * Removed outdated boilerplate from testing code * Removed unused “unittest” import
This commit is contained in:
parent
a79147ff96
commit
7b815a5f1d
19
Tests/test_image_entropy.py
Normal file
19
Tests/test_image_entropy.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from .helper import PillowTestCase, hopper
|
||||
|
||||
|
||||
class TestImageEntropy(PillowTestCase):
|
||||
|
||||
def test_entropy(self):
|
||||
|
||||
def entropy(mode):
|
||||
return hopper(mode).entropy()
|
||||
|
||||
self.assertAlmostEqual(entropy("1"), 0.9138803254693582)
|
||||
self.assertAlmostEqual(entropy("L"), 7.06650513081286)
|
||||
self.assertAlmostEqual(entropy("I"), 7.06650513081286)
|
||||
self.assertAlmostEqual(entropy("F"), 7.06650513081286)
|
||||
self.assertAlmostEqual(entropy("P"), 5.0530452472519745)
|
||||
self.assertAlmostEqual(entropy("RGB"), 8.821286587714319)
|
||||
self.assertAlmostEqual(entropy("RGBA"), 7.42724306524488)
|
||||
self.assertAlmostEqual(entropy("CMYK"), 7.4272430652448795)
|
||||
self.assertAlmostEqual(entropy("YCbCr"), 7.698360534903628)
|
|
@ -90,6 +90,8 @@ def testimage():
|
|||
2
|
||||
>>> len(im.histogram())
|
||||
768
|
||||
>>> '%.7f' % im.entropy()
|
||||
'8.8212866'
|
||||
>>> _info(im.point(list(range(256))*3))
|
||||
(None, 'RGB', (128, 128))
|
||||
>>> _info(im.resize((64, 64)))
|
||||
|
|
|
@ -1405,6 +1405,7 @@ class Image(object):
|
|||
bi-level image (mode "1") or a greyscale image ("L").
|
||||
|
||||
:param mask: An optional mask.
|
||||
:param extrema: An optional tuple of manually-specified extrema.
|
||||
:returns: A list containing pixel counts.
|
||||
"""
|
||||
self.load()
|
||||
|
@ -1417,6 +1418,32 @@ class Image(object):
|
|||
return self.im.histogram(extrema)
|
||||
return self.im.histogram()
|
||||
|
||||
def entropy(self, mask=None, extrema=None):
|
||||
"""
|
||||
Calculates and returns the entropy for the image.
|
||||
|
||||
A bilevel image (mode "1") is treated as a greyscale ("L")
|
||||
image by this method.
|
||||
|
||||
If a mask is provided, the method employs the histogram for
|
||||
those parts of the image where the mask image is non-zero.
|
||||
The mask image must have the same size as the image, and be
|
||||
either a bi-level image (mode "1") or a greyscale image ("L").
|
||||
|
||||
:param mask: An optional mask.
|
||||
:param extrema: An optional tuple of manually-specified extrema.
|
||||
:returns: A float value representing the image entropy
|
||||
"""
|
||||
self.load()
|
||||
if mask:
|
||||
mask.load()
|
||||
return self.im.entropy((0, 0), mask.im)
|
||||
if self.mode in ("I", "F"):
|
||||
if extrema is None:
|
||||
extrema = self.getextrema()
|
||||
return self.im.entropy(extrema)
|
||||
return self.im.entropy()
|
||||
|
||||
def offset(self, xoffset, yoffset=None):
|
||||
raise NotImplementedError(
|
||||
"offset() has been removed. Please call ImageChops.offset() instead."
|
||||
|
|
144
src/_imaging.c
144
src/_imaging.c
|
@ -86,6 +86,9 @@
|
|||
|
||||
#include "py3.h"
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
/* Configuration stuff. Feel free to undef things you don't need. */
|
||||
#define WITH_IMAGECHOPS /* ImageChops support */
|
||||
#define WITH_IMAGEDRAW /* ImageDraw support */
|
||||
|
@ -1176,59 +1179,68 @@ _getpixel(ImagingObject* self, PyObject* args)
|
|||
return getpixel(self->image, self->access, x, y);
|
||||
}
|
||||
|
||||
union hist_extrema {
|
||||
UINT8 u[2];
|
||||
INT32 i[2];
|
||||
FLOAT32 f[2];
|
||||
};
|
||||
|
||||
static union hist_extrema*
|
||||
parse_histogram_extremap(ImagingObject* self, PyObject* extremap,
|
||||
union hist_extrema* ep)
|
||||
{
|
||||
int i0, i1;
|
||||
double f0, f1;
|
||||
|
||||
if (extremap) {
|
||||
switch (self->image->type) {
|
||||
case IMAGING_TYPE_UINT8:
|
||||
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
|
||||
return NULL;
|
||||
ep->u[0] = CLIP8(i0);
|
||||
ep->u[1] = CLIP8(i1);
|
||||
break;
|
||||
case IMAGING_TYPE_INT32:
|
||||
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
|
||||
return NULL;
|
||||
ep->i[0] = i0;
|
||||
ep->i[1] = i1;
|
||||
break;
|
||||
case IMAGING_TYPE_FLOAT32:
|
||||
if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1))
|
||||
return NULL;
|
||||
ep->f[0] = (FLOAT32) f0;
|
||||
ep->f[1] = (FLOAT32) f1;
|
||||
break;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
return ep;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_histogram(ImagingObject* self, PyObject* args)
|
||||
{
|
||||
ImagingHistogram h;
|
||||
PyObject* list;
|
||||
int i;
|
||||
union {
|
||||
UINT8 u[2];
|
||||
INT32 i[2];
|
||||
FLOAT32 f[2];
|
||||
} extrema;
|
||||
void* ep;
|
||||
int i0, i1;
|
||||
double f0, f1;
|
||||
union hist_extrema extrema;
|
||||
union hist_extrema* ep;
|
||||
|
||||
PyObject* extremap = NULL;
|
||||
ImagingObject* maskp = NULL;
|
||||
if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp))
|
||||
return NULL;
|
||||
|
||||
if (extremap) {
|
||||
ep = &extrema;
|
||||
switch (self->image->type) {
|
||||
case IMAGING_TYPE_UINT8:
|
||||
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
|
||||
return NULL;
|
||||
/* FIXME: clip */
|
||||
extrema.u[0] = i0;
|
||||
extrema.u[1] = i1;
|
||||
break;
|
||||
case IMAGING_TYPE_INT32:
|
||||
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
|
||||
return NULL;
|
||||
extrema.i[0] = i0;
|
||||
extrema.i[1] = i1;
|
||||
break;
|
||||
case IMAGING_TYPE_FLOAT32:
|
||||
if (!PyArg_ParseTuple(extremap, "dd", &f0, &f1))
|
||||
return NULL;
|
||||
extrema.f[0] = (FLOAT32) f0;
|
||||
extrema.f[1] = (FLOAT32) f1;
|
||||
break;
|
||||
default:
|
||||
ep = NULL;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
ep = NULL;
|
||||
return NULL;
|
||||
|
||||
/* Using a var to avoid allocations. */
|
||||
ep = parse_histogram_extremap(self, extremap, &extrema);
|
||||
h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
|
||||
|
||||
if (!h)
|
||||
return NULL;
|
||||
return NULL;
|
||||
|
||||
/* Build an integer list containing the histogram */
|
||||
list = PyList_New(h->bands * 256);
|
||||
|
@ -1243,11 +1255,63 @@ _histogram(ImagingObject* self, PyObject* args)
|
|||
PyList_SetItem(list, i, item);
|
||||
}
|
||||
|
||||
/* Destroy the histogram structure */
|
||||
ImagingHistogramDelete(h);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
_entropy(ImagingObject* self, PyObject* args)
|
||||
{
|
||||
ImagingHistogram h;
|
||||
PyObject* entropy;
|
||||
int idx, length;
|
||||
long sum;
|
||||
double fentropy, fsum, p;
|
||||
union hist_extrema extrema;
|
||||
union hist_extrema* ep;
|
||||
|
||||
PyObject* extremap = NULL;
|
||||
ImagingObject* maskp = NULL;
|
||||
if (!PyArg_ParseTuple(args, "|OO!", &extremap, &Imaging_Type, &maskp))
|
||||
return NULL;
|
||||
|
||||
/* Using a local var to avoid allocations. */
|
||||
ep = parse_histogram_extremap(self, extremap, &extrema);
|
||||
h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);
|
||||
|
||||
if (!h)
|
||||
return NULL;
|
||||
|
||||
/* Calculate the histogram entropy */
|
||||
/* First, sum the histogram data */
|
||||
length = h->bands * 256;
|
||||
sum = 0;
|
||||
for (idx = 0; idx < length; idx++) {
|
||||
sum += h->histogram[idx];
|
||||
}
|
||||
|
||||
/* Next, normalize the histogram data, */
|
||||
/* using the histogram sum value */
|
||||
fsum = (double)sum;
|
||||
fentropy = 0.0;
|
||||
for (idx = 0; idx < length; idx++) {
|
||||
p = (double)h->histogram[idx] / fsum;
|
||||
if (p != 0.0) {
|
||||
fentropy += p * log(p) * M_LOG2E;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally, allocate a PyObject* for return */
|
||||
entropy = PyFloat_FromDouble(-fentropy);
|
||||
|
||||
/* Destroy the histogram structure */
|
||||
ImagingHistogramDelete(h);
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
#ifdef WITH_MODEFILTER
|
||||
static PyObject*
|
||||
_modefilter(ImagingObject* self, PyObject* args)
|
||||
|
@ -3193,6 +3257,7 @@ static struct PyMethodDef methods[] = {
|
|||
{"expand", (PyCFunction)_expand_image, 1},
|
||||
{"filter", (PyCFunction)_filter, 1},
|
||||
{"histogram", (PyCFunction)_histogram, 1},
|
||||
{"entropy", (PyCFunction)_entropy, 1},
|
||||
#ifdef WITH_MODEFILTER
|
||||
{"modefilter", (PyCFunction)_modefilter, 1},
|
||||
#endif
|
||||
|
@ -3912,4 +3977,3 @@ init_imaging(void)
|
|||
setup_module(m);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user