Skip to content

Commit

Permalink
Adding updated histogram entropy implementations (#598)
Browse files Browse the repository at this point in the history
As of version 6.1.0, the Pillow imaging library’s C module includes an optimized native entropy method:

• https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst#610-2019-07-01

This change detects and selects this new native method, if it is available in the version of Pillow available to `sorl-thumbnails`.

Additionally, an optimized version of the existing Python entropy method is furnished as a fallback: rather than summing the entire histogram value set, the function calculates the product of the image dimensions and band count to arrive at the same number. It also uses generator expressions and the specialized `math.fsum(…)` and `math.log2(…)` library calls to improve on the performance of its predecessor without altering the algorithm.

The appropriate image-entropy function is then conditionally added to the `sorl.thumbnail.engines.pil_engine.Engine` class as a `staticmethod` at module-load time.

*) Additionally, this PR rounds out the smart-crop unit test

Co-authored-by: Camilo Nova <[email protected]>
  • Loading branch information
fish2000 and camilonova committed Dec 24, 2019
1 parent f8aafc0 commit 55ed977
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 11 deletions.
37 changes: 28 additions & 9 deletions sorl/thumbnail/engines/pil_engine.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
from __future__ import unicode_literals, division

import math
from sorl.thumbnail.engines.base import EngineBase
from sorl.thumbnail.compat import BufferIO

try:
from PIL import Image, ImageFile, ImageDraw, ImageFilter
from PIL import Image, ImageFile, ImageDraw, ImageFilter, ImageMode
except ImportError:
import Image
import ImageFile
import ImageDraw
import ImageMode

EXIF_ORIENTATION = 0x0112


def color_count(image):
""" Return the number of color values in the input image --
this is the number of pixels times the band count
of the image.
"""
mode_descriptor = ImageMode.getmode(image.mode)
width, height = image.size
return width * height * len(mode_descriptor.bands)


def histogram_entropy_py(image):
""" Calculate the entropy of an images' histogram. """
from math import log2, fsum
histosum = float(color_count(image))
histonorm = (histocol / histosum for histocol in image.histogram())
return -fsum(p * log2(p) for p in histonorm if p != 0.0)


# Select the Pillow native histogram entropy function - if
# available - and fall back to the Python implementation:
histogram_entropy = getattr(Image.Image, 'entropy', histogram_entropy_py)


def round_corner(radius, fill):
"""Draw a round corner"""
corner = Image.new('L', (radius, radius), 0) # (0, 0, 0, 0))
Expand Down Expand Up @@ -204,6 +227,9 @@ def _entropy_crop(self, image, geometry_width, geometry_height, image_width, ima

return image

# Add the histogram_entropy fumnction as a static method:
_get_image_entropy = staticmethod(histogram_entropy)

def _scale(self, image, width, height):
return image.resize((width, height), resample=Image.ANTIALIAS)

Expand Down Expand Up @@ -261,10 +287,3 @@ def _get_raw_data(self, image, format_, quality, image_info=None, progressive=Fa
bf.close()

return raw_data

def _get_image_entropy(self, image):
"""calculate the entropy of an image"""
hist = image.histogram()
hist_size = sum(hist)
hist = [float(h) / hist_size for h in hist]
return -sum([p * math.log(p, 2) for p in hist if p != 0])
14 changes: 12 additions & 2 deletions tests/thumbnail_tests/test_engines.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,9 +340,19 @@ def mean_pixel(x, y):
for x, y in coords:
self.assertEqual(0 <= mean_pixel(x, y) < 5, True)

@unittest.skipIf(
'pil_engine' not in settings.THUMBNAIL_ENGINE,
'the other engines fail this test',
)
def test_smart_crop(self):
# TODO: Complete test for smart crop
self.BACKEND.get_thumbnail('32x32', 'data/white_border.jpg', crop='smart')
th = self.BACKEND.get_thumbnail('data/white_border.jpg', '32x32', crop='smart')
self.assertEqual(th.x, 32)
self.assertEqual(th.y, 32)

engine = PILEngine()
im = engine.get_image(th)
self.assertEqual(im.size[0], 32)
self.assertEqual(im.size[1], 32)

@unittest.skipIf(
'pil_engine' not in settings.THUMBNAIL_ENGINE,
Expand Down

0 comments on commit 55ed977

Please sign in to comment.