Skip to content

Commit

Permalink
Merge pull request #17 from leblancfg/flake8
Browse files Browse the repository at this point in the history
Force flake8 style check in travis
  • Loading branch information
leblancfg authored Oct 2, 2017
2 parents 2e568b9 + 1746c72 commit 175af1d
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 38 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ python:

before_install:
- which python; python --version
- pip install -r requirements-test.txt
# Stop the build if it doesn't pass syntax and complexity checks
- flake8 . --count --show-source --max-complexity=8 --statistics

install:
- pip install -r requirements.txt
Expand Down
12 changes: 6 additions & 6 deletions autocrop/__init__.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
# -*- coding: utf-8 -*-

import argparse
import os
import sys

from .autocrop import cli

# Inject vendored directory into system path.
v_path = os.path.abspath(os.path.sep.join([os.path.dirname(os.path.realpath(__file__)), 'vendor']))
v_path = os.path.abspath(os.path.sep.join(
[os.path.dirname(os.path.realpath(__file__)), 'vendor']))
sys.path.insert(0, v_path)

# Inject patched directory into system path.
v_path = os.path.abspath(os.path.sep.join([os.path.dirname(os.path.realpath(__file__)), 'patched']))
v_path = os.path.abspath(os.path.sep.join(
[os.path.dirname(os.path.realpath(__file__)), 'patched']))
sys.path.insert(0, v_path)

from .autocrop import cli

if __name__ == '__main__':
cli()

60 changes: 38 additions & 22 deletions autocrop/autocrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,23 @@
import shutil
import sys

from .__version__ import __title__, __description__, __author__, __version__
from .__version__ import __version__

fixexp = True # Flag to fix underexposition
INPUT_FILETYPES = ['*.jpg', '*.jpeg', '*.bmp', '*.dib', '*.jp2',
'*.png', '*.webp', '*.pbm', '*.pgm', '*.ppm',
'*.sr', '*.ras', '*.tiff', '*.tif']
INCREMENT = 0.06
GAMMA_THRES = 0.001
GAMMA_THRES = 0.001
GAMMA = 0.90
FACE_RATIO = 6

# Load XML Resource
cascFile= 'haarcascade_frontalface_default.xml'
cascFile = 'haarcascade_frontalface_default.xml'
d = os.path.dirname(sys.modules['autocrop'].__file__)
cascPath = os.path.join(d, cascFile)


# Define directory change within context
@contextmanager
def cd(newdir):
Expand All @@ -37,11 +38,13 @@ def cd(newdir):
finally:
os.chdir(prevdir)


# Define simple gamma correction fn
def gamma(img, correction):
img = cv2.pow(img/255.0, correction)
return np.uint8(img*255)


