├── .gitignore ├── LICENSE ├── README.md ├── blockhash.py ├── setup.py └── test ├── __init__.py ├── data ├── 00002701.jpg ├── 00002701_16_1.txt ├── 00002701_16_2.txt ├── 00011601.jpg ├── 00011601_16_1.txt ├── 00011601_16_2.txt ├── 00094701.jpg ├── 00094701_16_1.txt ├── 00094701_16_2.txt ├── 00106901.jpg ├── 00106901_16_1.txt ├── 00106901_16_2.txt ├── 00133601.jpg ├── 00133601_16_1.txt ├── 00133601_16_2.txt ├── 00136101.jpg ├── 00136101_16_1.txt ├── 00136101_16_2.txt ├── 00631701.jpg ├── 00631701_16_1.txt ├── 00631701_16_2.txt ├── 00631801.jpg ├── 00631801_16_1.txt ├── 00631801_16_2.txt ├── 01109801.jpg ├── 01109801_16_1.txt ├── 01109801_16_2.txt ├── 06855701.jpg ├── 06855701_16_1.txt ├── 06855701_16_2.txt ├── 24442301.jpg ├── 24442301_16_1.txt ├── 24442301_16_2.txt ├── 32499201.jpg ├── 32499201_16_1.txt ├── 32499201_16_2.txt ├── Babylonian.png ├── Babylonian_16_1.txt ├── Babylonian_16_2.txt ├── clipper_ship.jpg ├── clipper_ship_16_1.txt ├── clipper_ship_16_2.txt ├── emptyBasket.png ├── emptyBasket_16_1.txt ├── emptyBasket_16_2.txt ├── puffy_white.png ├── puffy_white_16_1.txt ├── puffy_white_16_2.txt ├── sources.txt ├── stoplights.jpg ├── stoplights_16_1.txt └── stoplights_16_2.txt └── test_blockhash.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | *~ 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Commons Machinery 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | blockhash-python 2 | ================ 3 | 4 | This is a perceptual image hash calculation tool based on algorithm descibed in 5 | Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu. 6 | 7 | Usage 8 | ----- 9 | 10 | This script requires Python 2.x or Python 3 and Python Imaging (PIL) 1.1.6 or above. 11 | 12 | Run `blockhash.py [list of images]` for calculating hashes. 13 | 14 | Run `blockhash.py --help` for the list of options. 15 | 16 | License 17 | ------- 18 | 19 | Copyright 2014 Commons Machinery http://commonsmachinery.se/ 20 | 21 | Distributed under an MIT license, please see LICENSE in the top dir. 22 | 23 | Contact: dev@commonsmachinery.se 24 | -------------------------------------------------------------------------------- /blockhash.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # 3 | # Perceptual image hash calculation tool based on algorithm descibed in 4 | # Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu 5 | # 6 | # Copyright 2014 Commons Machinery http://commonsmachinery.se/ 7 | # Distributed under an MIT license, please see LICENSE in the top dir. 8 | 9 | import math 10 | import argparse 11 | import PIL.Image as Image 12 | 13 | def median(data): 14 | data = sorted(data) 15 | length = len(data) 16 | if length % 2 == 0: 17 | return (data[length // 2 - 1] + data[length // 2]) / 2.0 18 | return data[length // 2] 19 | 20 | def total_value_rgba(im, data, x, y): 21 | r, g, b, a = data[y * im.size[0] + x] 22 | if a == 0: 23 | return 765 24 | else: 25 | return r + g + b 26 | 27 | def total_value_rgb(im, data, x, y): 28 | r, g, b = data[y * im.size[0] + x] 29 | return r + g + b 30 | 31 | def translate_blocks_to_bits(blocks, pixels_per_block): 32 | half_block_value = pixels_per_block * 256 * 3 / 2 33 | 34 | # Compare medians across four horizontal bands 35 | bandsize = len(blocks) // 4 36 | for i in range(4): 37 | m = median(blocks[i * bandsize : (i + 1) * bandsize]) 38 | for j in range(i * bandsize, (i + 1) * bandsize): 39 | v = blocks[j] 40 | 41 | # Output a 1 if the block is brighter than the median. 42 | # With images dominated by black or white, the median may 43 | # end up being 0 or the max value, and thus having a lot 44 | # of blocks of value equal to the median. To avoid 45 | # generating hashes of all zeros or ones, in that case output 46 | # 0 if the median is in the lower value space, 1 otherwise 47 | blocks[j] = int(v > m or (abs(v - m) < 1 and m > half_block_value)) 48 | 49 | 50 | def bits_to_hexhash(bits): 51 | return '{0:0={width}x}'.format(int(''.join([str(x) for x in bits]), 2), width = len(bits) // 4) 52 | 53 | 54 | def blockhash_even(im, bits): 55 | if im.mode == 'RGBA': 56 | total_value = total_value_rgba 57 | elif im.mode == 'RGB': 58 | total_value = total_value_rgb 59 | else: 60 | raise RuntimeError('Unsupported image mode: {}'.format(im.mode)) 61 | 62 | data = im.getdata() 63 | width, height = im.size 64 | blocksize_x = width // bits 65 | blocksize_y = height // bits 66 | 67 | result = [] 68 | 69 | for y in range(bits): 70 | for x in range(bits): 71 | value = 0 72 | 73 | for iy in range(blocksize_y): 74 | for ix in range(blocksize_x): 75 | cx = x * blocksize_x + ix 76 | cy = y * blocksize_y + iy 77 | value += total_value(im, data, cx, cy) 78 | 79 | result.append(value) 80 | 81 | translate_blocks_to_bits(result, blocksize_x * blocksize_y) 82 | return bits_to_hexhash(result) 83 | 84 | def blockhash(im, bits): 85 | if im.mode == 'RGBA': 86 | total_value = total_value_rgba 87 | elif im.mode == 'RGB': 88 | total_value = total_value_rgb 89 | else: 90 | raise RuntimeError('Unsupported image mode: {}'.format(im.mode)) 91 | 92 | data = im.getdata() 93 | width, height = im.size 94 | 95 | even_x = width % bits == 0 96 | even_y = height % bits == 0 97 | 98 | if even_x and even_y: 99 | return blockhash_even(im, bits) 100 | 101 | blocks = [[0 for col in range(bits)] for row in range(bits)] 102 | 103 | block_width = float(width) / bits 104 | block_height = float(height) / bits 105 | 106 | for y in range(height): 107 | if even_y: 108 | # don't bother dividing y, if the size evenly divides by bits 109 | block_top = block_bottom = int(y // block_height) 110 | weight_top, weight_bottom = 1, 0 111 | else: 112 | y_frac, y_int = math.modf((y + 1) % block_height) 113 | 114 | weight_top = (1 - y_frac) 115 | weight_bottom = (y_frac) 116 | 117 | # y_int will be 0 on bottom/right borders and on block boundaries 118 | if y_int > 0 or (y + 1) == height: 119 | block_top = block_bottom = int(y // block_height) 120 | else: 121 | block_top = int(y // block_height) 122 | block_bottom = int(-(-y // block_height)) # int(math.ceil(float(y) / block_height)) 123 | 124 | for x in range(width): 125 | value = total_value(im, data, x, y) 126 | 127 | if even_x: 128 | # don't bother dividing x, if the size evenly divides by bits 129 | block_left = block_right = int(x // block_width) 130 | weight_left, weight_right = 1, 0 131 | else: 132 | x_frac, x_int = math.modf((x + 1) % block_width) 133 | 134 | weight_left = (1 - x_frac) 135 | weight_right = (x_frac) 136 | 137 | # x_int will be 0 on bottom/right borders and on block boundaries 138 | if x_int > 0 or (x + 1) == width: 139 | block_left = block_right = int(x // block_width) 140 | else: 141 | block_left = int(x // block_width) 142 | block_right = int(-(-x // block_width)) # int(math.ceil(float(x) / block_width)) 143 | 144 | # add weighted pixel value to relevant blocks 145 | blocks[block_top][block_left] += value * weight_top * weight_left 146 | blocks[block_top][block_right] += value * weight_top * weight_right 147 | blocks[block_bottom][block_left] += value * weight_bottom * weight_left 148 | blocks[block_bottom][block_right] += value * weight_bottom * weight_right 149 | 150 | result = [blocks[row][col] for row in range(bits) for col in range(bits)] 151 | 152 | translate_blocks_to_bits(result, block_width * block_height) 153 | return bits_to_hexhash(result) 154 | 155 | if __name__ == '__main__': 156 | parser = argparse.ArgumentParser() 157 | 158 | parser.add_argument('--quick', type=bool, default=False, 159 | help='Use quick hashing method. Default: False') 160 | parser.add_argument('--bits', type=int, default=16, 161 | help='Create hash of size N^2 bits. Default: 16') 162 | parser.add_argument('--size', 163 | help='Resize image to specified size before hashing, e.g. 256x256') 164 | parser.add_argument('--interpolation', type=int, default=1, choices=[1, 2, 3, 4], 165 | help='Interpolation method: 1 - nearest neightbor, 2 - bilinear, 3 - bicubic, 4 - antialias. Default: 1') 166 | parser.add_argument('--debug', action='store_true', 167 | help='Print hashes as 2D maps (for debugging)') 168 | parser.add_argument('filenames', nargs='+') 169 | 170 | args = parser.parse_args() 171 | 172 | if args.interpolation == 1: 173 | interpolation = Image.NEAREST 174 | elif args.interpolation == 2: 175 | interpolation = Image.BILINEAR 176 | elif args.interpolation == 3: 177 | interpolation = Image.BICUBIC 178 | elif args.interpolation == 4: 179 | interpolation = Image.ANTIALIAS 180 | 181 | if args.quick: 182 | method = blockhash_even 183 | else: 184 | method = blockhash 185 | 186 | for fn in args.filenames: 187 | im = Image.open(fn) 188 | 189 | # convert indexed/grayscale images to RGB 190 | if im.mode == '1' or im.mode == 'L' or im.mode == 'P': 191 | im = im.convert('RGB') 192 | elif im.mode == 'LA': 193 | im = im.convert('RGBA') 194 | 195 | if args.size: 196 | size = args.size.split('x') 197 | size = (int(size[0]), int(size[1])) 198 | im = im.resize(size, interpolation) 199 | 200 | hash = method(im, args.bits) 201 | 202 | print('{hash} {fn}'.format(fn=fn, hash=hash)) 203 | 204 | if args.debug: 205 | bin_hash = '{:0{width}b}'.format(int(hash, 16), width=args.bits ** 2) 206 | map = [bin_hash[i:i+args.bits] for i in range(0, len(bin_hash), args.bits)] 207 | print("") 208 | print("\n".join(map)) 209 | print("") 210 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name='blockhash', 7 | version='0.1', 8 | description='Perceptual image hash calculation tool', 9 | author='Commons Machinery', 10 | author_email='dev@commonsmachinery.se', 11 | license='MIT', 12 | scripts=['blockhash.py'], 13 | requires=['pillow'], 14 | ) 15 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/__init__.py -------------------------------------------------------------------------------- /test/data/00002701.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00002701.jpg -------------------------------------------------------------------------------- /test/data/00002701_16_1.txt: -------------------------------------------------------------------------------- 1 | f81bf99ffb803400e07f8c5d849f049707033a033fe33fe1bfe00e618ee30ca7 00002701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00002701_16_2.txt: -------------------------------------------------------------------------------- 1 | f81bf99ffb803400e07f8c7d049f058706013e233fe33fe11f600e638ea30def 00002701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00011601.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00011601.jpg -------------------------------------------------------------------------------- /test/data/00011601_16_1.txt: -------------------------------------------------------------------------------- 1 | 3fff1ca004e80fe81fe00fe01fe81de8fffb8ffb00e3000000f303ff83ff4185 00011601.jpg 2 | -------------------------------------------------------------------------------- /test/data/00011601_16_2.txt: -------------------------------------------------------------------------------- 1 | 3fff1ca004e80fe81fe00fe01fe81de8fffb0ffb00610031003b03ff80df01ff 00011601.jpg 2 | -------------------------------------------------------------------------------- /test/data/00094701.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00094701.jpg -------------------------------------------------------------------------------- /test/data/00094701_16_1.txt: -------------------------------------------------------------------------------- 1 | 03ff007d02da33f325e366f90501379fd103c12ffd04fc787ff31e511f111702 00094701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00094701_16_2.txt: -------------------------------------------------------------------------------- 1 | 03ff007d02da33f325e366690581b79fd103f307fc0474f93ff31e117f550700 00094701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00106901.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00106901.jpg -------------------------------------------------------------------------------- /test/data/00106901_16_1.txt: -------------------------------------------------------------------------------- 1 | ff80fe80fe00fe01ff00ff00ff00ff00fec0ff00f820fae0fcc0e7f0e7e0e0c0 00106901.jpg 2 | -------------------------------------------------------------------------------- /test/data/00106901_16_2.txt: -------------------------------------------------------------------------------- 1 | ff80fe80fc01fe01ff00ff00ff00ff00fec0fd20f0e0fac0ecf0e7f0e7e0e040 00106901.jpg 2 | -------------------------------------------------------------------------------- /test/data/00133601.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00133601.jpg -------------------------------------------------------------------------------- /test/data/00133601_16_1.txt: -------------------------------------------------------------------------------- 1 | 00000fe07ff8fff81ff839d831883bcc39dc31ac300c3ffc9ef88e708041f1bf 00133601.jpg 2 | -------------------------------------------------------------------------------- /test/data/00133601_16_2.txt: -------------------------------------------------------------------------------- 1 | 00000fe07ff8fff81ff8399831983bcc35ac303c384c3ffc8ef00660c003ffff 00133601.jpg 2 | -------------------------------------------------------------------------------- /test/data/00136101.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00136101.jpg -------------------------------------------------------------------------------- /test/data/00136101_16_1.txt: -------------------------------------------------------------------------------- 1 | 000081f887ff8fff0fe00fe00ff80ff807fc07fc03f803f803e001e007f0ffff 00136101.jpg 2 | -------------------------------------------------------------------------------- /test/data/00136101_16_2.txt: -------------------------------------------------------------------------------- 1 | 000083f887fe8fff0fe00fe00ff80ff807fc07f807f803f803e003e007e0ffff 00136101.jpg 2 | -------------------------------------------------------------------------------- /test/data/00631701.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00631701.jpg -------------------------------------------------------------------------------- /test/data/00631701_16_1.txt: -------------------------------------------------------------------------------- 1 | fffffc01bd0180018c61806fcf7181efae6d8e71893983878001800fc0ffefff 00631701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00631701_16_2.txt: -------------------------------------------------------------------------------- 1 | fffff803bd018001886382e78f7382afceef8e71813187070001800fc0ffffff 00631701.jpg 2 | -------------------------------------------------------------------------------- /test/data/00631801.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/00631801.jpg -------------------------------------------------------------------------------- /test/data/00631801_16_1.txt: -------------------------------------------------------------------------------- 1 | ffff8001bfb182018e8186e58dd587e58641807187df9be780018007e07fffff 00631801.jpg 2 | -------------------------------------------------------------------------------- /test/data/00631801_16_2.txt: -------------------------------------------------------------------------------- 1 | ffffc001bfa182018aa186e1877786e78623827385399fcf0001800fc0ffffff 00631801.jpg 2 | -------------------------------------------------------------------------------- /test/data/01109801.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/01109801.jpg -------------------------------------------------------------------------------- /test/data/01109801_16_1.txt: -------------------------------------------------------------------------------- 1 | f80ff80ff007f007f007f183f4c3c5c3c7c3c7e3c1e3c007c003c007e087ffff 01109801.jpg 2 | -------------------------------------------------------------------------------- /test/data/01109801_16_2.txt: -------------------------------------------------------------------------------- 1 | f80ff80ff007f007f007f183f4c3c5c3c7c3c7e3c1e3c007c007c007e007ffff 01109801.jpg 2 | -------------------------------------------------------------------------------- /test/data/06855701.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/06855701.jpg -------------------------------------------------------------------------------- /test/data/06855701_16_1.txt: -------------------------------------------------------------------------------- 1 | fefd0fff000f0003037f03bc00bd11ff01ff01fe01bc01ff01ff007f03fd03f8 06855701.jpg 2 | -------------------------------------------------------------------------------- /test/data/06855701_16_2.txt: -------------------------------------------------------------------------------- 1 | fefd0fff000f0003037f03bc01bc11ff01fe01be01bd03ff007f007f0ffd0bf0 06855701.jpg 2 | -------------------------------------------------------------------------------- /test/data/24442301.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/24442301.jpg -------------------------------------------------------------------------------- /test/data/24442301_16_1.txt: -------------------------------------------------------------------------------- 1 | ffff8101bf0192059005860f9d8fbf8d9f818f818f998e8b8f8982e18001ffff 24442301.jpg 2 | -------------------------------------------------------------------------------- /test/data/24442301_16_2.txt: -------------------------------------------------------------------------------- 1 | ffff8101bf0192059005860f9d8fbfa99f818f818f999e198fc182e18001ffff 24442301.jpg 2 | -------------------------------------------------------------------------------- /test/data/32499201.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/32499201.jpg -------------------------------------------------------------------------------- /test/data/32499201_16_1.txt: -------------------------------------------------------------------------------- 1 | 0002001f1fff1fff7ff31ff303f300006f9178f0f8701ee0ffc8ce88c1cc01fc 32499201.jpg 2 | -------------------------------------------------------------------------------- /test/data/32499201_16_2.txt: -------------------------------------------------------------------------------- 1 | 0002001f1fff1fff7ff71ff301f300007f9278f0f8700fc0ff98cc88c1cc03fc 32499201.jpg 2 | -------------------------------------------------------------------------------- /test/data/Babylonian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/Babylonian.png -------------------------------------------------------------------------------- /test/data/Babylonian_16_1.txt: -------------------------------------------------------------------------------- 1 | fe7ffe7ffe7ffc7ffc7ffc7ffc7f0000c103e187f18ff08f8003fc3ff81ff81f Babylonian.png 2 | -------------------------------------------------------------------------------- /test/data/Babylonian_16_2.txt: -------------------------------------------------------------------------------- 1 | fe7ffe7ffe7ffc7ffc7ffc7ffc000000e107e187f89fe007c00ff81ff81ff83f Babylonian.png 2 | -------------------------------------------------------------------------------- /test/data/clipper_ship.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/clipper_ship.jpg -------------------------------------------------------------------------------- /test/data/clipper_ship_16_1.txt: -------------------------------------------------------------------------------- 1 | 00007ff07ff07fe07fe67ff07560600077fe701e7f5e000079fd40410001ffff clipper_ship.jpg 2 | -------------------------------------------------------------------------------- /test/data/clipper_ship_16_2.txt: -------------------------------------------------------------------------------- 1 | 00007ff07ff07fe07fe67ff07560600077fe701e7f5e000079fd40410001ffff clipper_ship.jpg 2 | -------------------------------------------------------------------------------- /test/data/emptyBasket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/emptyBasket.png -------------------------------------------------------------------------------- /test/data/emptyBasket_16_1.txt: -------------------------------------------------------------------------------- 1 | fffffe07e00380009b899f818f8f8c439f9fdc0fc807c803c003c00fe03ff1ff emptyBasket.png 2 | -------------------------------------------------------------------------------- /test/data/emptyBasket_16_2.txt: -------------------------------------------------------------------------------- 1 | fffffe07e00380009f899f818f0b8c4b9f9fd00fcc07cc03c003e00fe07ff1ff emptyBasket.png 2 | -------------------------------------------------------------------------------- /test/data/puffy_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/puffy_white.png -------------------------------------------------------------------------------- /test/data/puffy_white_16_1.txt: -------------------------------------------------------------------------------- 1 | 00000000fffffffff803f807f807f80ff90ff90fb90c9980ffffffff00000000 puffy_white.png 2 | -------------------------------------------------------------------------------- /test/data/puffy_white_16_2.txt: -------------------------------------------------------------------------------- 1 | 00000000fffffffff803f807f807f80ff90ff90fb90c9980ffffffff00000000 puffy_white.png 2 | -------------------------------------------------------------------------------- /test/data/sources.txt: -------------------------------------------------------------------------------- 1 | puffy_white.jpg - http://www.nasa.gov/multimedia/imagegallery/image_feature_2466.html 2 | clipper_ship.jpg - http://commons.wikimedia.org/wiki/File:%27Clipper_Ship_Great_Republic%27_by_James_Butterworth,_c._1853_-_Old_State_House_Museum,_Boston,_MA_-_IMG_6709.JPG 3 | emptyBasket.png - https://openclipart.org/detail/194657/plastic-laundry-basket-by-gubrww2-194657 4 | Babylonian.png - https://openclipart.org/detail/194594/babylonian-or-summerian-religious-deity-by-lordoftheloch-194594 5 | 6 | The remaining test images come from http://www.getty.edu/about/opencontent.html 7 | -------------------------------------------------------------------------------- /test/data/stoplights.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/commonsmachinery/blockhash-python/f28b741a1f6bfff25763ae42dc2190051079998c/test/data/stoplights.jpg -------------------------------------------------------------------------------- /test/data/stoplights_16_1.txt: -------------------------------------------------------------------------------- 1 | 0fe01fe007e027c037c077c00fe00fe007e047c001c003c007e007e007e003e0 stoplights.jpg 2 | -------------------------------------------------------------------------------- /test/data/stoplights_16_2.txt: -------------------------------------------------------------------------------- 1 | 0fe01fe007e027c037c077c00fc00fe007e047c001c003c007e007e007e003c0 stoplights.jpg 2 | -------------------------------------------------------------------------------- /test/test_blockhash.py: -------------------------------------------------------------------------------- 1 | # Perceptual image hash calculation tool based on algorithm descibed in 2 | # Block Mean Value Based Image Perceptual Hashing by Bian Yang, Fan Gu and Xiamu Niu 3 | # 4 | # Copyright 2014 Commons Machinery http://commonsmachinery.se/ 5 | # Distributed under an MIT license, please see LICENSE in the top dir. 6 | 7 | import unittest 8 | import blockhash 9 | import PIL.Image as Image 10 | import os, glob 11 | 12 | datadir = os.path.join(os.path.dirname(__file__), 'data') 13 | 14 | class BlockhashTestCase(unittest.TestCase): 15 | def __init__(self, img_filename=None, hash_filename=None, method=None, bits=None): 16 | unittest.TestCase.__init__(self) 17 | self.img_filename = img_filename 18 | self.hash_filename = hash_filename 19 | self.method = method 20 | self.bits = bits 21 | 22 | def runTest(self): 23 | im = Image.open(self.img_filename) 24 | 25 | # convert indexed/grayscale images to RGB 26 | if im.mode == '1' or im.mode == 'L' or im.mode == 'P': 27 | im = im.convert('RGB') 28 | elif im.mode == 'LA': 29 | im = im.convert('RGBA') 30 | 31 | with open(self.hash_filename) as f: 32 | expected_hash = f.readline().split()[0] 33 | 34 | if self.method == 1: 35 | method = blockhash.blockhash_even 36 | elif self.method == 2: 37 | method = blockhash.blockhash 38 | 39 | hash = method(im, self.bits) 40 | hash = "".join([str(x) for x in hash]) 41 | self.assertEqual(expected_hash, hash) 42 | 43 | def load_tests(loader, tests, pattern): 44 | test_cases = unittest.TestSuite() 45 | for img_fn in (glob.glob(os.path.join(datadir, '*.jpg')) + 46 | glob.glob(os.path.join(datadir, '*.png'))): 47 | for m in range(2): 48 | bits = 16 49 | method = m + 1 50 | basename, ext = os.path.splitext(img_fn) 51 | hash_fn = basename + '_{}_{}.txt'.format(bits, method) 52 | test_cases.addTest(BlockhashTestCase(img_fn, hash_fn, method, bits)) 53 | pass 54 | return test_cases 55 | --------------------------------------------------------------------------------