Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Tests/test_image_entropy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from helper import unittest, 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)


if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions selftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand Down
31 changes: 31 additions & 0 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,7 @@ def histogram(self, mask=None, extrema=None):
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()
Expand All @@ -1373,6 +1374,36 @@ def histogram(self, mask=None, extrema=None):
return self.im.histogram(extrema)
return self.im.histogram()

def entropy(self, mask=None, extrema=None):
"""
Returns the histogram entropy. The histogram is returned as
a list of pixel counts, one for each pixel value in the source
image. If the image has more than one band, the histograms for
all bands are concatenated (for example, the histogram for an
"RGB" image contains 768 values).

A bilevel image (mode "1") is treated as a greyscale ("L") image
by this method.

If a mask is provided, the method returns a 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 measuring 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.")
Expand Down
141 changes: 93 additions & 48 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,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 */
Expand Down Expand Up @@ -1175,59 +1178,62 @@ _getpixel(ImagingObject* self, PyObject* args)
return getpixel(self->image, self->access, x, y);
}

#define HISTOGRAM_METHOD_PROLOGUE(HISTO) \
ImagingHistogram HISTO; \
union { \
UINT8 u[2]; \
INT32 i[2]; \
FLOAT32 f[2]; \
} extrema; \
void* ep; \
int i0, i1; \
double f0, f1; \
\
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; \
\
HISTO = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep); \
\
if (!HISTO) \
return NULL;

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;

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;

h = ImagingGetHistogram(self->image, (maskp) ? maskp->image : NULL, ep);

if (!h)
return NULL;
HISTOGRAM_METHOD_PROLOGUE(h);

/* Build an integer list containing the histogram */
list = PyList_New(h->bands * 256);
Expand All @@ -1242,11 +1248,49 @@ _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)
{
PyObject* entropy;
int idx, length;
long sum;
double fentropy, fsum, p;
HISTOGRAM_METHOD_PROLOGUE(h);

/* 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;
fentropy += p != 0.0 ? (p * log(p) * M_LOG2E) : 0.0;
}

/* Finally, allocate a PyObject* for return */
entropy = PyFloat_FromDouble(-fentropy);

/* Destroy the histogram structure */
ImagingHistogramDelete(h);

return entropy;
}

#undef HISTOGRAM_METHOD_PROLOGUE

#ifdef WITH_MODEFILTER
static PyObject*
_modefilter(ImagingObject* self, PyObject* args)
Expand Down Expand Up @@ -3191,6 +3235,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
Expand Down