def crop(image, fwidth=500, fheight=500):
"""Given a ndarray image with a face, returns cropped array.
Expand Down Expand Up @@ -69,45 +72,49 @@ def crop(image, fwidth=500, fheight=500):
scaleFactor=1.1,
minNeighbors=5,
minSize=(minface, minface),
flags = cv2.CASCADE_FIND_BIGGEST_OBJECT | cv2.CASCADE_DO_ROUGH_SEARCH
flags=cv2.CASCADE_FIND_BIGGEST_OBJECT | cv2.CASCADE_DO_ROUGH_SEARCH
)

# Handle no faces
if len(faces) == 0:
if len(faces) == 0:
return None

# Make padding from probable biggest face
x, y, w, h = faces[-1]
pad = h / FACE_RATIO

# Make sure padding is contained within picture
while True: # decreases pad by 6% increments to fit crop into image. Can lead to very small faces.
if y-2*pad < 0 or y+h+pad > height or int(x-1.5*pad) < 0 or x+w+int(1.5*pad) > width:
# decreases pad by 6% increments to fit crop into image.
# Can lead to very small faces.
while True:
if (y-2*pad < 0 or y+h+pad > height or
int(x-1.5*pad) < 0 or x+w+int(1.5*pad) > width):
pad = (1 - INCREMENT) * pad
else:
break

# Crop the image from the original
h1 = int(x-1.5*pad)
h1 = int(x-1.5*pad)
h2 = int(x+w+1.5*pad)
v1 = int(y-2*pad)
v2 = int(y+h+pad)
image = image[v1:v2, h1:h2]

# Resize the damn thing
image = cv2.resize(image, (fheight, fwidth), interpolation = cv2.INTER_AREA)
image = cv2.resize(image, (fheight, fwidth), interpolation=cv2.INTER_AREA)

# ====== Dealing with underexposition ======
if fixexp == True:
if fixexp:
# Check if under-exposed
uexp = cv2.calcHist([gray], [0], None, [256], [0,256])
if sum(uexp[-26:]) < GAMMA_THRES * sum(uexp) :
uexp = cv2.calcHist([gray], [0], None, [256], [0, 256])
if sum(uexp[-26:]) < GAMMA_THRES * sum(uexp):
image = gamma(image, GAMMA)
return image


def main(path, fheight, fwidth):
"""Given path containing image files to process, will
1) copy them to `path/bkp`, and
1) copy them to `path/bkp`, and
2) create face-cropped versions and place them in `path/crop`
"""
errors = 0
Expand All @@ -121,12 +128,12 @@ def main(path, fheight, fwidth):
shutil.copy(file, 'bkp')

# Perform the actual crop
input = cv2.imread(file)
input = cv2.imread(file)
image = crop(input, fwidth, fheight)

# Make sure there actually was a face in there
if image == None:
print(' No faces can be detected in file {0}.'.format(str(file)))
if image is None:
print('No faces can be detected in file {}.'.format(str(file)))
errors += 1
continue

Expand All @@ -140,15 +147,24 @@ def main(path, fheight, fwidth):
# Stop and print timer
print(' {0} files have been cropped'.format(len(files_grabbed) - errors))


def cli():
parser = argparse.ArgumentParser(description='Automatically crops faces from batches of pictures')
parser.add_argument('-p', '--path', default='photos', help='Folder where images to crop are located. Default: photos/')
parser.add_argument('-w', '--width', type=int, default=500, help='Width of cropped files in px. Default: 500')
parser.add_argument('-H', '--height', type=int, default=500, help='Height of cropped files in px. Default: 500')
parser.add_argument('-v', '--version', action='version', version='%(prog)s version {}'.format(__version__))
help_d = dict(
description='Automatically crops faces from batches of pictures',
path='Folder where images to crop are located. Default=photos/',
width='Width of cropped files in px. Default=500',
height='Height of cropped files in px. Default=500')

parser = argparse.ArgumentParser(description=help_d.description)
parser.add_argument('-p', '--path', default='photos', help=help_d.path)
parser.add_argument('-w', '--width', type=int,
default=500, help=help_d.width)
parser.add_argument('-H', '--height',
type=int, default=500, help=help_d.height)
parser.add_argument('-v', '--version', action='version',
version='%(prog)s version {}'.format(__version__))

args = parser.parse_args()
print('Processing images in folder:', args.path)

main(args.path, args.height, args.width)

2 changes: 2 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest
flake8
8 changes: 4 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
# The rest you shouldn't have to touch too much :)
# ------------------------------------------------
# Except, perhaps the License and Trove Classifiers!
# If you do change the License, remember to change the Trove Classifier for that!
# If you do change the License, remember to change the Trove Classifier

here = os.path.abspath(os.path.dirname(__file__))

# Import the README and use it as the long-description.
# Note: this will only work if 'README.rst' is present in your MANIFEST.in file!
# Note: this will only work if 'README.rst' is present in your MANIFEST.in file
# XXX: Changed rst to md
with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
long_description = '\n' + f.read()
Expand Down Expand Up @@ -68,7 +68,8 @@ def run(self):
pass

self.status('Building Source and Wheel (universal) distribution…')
os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
setup_str = '{0} setup.py sdist bdist_wheel --universal'
os.system(setup_str.format(sys.executable))

self.status('Uploading the package to PyPi via Twine…')
os.system('twine upload dist/*')
Expand Down Expand Up @@ -110,4 +111,3 @@ def run(self):
'upload': UploadCommand,
},
)

11 changes: 5 additions & 6 deletions tests/test_autocrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,25 @@

import cv2
import numpy as np
import os.path

from autocrop.autocrop import gamma, main, crop, cli
import pytest
from autocrop.autocrop import gamma, crop


def test_gamma_can_do_sqrt():
"""This function is so tightly coupled to cv2 it's probably useless.
Still might flag cv2 or numpy boo-boos."""
matrix = np.array([[1,2,3],[4,5,6],[7,8,9]])
matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
expected = np.uint8([[15, 22, 27], [31, 35, 39], [42, 45, 47]])
np.testing.assert_array_equal(gamma(img=matrix, correction=0.5), expected)


def test_crop_noise_returns_none():
loc = 'tests/data/noise.png'
noise = cv2.imread(loc)
assert crop(noise) == None
assert crop(noise) is None


def test_obama_has_a_face():
loc = 'tests/data/obama.jpg'
obama = cv2.imread(loc)
assert len(crop(obama, 500, 500)) == 500

0 comments on commit 175af1d

Please sign in to comment.