Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from Jun 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions Tests/test_image_entropy.py
@@ -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)
2 changes: 2 additions & 0 deletions selftest.py
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
27 changes: 27 additions & 0 deletions src/PIL/Image.py
Expand Up @@ -1405,6 +1405,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 @@ -1417,6 +1418,32 @@ def histogram(self, mask=None, extrema=None):
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."
Expand Down
120 changes: 90 additions & 30 deletions src/_imaging.c
Expand Up @@ -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 */
Expand Down Expand Up @@ -1176,59 +1179,68 @@ _getpixel(ImagingObject* self, PyObject* args)
return getpixel(self->image, self->access, x, y);
}

static PyObject*
_histogram(ImagingObject* self, PyObject* args)
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)
{
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;
ep->u[0] = CLIP8(i0);
ep->u[1] = CLIP8(i1);
break;
case IMAGING_TYPE_INT32:
if (!PyArg_ParseTuple(extremap, "ii", &i0, &i1))
return NULL;
extrema.i[0] = i0;
extrema.i[1] = i1;
ep->i[0] = i0;
ep->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;
ep->f[0] = (FLOAT32) f0;
ep->f[1] = (FLOAT32) f1;
break;
default:
ep = NULL;
break;
return NULL;
}
} else
ep = NULL;
} else {
return NULL;
}
return ep;
}

static PyObject*
_histogram(ImagingObject* self, PyObject* args)
{
ImagingHistogram h;
PyObject* list;
int i;
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 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);
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -3912,4 +3973,3 @@ init_imaging(void)
setup_module(m);
}
#endif