From cd62c72dcc743b5f7689e4e13fdf7db87fdc2e69 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 12:04:03 +0000 Subject: [PATCH 1/7] Changed API for setting/getting focal points Previously, we used a property to do this. Heres the advantages of the new way: - The old way felt a bit like it was pretending to be a database field when it wasn't. The new way makes it easier for the developer to understand that this is just a setter/getter for 4 fields and not a field itself. - Code looks nicer - Easier to override in subclasses - More like Django user model --- .../images/feature_detection.rst | 4 +-- wagtail/wagtailimages/models.py | 27 ++++++++++--------- wagtail/wagtailimages/views/frontend.py | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/docs/core_components/images/feature_detection.rst b/docs/core_components/images/feature_detection.rst index 0b3d03873..9a67e758f 100644 --- a/docs/core_components/images/feature_detection.rst +++ b/docs/core_components/images/feature_detection.rst @@ -83,6 +83,6 @@ You can manually run feature detection on all images by running the following co from wagtail.wagtailimages.models import Image for image in Image.objects.all(): - if image.focal_point is None: - image.focal_point = image.get_suggested_focal_point() + if not image.has_focal_point(): + image.set_focal_point(image.get_suggested_focal_point()) image.save() diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index b3905755d..8c148a889 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -73,8 +73,7 @@ class AbstractImage(models.Model, TagSearchable): def __str__(self): return self.title - @property - def focal_point(self): + def get_focal_point(self): if self.focal_point_x is not None and \ self.focal_point_y is not None and \ self.focal_point_width is not None and \ @@ -86,8 +85,10 @@ class AbstractImage(models.Model, TagSearchable): height=self.focal_point_height, ) - @focal_point.setter - def focal_point(self, focal_point): + def has_focal_point(self): + return self.get_focal_point() is not None + + def set_focal_point(self, focal_point): if focal_point is not None: self.focal_point_x = focal_point.x self.focal_point_y = focal_point.y @@ -134,10 +135,10 @@ class AbstractImage(models.Model, TagSearchable): filter, created = Filter.objects.get_or_create(spec=filter) try: - if self.focal_point: + if self.has_focal_point(): rendition = self.renditions.get( filter=filter, - focal_point_key=self.focal_point.get_key(), + focal_point_key=self.get_focal_point().get_key(), ) else: rendition = self.renditions.get( @@ -150,11 +151,11 @@ class AbstractImage(models.Model, TagSearchable): # If we have a backend attribute then pass it to process # image - else pass 'default' backend_name = getattr(self, 'backend', 'default') - generated_image = filter.process_image(file_field.file, backend_name=backend_name, focal_point=self.focal_point) + generated_image = filter.process_image(file_field.file, backend_name=backend_name, focal_point=self.get_focal_point()) # generate new filename derived from old one, inserting the filter spec and focal point key before the extension - if self.focal_point is not None: - focal_point_key = "focus-" + self.focal_point.get_key() + if self.has_focal_point(): + focal_point_key = "focus-" + self.get_focal_point().get_key() else: focal_point_key = "focus-none" @@ -165,10 +166,10 @@ class AbstractImage(models.Model, TagSearchable): output_filename = filename_without_extension + '.' + extension generated_image_file = File(generated_image, name=output_filename) - if self.focal_point: + if self.has_focal_point(): rendition, created = self.renditions.get_or_create( filter=filter, - focal_point_key=self.focal_point.get_key(), + focal_point_key=self.get_focal_point().get_key(), defaults={'file': generated_image_file} ) else: @@ -222,9 +223,9 @@ def image_feature_detection(sender, instance, **kwargs): raise ImproperlyConfigured("pyOpenCV could not be found.") # Make sure the image doesn't already have a focal point - if instance.focal_point is None: + if not instance.has_focal_point(): # Set the focal point - instance.focal_point = instance.get_suggested_focal_point() + instance.set_focal_point(instance.get_suggested_focal_point()) # Receive the pre_delete signal and delete the file associated with the model instance. diff --git a/wagtail/wagtailimages/views/frontend.py b/wagtail/wagtailimages/views/frontend.py index 39c0b4ac1..7b03d900e 100644 --- a/wagtail/wagtailimages/views/frontend.py +++ b/wagtail/wagtailimages/views/frontend.py @@ -15,6 +15,6 @@ def serve(request, signature, image_id, filter_spec): raise PermissionDenied try: - return Filter(spec=filter_spec).process_image(image.file.file, HttpResponse(content_type='image/jpeg'), focal_point=image.focal_point) + return Filter(spec=filter_spec).process_image(image.file.file, HttpResponse(content_type='image/jpeg'), focal_point=image.get_focal_point()) except Filter.InvalidFilterSpecError: return HttpResponse("Invalid filter spec: " + filter_spec, content_type='text/plain', status=400) From 293addd921059aaf6004d702a581df13e02e8936 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 13:27:52 +0000 Subject: [PATCH 2/7] Removed FocalPoint class And refactored code that used it to use Rect instead --- wagtail/wagtailimages/backends/base.py | 12 +-- wagtail/wagtailimages/models.py | 54 +++++++--- .../wagtailimages/utils/feature_detection.py | 19 +--- wagtail/wagtailimages/utils/focal_point.py | 98 ------------------- wagtail/wagtailimages/utils/rect.py | 31 ++++++ 5 files changed, 77 insertions(+), 137 deletions(-) delete mode 100644 wagtail/wagtailimages/utils/focal_point.py diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index 7c95f2612..56266b0e6 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -3,7 +3,6 @@ from __future__ import division from django.conf import settings from wagtail.wagtailimages.utils.rect import Rect -from wagtail.wagtailimages.utils.focal_point import FocalPoint class BaseImageBackend(object): @@ -183,8 +182,7 @@ class BaseImageBackend(object): # Find focal point UV if focal_point is not None: - fp_x = focal_point.x - fp_y = focal_point.y + fp_x, fp_y = focal_point.centroid else: # Fall back to positioning in the centre fp_x = im_width / 2 @@ -205,10 +203,10 @@ class BaseImageBackend(object): # Make sure the entire focal point is in the crop box if focal_point is not None: - focal_point_left = focal_point.x - focal_point.width / 2 - focal_point_top = focal_point.y - focal_point.height / 2 - focal_point_right = focal_point.x + focal_point.width / 2 - focal_point_bottom = focal_point.y + focal_point.height / 2 + focal_point_left = focal_point.left + focal_point_top = focal_point.top + focal_point_right = focal_point.right + focal_point_bottom = focal_point.bottom if left > focal_point_left: right -= left - focal_point_left diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 8c148a889..4e89a3ad6 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -23,8 +23,8 @@ from unidecode import unidecode from wagtail.wagtailadmin.taggable import TagSearchable from wagtail.wagtailimages.backends import get_image_backend from wagtail.wagtailsearch import index -from wagtail.wagtailimages.utils.focal_point import FocalPoint from wagtail.wagtailimages.utils.feature_detection import FeatureDetector, opencv_available +from wagtail.wagtailimages.utils.rect import Rect from wagtail.wagtailadmin.utils import get_object_usage @@ -78,22 +78,22 @@ class AbstractImage(models.Model, TagSearchable): self.focal_point_y is not None and \ self.focal_point_width is not None and \ self.focal_point_height is not None: - return FocalPoint( + return Rect.from_point( self.focal_point_x, self.focal_point_y, - width=self.focal_point_width, - height=self.focal_point_height, + self.focal_point_width, + self.focal_point_height, ) def has_focal_point(self): return self.get_focal_point() is not None - def set_focal_point(self, focal_point): - if focal_point is not None: - self.focal_point_x = focal_point.x - self.focal_point_y = focal_point.y - self.focal_point_width = focal_point.width - self.focal_point_height = focal_point.height + def set_focal_point(self, rect): + if rect is not None: + self.focal_point_x = rect.centroid_x + self.focal_point_y = rect.centroid_y + self.focal_point_width = rect.width + self.focal_point_height = rect.height else: self.focal_point_x = None self.focal_point_y = None @@ -119,14 +119,36 @@ class AbstractImage(models.Model, TagSearchable): # Use feature detection to find a focal point feature_detector = FeatureDetector(image.size, image_data[0], image_data[1]) - focal_point = feature_detector.get_focal_point() - # Add 20% extra room around the edge of the focal point - if focal_point: - focal_point.width *= 1.20 - focal_point.height *= 1.20 + faces = feature_detector.detect_faces() + if faces: + # Create a bounding box around all faces + left = min(face.left for face in faces) + top = min(face.top for face in faces) + right = max(face.right for face in faces) + bottom = max(face.bottom for face in faces) + focal_point = Rect(left, top, right, bottom) + else: + features = feature_detector.detect_features() + if features: + # Create a bounding box around all features + left = min(feature.x for feature in features) + top = min(feature.y for feature in features) + right = max(feature.x for feature in features) + bottom = max(feature.y for feature in features) + focal_point = Rect(left, top, right, bottom) - return focal_point + # Add 20% to width and height and give it a minimum size + x, y = focal_point.centroid + width, height = focal_point.size + + width *= 1.20 + height *= 1.20 + + width = max(width, 100) + height = max(height, 100) + + return Rect.from_point(x, y, width, height) def get_rendition(self, filter): if not hasattr(filter, 'process_image'): diff --git a/wagtail/wagtailimages/utils/feature_detection.py b/wagtail/wagtailimages/utils/feature_detection.py index 6aab45c97..4009198d7 100644 --- a/wagtail/wagtailimages/utils/feature_detection.py +++ b/wagtail/wagtailimages/utils/feature_detection.py @@ -20,7 +20,7 @@ else: opencv_available = False -from wagtail.wagtailimages.utils.focal_point import FocalPoint, combine_focal_points +from wagtail.wagtailimages.utils.rect import Rect class FeatureDetector(object): @@ -50,7 +50,7 @@ class FeatureDetector(object): points = cv.GoodFeaturesToTrack(image, eig_image, temp_image, 20, 0.04, 1.0, useHarris=False) if points: - return [FocalPoint(x, y, 1) for x, y in points] + return points return [] @@ -73,19 +73,6 @@ class FeatureDetector(object): ) if faces: - return [FocalPoint.from_square(face[0][0], face[0][1], face[0][2], face[0][3]) for face in faces] + return [Rect(face[0][0], face[0][1], face[0][0] + face[0][2], face[0][1] + face[0][3]) for face in faces] return [] - - def get_focal_point(self): - # Face detection - faces = self.detect_faces() - - if faces: - return combine_focal_points(faces) - - # Feature detection - features = self.detect_features() - - if features: - return combine_focal_points(features) diff --git a/wagtail/wagtailimages/utils/focal_point.py b/wagtail/wagtailimages/utils/focal_point.py deleted file mode 100644 index 55593db66..000000000 --- a/wagtail/wagtailimages/utils/focal_point.py +++ /dev/null @@ -1,98 +0,0 @@ -# https://github.com/thumbor/thumbor/blob/8a50bfba9443e8d2a1a691ab20eeb525815be597/thumbor/point.py - -# thumbor imaging service -# https://github.com/globocom/thumbor/wiki - -# Licensed under the MIT license: -# http://www.opensource.org/licenses/mit-license -# Copyright (c) 2011 globo.com timehome@corp.globo.com - - -class FocalPoint(object): - ALIGNMENT_PERCENTAGES = { - 'left': 0.0, - 'center': 0.5, - 'right': 1.0, - 'top': 0.0, - 'middle': 0.5, - 'bottom': 1.0 - } - - def to_dict(self): - return { - 'x': self.x, - 'y': self.y, - 'z': self.weight, - 'height': self.height, - 'width': self.width, - 'origin': self.origin - } - - @classmethod - def from_dict(cls, values): - return cls( - x=float(values['x']), - y=float(values['y']), - weight=float(values['z']), - width=float(values.get('width', 1)), - height=float(values.get('height', 1)), - origin=values.get('origin', 'alignment') - ) - - def __init__(self, x, y, height=1, width=1, weight=1.0, origin="alignment"): - self.x = x - self.y = y - self.height = height - self.width = width - self.weight = weight - self.origin = origin - - @classmethod - def from_square(cls, x, y, width, height, origin='detection'): - center_x = x + (width / 2) - center_y = y + (height / 2) - return cls(center_x, center_y, height=height, width=width, weight=width * height, origin=origin) - - @classmethod - def from_alignment(cls, halign, valign, width, height): - x = width * cls.ALIGNMENT_PERCENTAGES[halign] - y = height * cls.ALIGNMENT_PERCENTAGES[valign] - - return cls(x, y) - - def __repr__(self): - return 'FocalPoint(x: %d, y: %d, width: %d, height: %d, weight: %d, origin: %s)' % ( - self.x, self.y, self.width, self.height, self.weight, self.origin - ) - - def get_key(self): - return "%(x)d-%(y)d-%(width)dx%(height)d" % self.to_dict() - - -def combine_focal_points(focal_points): - # https://github.com/thumbor/thumbor/blob/fc75f2d617942e3548986fe8403ad717fc9978ba/thumbor/transformer.py#L255-L269 - if not focal_points: - return - - total_weight = 0.0 - total_x = 0.0 - total_y = 0.0 - - for focal_point in focal_points: - total_weight += focal_point.weight - - total_x += focal_point.x * focal_point.weight - total_y += focal_point.y * focal_point.weight - - x = total_x / total_weight - y = total_y / total_weight - - min_x = min([point.x - point.width / 2 for point in focal_points]) - min_y = min([point.y - point.height / 2 for point in focal_points]) - max_x = max([point.x + point.width / 2 for point in focal_points]) - max_y = max([point.y + point.height / 2 for point in focal_points]) - - width = max_x - min_x - height = max_y - min_y - - return FocalPoint(x, y, width=width, height=height, weight=total_weight) diff --git a/wagtail/wagtailimages/utils/rect.py b/wagtail/wagtailimages/utils/rect.py index 5140ce0bb..cd56f4b0d 100644 --- a/wagtail/wagtailimages/utils/rect.py +++ b/wagtail/wagtailimages/utils/rect.py @@ -23,6 +23,18 @@ class Rect(object): def size(self): return self.width, self.height + @property + def centroid_x(self): + return (self.left + self.right) / 2 + + @property + def centroid_y(self): + return (self.top + self.bottom) / 2 + + @property + def centroid(self): + return self.centroid_x, self.centroid_y + def as_tuple(self): return self.left, self.top, self.right, self.bottom @@ -36,3 +48,22 @@ class Rect(object): return 'Rect(left: %d, top: %d, right: %d, bottom: %d)' % ( self.left, self.top, self.right, self.bottom ) + + @classmethod + def from_point(cls, x, y, width, height): + return cls( + x - width / 2, + y - height / 2, + x + width / 2, + y + height / 2, + ) + + + # DELETEME + def get_key(self): + return "%(x)d-%(y)d-%(width)dx%(height)d" % { + 'x': int(self.centroid_x), + 'y': int(self.centroid_y), + 'width': int(self.width), + 'height': int(self.height), + } From 58caa3566e828ab4605b566c84dc632af326d6dd Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 14:02:20 +0000 Subject: [PATCH 3/7] Added get_rect method to image --- wagtail/wagtailimages/models.py | 3 +++ wagtail/wagtailimages/tests/test_models.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 4e89a3ad6..185ad534e 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -73,6 +73,9 @@ class AbstractImage(models.Model, TagSearchable): def __str__(self): return self.title + def get_rect(self): + return Rect(0, 0, self.width, self.height) + def get_focal_point(self): if self.focal_point_x is not None and \ self.focal_point_y is not None and \ diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 382a18ece..5c14958d4 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -13,6 +13,7 @@ from wagtail.tests.models import EventPage, EventPageCarouselItem from wagtail.wagtailimages.models import Rendition from wagtail.wagtailimages.backends import get_image_backend from wagtail.wagtailimages.backends.pillow import PillowBackend +from wagtail.wagtailimages.utils.rect import Rect from .utils import Image, get_test_image_file @@ -31,6 +32,9 @@ class TestImage(TestCase): def test_is_landscape(self): self.assertTrue(self.image.is_landscape()) + def test_get_rect(self): + self.assertTrue(self.image.get_rect(), Rect(0, 0, 640, 480)) + class TestImagePermissions(TestCase): def setUp(self): From 404615e1dfa1f64b6b9c3efefb13dec7f141e391 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 14:11:01 +0000 Subject: [PATCH 4/7] Added tests for Rect class --- wagtail/wagtailimages/tests/tests.py | 50 ++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py index 227580e1a..6aba4a135 100644 --- a/wagtail/wagtailimages/tests/tests.py +++ b/wagtail/wagtailimages/tests/tests.py @@ -8,6 +8,7 @@ from django.utils import six from django.core.urlresolvers import reverse from wagtail.wagtailimages.utils.crypto import generate_signature, verify_signature +from wagtail.wagtailimages.utils.rect import Rect from wagtail.wagtailimages.formats import Format, get_image_format, register_image_format from .utils import Image, get_test_image_file @@ -177,3 +178,52 @@ class TestFrontendServeView(TestCase): # Check response self.assertEqual(response.status_code, 400) + + +class TestRect(TestCase): + def test_init(self): + rect = Rect(100, 150, 200, 250) + self.assertEqual(rect.left, 100) + self.assertEqual(rect.top, 150) + self.assertEqual(rect.right, 200) + self.assertEqual(rect.bottom, 250) + + def test_equality(self): + self.assertEqual(Rect(100, 150, 200, 250), Rect(100, 150, 200, 250)) + self.assertNotEqual(Rect(100, 150, 200, 250), Rect(10, 15, 20, 25)) + + def test_getitem(self): + rect = Rect(100, 150, 200, 250) + self.assertEqual(rect[0], 100) + self.assertEqual(rect[1], 150) + self.assertEqual(rect[2], 200) + self.assertEqual(rect[3], 250) + self.assertRaises(IndexError, rect.__getitem__, 4) + + def test_as_tuple(self): + rect = Rect(100, 150, 200, 250) + self.assertEqual(rect.as_tuple(), (100, 150, 200, 250)) + + def test_size(self): + rect = Rect(100, 150, 200, 350) + self.assertEqual(rect.size, (100, 200)) + self.assertEqual(rect.width, 100) + self.assertEqual(rect.height, 200) + + def test_centroid(self): + rect = Rect(100, 150, 200, 350) + self.assertEqual(rect.centroid, (150, 250)) + self.assertEqual(rect.centroid_x, 150) + self.assertEqual(rect.centroid_y, 250) + + def test_repr(self): + rect = Rect(100, 150, 200, 250) + self.assertEqual(repr(rect), "Rect(left: 100, top: 150, right: 200, bottom: 250)") + + def test_from_point(self): + rect = Rect.from_point(100, 200, 50, 20) + self.assertEqual(rect, Rect(75, 190, 125, 210)) + + def test_get_key(self): + rect = Rect(100, 150, 200, 250) + self.assertEqual(rect.get_key(), '150-200-100x100') From 4afdbaadca167d8d63349110bd59278aafaba547 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 14:18:34 +0000 Subject: [PATCH 5/7] Added tests for set/get focal point methods --- wagtail/wagtailimages/tests/test_models.py | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 5c14958d4..0fcc16ceb 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -35,6 +35,49 @@ class TestImage(TestCase): def test_get_rect(self): self.assertTrue(self.image.get_rect(), Rect(0, 0, 640, 480)) + def test_get_focal_point(self): + self.assertEqual(self.image.get_focal_point(), None) + + # Add a focal point to the image + self.image.focal_point_x = 100 + self.image.focal_point_y = 200 + self.image.focal_point_width = 50 + self.image.focal_point_height = 20 + + # Get it + self.assertEqual(self.image.get_focal_point(), Rect(75, 190, 125, 210)) + + def test_has_focal_point(self): + self.assertFalse(self.image.has_focal_point()) + + # Add a focal point to the image + self.image.focal_point_x = 100 + self.image.focal_point_y = 200 + self.image.focal_point_width = 50 + self.image.focal_point_height = 20 + + self.assertTrue(self.image.has_focal_point()) + + def test_set_focal_point(self): + self.assertEqual(self.image.focal_point_x, None) + self.assertEqual(self.image.focal_point_y, None) + self.assertEqual(self.image.focal_point_width, None) + self.assertEqual(self.image.focal_point_height, None) + + self.image.set_focal_point(Rect(100, 150, 200, 350)) + + self.assertEqual(self.image.focal_point_x, 150) + self.assertEqual(self.image.focal_point_y, 250) + self.assertEqual(self.image.focal_point_width, 100) + self.assertEqual(self.image.focal_point_height, 200) + + self.image.set_focal_point(None) + + self.assertEqual(self.image.focal_point_x, None) + self.assertEqual(self.image.focal_point_y, None) + self.assertEqual(self.image.focal_point_width, None) + self.assertEqual(self.image.focal_point_height, None) + class TestImagePermissions(TestCase): def setUp(self): From 638c6bc9471155a489570aecc98e652e11488707 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Sun, 26 Oct 2014 14:24:08 +0000 Subject: [PATCH 6/7] Removed wagtailimages/utils folder --- wagtail/wagtailimages/backends/base.py | 2 +- .../face_detection/haarcascade_frontalface_alt2.xml | 0 wagtail/wagtailimages/{utils => }/feature_detection.py | 2 +- wagtail/wagtailimages/models.py | 4 ++-- wagtail/wagtailimages/{utils => }/rect.py | 0 wagtail/wagtailimages/tests/test_admin_views.py | 2 +- wagtail/wagtailimages/tests/test_models.py | 2 +- wagtail/wagtailimages/tests/tests.py | 4 ++-- wagtail/wagtailimages/{utils/crypto.py => utils.py} | 0 wagtail/wagtailimages/utils/__init__.py | 0 wagtail/wagtailimages/views/frontend.py | 2 +- wagtail/wagtailimages/views/images.py | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename wagtail/wagtailimages/{utils => }/face_detection/haarcascade_frontalface_alt2.xml (100%) rename wagtail/wagtailimages/{utils => }/feature_detection.py (97%) rename wagtail/wagtailimages/{utils => }/rect.py (100%) rename wagtail/wagtailimages/{utils/crypto.py => utils.py} (100%) delete mode 100644 wagtail/wagtailimages/utils/__init__.py diff --git a/wagtail/wagtailimages/backends/base.py b/wagtail/wagtailimages/backends/base.py index 56266b0e6..66db7b689 100644 --- a/wagtail/wagtailimages/backends/base.py +++ b/wagtail/wagtailimages/backends/base.py @@ -2,7 +2,7 @@ from __future__ import division from django.conf import settings -from wagtail.wagtailimages.utils.rect import Rect +from wagtail.wagtailimages.rect import Rect class BaseImageBackend(object): diff --git a/wagtail/wagtailimages/utils/face_detection/haarcascade_frontalface_alt2.xml b/wagtail/wagtailimages/face_detection/haarcascade_frontalface_alt2.xml similarity index 100% rename from wagtail/wagtailimages/utils/face_detection/haarcascade_frontalface_alt2.xml rename to wagtail/wagtailimages/face_detection/haarcascade_frontalface_alt2.xml diff --git a/wagtail/wagtailimages/utils/feature_detection.py b/wagtail/wagtailimages/feature_detection.py similarity index 97% rename from wagtail/wagtailimages/utils/feature_detection.py rename to wagtail/wagtailimages/feature_detection.py index 4009198d7..147521289 100644 --- a/wagtail/wagtailimages/utils/feature_detection.py +++ b/wagtail/wagtailimages/feature_detection.py @@ -20,7 +20,7 @@ else: opencv_available = False -from wagtail.wagtailimages.utils.rect import Rect +from wagtail.wagtailimages.rect import Rect class FeatureDetector(object): diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 185ad534e..287e7d911 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -23,8 +23,8 @@ from unidecode import unidecode from wagtail.wagtailadmin.taggable import TagSearchable from wagtail.wagtailimages.backends import get_image_backend from wagtail.wagtailsearch import index -from wagtail.wagtailimages.utils.feature_detection import FeatureDetector, opencv_available -from wagtail.wagtailimages.utils.rect import Rect +from wagtail.wagtailimages.feature_detection import FeatureDetector, opencv_available +from wagtail.wagtailimages.rect import Rect from wagtail.wagtailadmin.utils import get_object_usage diff --git a/wagtail/wagtailimages/utils/rect.py b/wagtail/wagtailimages/rect.py similarity index 100% rename from wagtail/wagtailimages/utils/rect.py rename to wagtail/wagtailimages/rect.py diff --git a/wagtail/wagtailimages/tests/test_admin_views.py b/wagtail/wagtailimages/tests/test_admin_views.py index b22e21257..822502703 100644 --- a/wagtail/wagtailimages/tests/test_admin_views.py +++ b/wagtail/wagtailimages/tests/test_admin_views.py @@ -7,7 +7,7 @@ from django.contrib.auth.models import Permission from django.core.files.uploadedfile import SimpleUploadedFile from wagtail.tests.utils import WagtailTestUtils -from wagtail.wagtailimages.utils.crypto import generate_signature +from wagtail.wagtailimages.utils import generate_signature from .utils import Image, get_test_image_file diff --git a/wagtail/wagtailimages/tests/test_models.py b/wagtail/wagtailimages/tests/test_models.py index 0fcc16ceb..753f0e166 100644 --- a/wagtail/wagtailimages/tests/test_models.py +++ b/wagtail/wagtailimages/tests/test_models.py @@ -13,7 +13,7 @@ from wagtail.tests.models import EventPage, EventPageCarouselItem from wagtail.wagtailimages.models import Rendition from wagtail.wagtailimages.backends import get_image_backend from wagtail.wagtailimages.backends.pillow import PillowBackend -from wagtail.wagtailimages.utils.rect import Rect +from wagtail.wagtailimages.rect import Rect from .utils import Image, get_test_image_file diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py index 6aba4a135..8dca20597 100644 --- a/wagtail/wagtailimages/tests/tests.py +++ b/wagtail/wagtailimages/tests/tests.py @@ -7,8 +7,8 @@ from django import template from django.utils import six from django.core.urlresolvers import reverse -from wagtail.wagtailimages.utils.crypto import generate_signature, verify_signature -from wagtail.wagtailimages.utils.rect import Rect +from wagtail.wagtailimages.utils import generate_signature, verify_signature +from wagtail.wagtailimages.rect import Rect from wagtail.wagtailimages.formats import Format, get_image_format, register_image_format from .utils import Image, get_test_image_file diff --git a/wagtail/wagtailimages/utils/crypto.py b/wagtail/wagtailimages/utils.py similarity index 100% rename from wagtail/wagtailimages/utils/crypto.py rename to wagtail/wagtailimages/utils.py diff --git a/wagtail/wagtailimages/utils/__init__.py b/wagtail/wagtailimages/utils/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/wagtail/wagtailimages/views/frontend.py b/wagtail/wagtailimages/views/frontend.py index 7b03d900e..0ad2534d3 100644 --- a/wagtail/wagtailimages/views/frontend.py +++ b/wagtail/wagtailimages/views/frontend.py @@ -4,7 +4,7 @@ from django.core.exceptions import PermissionDenied from django.views.decorators.cache import cache_control from wagtail.wagtailimages.models import get_image_model, Filter -from wagtail.wagtailimages.utils.crypto import verify_signature +from wagtail.wagtailimages.utils import verify_signature @cache_control(max_age=60*60*24*60) # Cache for 60 days diff --git a/wagtail/wagtailimages/views/images.py b/wagtail/wagtailimages/views/images.py index a2a6d4825..4ef3ecf53 100644 --- a/wagtail/wagtailimages/views/images.py +++ b/wagtail/wagtailimages/views/images.py @@ -16,7 +16,7 @@ from wagtail.wagtailsearch.backends import get_search_backends from wagtail.wagtailimages.models import get_image_model, Filter from wagtail.wagtailimages.forms import get_image_form, URLGeneratorForm -from wagtail.wagtailimages.utils.crypto import generate_signature +from wagtail.wagtailimages.utils import generate_signature from wagtail.wagtailimages.fields import MAX_UPLOAD_SIZE From 27832b3282b3d63414b97dad6ea126afa2224b38 Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Tue, 4 Nov 2014 17:10:43 +0000 Subject: [PATCH 7/7] Return None when no features detected --- wagtail/wagtailimages/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wagtail/wagtailimages/models.py b/wagtail/wagtailimages/models.py index 287e7d911..7de5b9ca6 100644 --- a/wagtail/wagtailimages/models.py +++ b/wagtail/wagtailimages/models.py @@ -140,6 +140,8 @@ class AbstractImage(models.Model, TagSearchable): right = max(feature.x for feature in features) bottom = max(feature.y for feature in features) focal_point = Rect(left, top, right, bottom) + else: + return None # Add 20% to width and height and give it a minimum size x, y = focal_point.centroid