Added an image.entropy() method (#3608)

Added an `image.entropy()` method
This commit is contained in:
Hugo 2019-06-29 10:12:34 +03:00 committed by GitHub
commit 08c47925d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 146 additions and 40 deletions

View File

@ -0,0 +1,17 @@
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)

View File

@ -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)))

View File

@ -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."

View File

@ -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,59 @@ _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;
int idx, length;
long sum;
double entropy, 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;
entropy = 0.0;
for (idx = 0; idx < length; idx++) {
p = (double)h->histogram[idx] / fsum;
if (p != 0.0) {
entropy += p * log(p) * M_LOG2E;
}
}
/* Destroy the histogram structure */
ImagingHistogramDelete(h);
return PyFloat_FromDouble(-entropy);
}
#ifdef WITH_MODEFILTER
static PyObject*
_modefilter(ImagingObject* self, PyObject* args)
@ -3193,6 +3253,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
@ -3918,4 +3979,3 @@ init_imaging(void)
setup_module(m);
}
#endif