From 02797ca214c52f5e6e93520f72c766c34e1a0d2a Mon Sep 17 00:00:00 2001 From: Mark Smith Date: Sat, 18 May 2019 21:08:38 -0700 Subject: [PATCH] Organize blockhash as a module This makes blockhash install as a module which makes it possible to be packaged in bazel rules, but is probably also slightly more useful in general. This also updates to use setuptools and converts the script to an entry_point which will make it more broadly supported on different platforms. --- blockhash/__init__.py | 1 + blockhash.py => blockhash/blockhash.py | 62 ++-------------------- blockhash/command_line.py | 73 ++++++++++++++++++++++++++ setup.py | 13 +++-- test/test_blockhash.py | 14 ++++- 5 files changed, 100 insertions(+), 63 deletions(-) create mode 100644 blockhash/__init__.py rename blockhash.py => blockhash/blockhash.py (72%) create mode 100755 blockhash/command_line.py 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()