mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-08-25 14:44:45 +03:00
Added 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 macro, and a new C function was added that uses this macro. 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. Subsequent commits: * Using assertAlmostEqual() in entropy tests * Added description of `extrema` arguments. * Only test seven digits of float returned by im.entropy()
This commit is contained in:
parent
41fba67fb0
commit
4ce620c55d
23
Tests/test_image_entropy.py
Normal file
23
Tests/test_image_entropy.py
Normal file
|
@ -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()
|
|
@ -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)))
|
||||
|
|
|
@ -1361,6 +1361,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()
|
||||
|
@ -1373,6 +1374,36 @@ class Image(object):
|
|||
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.")
|
||||
|
|
138
src/_imaging.c
138
src/_imaging.c
|
@ -1175,59 +1175,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);
|
||||
|
@ -1242,11 +1245,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 * log2(p)) : 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)
|
||||
|
@ -3191,6 +3232,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
|
||||
|
|
Loading…
Reference in New Issue
Block a user