diff --git a/blockhash/__init__.py b/blockhash/__init__.py new file mode 100644 index 0000000..fa07cf7 --- /dev/null +++ b/blockhash/__init__.py @@ -0,0 +1 @@ +from blockhash import blockhash, blockhash_even diff --git a/blockhash.py b/blockhash/blockhash.py similarity index 72% rename from blockhash.py rename to blockhash/blockhash.py index a2b0043..12bc9e2 100755 --- a/blockhash.py +++ b/blockhash/blockhash.py @@ -7,8 +7,7 @@ # Distributed under an MIT license, please see LICENSE in the top dir. import math -import argparse -import PIL.Image as Image + def median(data): data = sorted(data) @@ -17,6 +16,7 @@ def median(data): return (data[length // 2 - 1] + data[length // 2]) / 2.0 return data[length // 2] + def total_value_rgba(im, data, x, y): r, g, b, a = data[y * im.size[0] + x] if a == 0: @@ -24,10 +24,12 @@ def total_value_rgba(im, data, x, y): else: return r + g + b + def total_value_rgb(im, data, x, y): r, g, b = data[y * im.size[0] + x] return r + g + b + def translate_blocks_to_bits(blocks, pixels_per_block): half_block_value = pixels_per_block * 256 * 3 / 2 @@ -81,6 +83,7 @@ def blockhash_even(im, bits): translate_blocks_to_bits(result, blocksize_x * blocksize_y) return bits_to_hexhash(result) + def blockhash(im, bits): if im.mode == 'RGBA': total_value = total_value_rgba @@ -152,58 +155,3 @@ def blockhash(im, bits): translate_blocks_to_bits(result, block_width * block_height) return bits_to_hexhash(result) -if __name__ == '__main__': - parser = argparse.ArgumentParser() - - parser.add_argument('--quick', type=bool, default=False, - help='Use quick hashing method. Default: False') - parser.add_argument('--bits', type=int, default=16, - help='Create hash of size N^2 bits. Default: 16') - parser.add_argument('--size', - help='Resize image to specified size before hashing, e.g. 256x256') - parser.add_argument('--interpolation', type=int, default=1, choices=[1, 2, 3, 4], - help='Interpolation method: 1 - nearest neightbor, 2 - bilinear, 3 - bicubic, 4 - antialias. Default: 1') - parser.add_argument('--debug', action='store_true', - help='Print hashes as 2D maps (for debugging)') - parser.add_argument('filenames', nargs='+') - - args = parser.parse_args() - - if args.interpolation == 1: - interpolation = Image.NEAREST - elif args.interpolation == 2: - interpolation = Image.BILINEAR - elif args.interpolation == 3: - interpolation = Image.BICUBIC - elif args.interpolation == 4: - interpolation = Image.ANTIALIAS - - if args.quick: - method = blockhash_even - else: - method = blockhash - - for fn in args.filenames: - im = Image.open(fn) - - # convert indexed/grayscale images to RGB - if im.mode == '1' or im.mode == 'L' or im.mode == 'P': - im = im.convert('RGB') - elif im.mode == 'LA': - im = im.convert('RGBA') - - if args.size: - size = args.size.split('x') - size = (int(size[0]), int(size[1])) - im = im.resize(size, interpolation) - - hash = method(im, args.bits) - - print('{hash} {fn}'.format(fn=fn, hash=hash)) - - if args.debug: - bin_hash = '{:0{width}b}'.format(int(hash, 16), width=args.bits ** 2) - map = [bin_hash[i:i+args.bits] for i in range(0, len(bin_hash), args.bits)] - print("") - print("\n".join(map)) - print("") diff --git a/blockhash/command_line.py b/blockhash/command_line.py new file mode 100755 index 0000000..5e7cfef --- /dev/null +++ b/blockhash/command_line.py @@ -0,0 +1,73 @@ +#! /usr/bin/env python +# +# Perceptual image hash calculation tool based on algorithm descibed in +# Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu +# +# Copyright 2014 Commons Machinery http://commonsmachinery.se/ +# Distributed under an MIT license, please see LICENSE in the top dir. + +import argparse +import PIL.Image as Image + +from blockhash import blockhash + + +def main(): + parser = argparse.ArgumentParser() + + parser.add_argument('--quick', type=bool, default=False, + help='Use quick hashing method. Default: False') + parser.add_argument('--bits', type=int, default=16, + help='Create hash of size N^2 bits. Default: 16') + parser.add_argument('--size', + help='Resize image to specified size before hashing, e.g. 256x256') + parser.add_argument('--interpolation', type=int, default=1, choices=[1, 2, 3, 4], + help='Interpolation method: 1 - nearest neightbor, 2 - bilinear, 3 - bicubic, 4 - antialias. Default: 1') + parser.add_argument('--debug', action='store_true', + help='Print hashes as 2D maps (for debugging)') + parser.add_argument('filenames', nargs='+') + + args = parser.parse_args() + + if args.interpolation == 1: + interpolation = Image.NEAREST + elif args.interpolation == 2: + interpolation = Image.BILINEAR + elif args.interpolation == 3: + interpolation = Image.BICUBIC + elif args.interpolation == 4: + interpolation = Image.ANTIALIAS + + if args.quick: + method = blockhash_even + else: + method = blockhash + + for fn in args.filenames: + im = Image.open(fn) + + # convert indexed/grayscale images to RGB + if im.mode == '1' or im.mode == 'L' or im.mode == 'P': + im = im.convert('RGB') + elif im.mode == 'LA': + im = im.convert('RGBA') + + if args.size: + size = args.size.split('x') + size = (int(size[0]), int(size[1])) + im = im.resize(size, interpolation) + + hash = method(im, args.bits) + + print('{hash} {fn}'.format(fn=fn, hash=hash)) + + if args.debug: + bin_hash = '{:0{width}b}'.format(int(hash, 16), width=args.bits ** 2) + map = [bin_hash[i:i+args.bits] for i in range(0, len(bin_hash), args.bits)] + print("") + print("\n".join(map)) + print("") + + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py index 89a1c4f..c666a8e 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,19 @@ #!/usr/bin/env python -from distutils.core import setup +from setuptools import setup, find_packages setup( name='blockhash', - version='0.1', + version='0.1.1', description='Perceptual image hash calculation tool', author='Commons Machinery', author_email='dev@commonsmachinery.se', license='MIT', - scripts=['blockhash.py'], - requires=['pillow'], + packages=find_packages(), + entry_points={ + 'console_scripts': [ + 'blockhash=blockhash.command_line:main', + ], + }, + install_requires=['pillow'], ) diff --git a/test/test_blockhash.py b/test/test_blockhash.py index 3be5056..4c8717b 100644 --- a/test/test_blockhash.py +++ b/test/test_blockhash.py @@ -4,13 +4,18 @@ # Copyright 2014 Commons Machinery http://commonsmachinery.se/ # Distributed under an MIT license, please see LICENSE in the top dir. +import glob +import os import unittest -import blockhash + import PIL.Image as Image -import os, glob + +import blockhash + datadir = os.path.join(os.path.dirname(__file__), 'data') + class BlockhashTestCase(unittest.TestCase): def __init__(self, img_filename=None, hash_filename=None, method=None, bits=None): unittest.TestCase.__init__(self) @@ -40,6 +45,7 @@ def runTest(self): hash = "".join([str(x) for x in hash]) self.assertEqual(expected_hash, hash) + def load_tests(loader, tests, pattern): test_cases = unittest.TestSuite() for img_fn in (glob.glob(os.path.join(datadir, '*.jpg')) + @@ -52,3 +58,7 @@ def load_tests(loader, tests, pattern): test_cases.addTest(BlockhashTestCase(img_fn, hash_fn, method, bits)) pass return test_cases + + +if __name__ == '__main__': + unittest.main()