Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "3.7"
- "3.8"

install:
- pip install -e .[test]
- pip install coveralls
script: coverage run --source blockhash -m nose -v
after_success:
- coveralls
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include blockhash/resources/*
include blockhash/resources/scripts/*

23 changes: 0 additions & 23 deletions README.md

This file was deleted.

1 change: 1 addition & 0 deletions README.md
Binary file added assets/swiss_army_knife.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions blockhash/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '0.1.4'
1 change: 1 addition & 0 deletions blockhash/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFAULT_BITS = 16
69 changes: 4 additions & 65 deletions blockhash.py → blockhash/core.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
#! /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 math
import argparse
import PIL.Image as Image

import blockhash.constants

def median(data):
data = sorted(data)
Expand Down Expand Up @@ -51,7 +46,7 @@ def bits_to_hexhash(bits):
return '{0:0={width}x}'.format(int(''.join([str(x) for x in bits]), 2), width = len(bits) // 4)


def blockhash_even(im, bits):
def blockhash_even(im, bits=blockhash.constants.DEFAULT_BITS):
if im.mode == 'RGBA':
total_value = total_value_rgba
elif im.mode == 'RGB':
Expand Down Expand Up @@ -81,7 +76,7 @@ def blockhash_even(im, bits):
translate_blocks_to_bits(result, blocksize_x * blocksize_y)
return bits_to_hexhash(result)

def blockhash(im, bits):
def blockhash(im, bits=blockhash.constants.DEFAULT_BITS):
if im.mode == 'RGBA':
total_value = total_value_rgba
elif im.mode == 'RGB':
Expand Down Expand Up @@ -151,59 +146,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("")
34 changes: 34 additions & 0 deletions blockhash/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
**This is a fork of the original, stale project to fix some of the issues, add CI verification, and push to PyPI.**

[![Build Status](https://travis-ci.com/dsoprea/blockhash-python.svg?branch=master)](https://travis-ci.com/dsoprea/blockhash-python)
[![Coverage Status](https://coveralls.io/repos/github/dsoprea/blockhash-python/badge.svg?branch=master)](https://coveralls.io/github/dsoprea/blockhash-python?branch=master)

blockhash-python
================

This is a 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.

Installation
------------

Either download/clone the project directly, or install the "phash-blockhashio" package from PyPI.


Usage
-----

This script requires Python 2.x or Python 3 and Python Imaging (PIL) 1.1.6 or above.

Run `blockhash [list of images]` for calculating hashes.

Run `blockhash --help` for the list of options.

License
-------

Copyright 2014 Commons Machinery http://commonsmachinery.se/

Distributed under an MIT license, please see LICENSE in the top dir.

Contact: [email protected]
1 change: 1 addition & 0 deletions blockhash/resources/requirements-testing.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nose2
1 change: 1 addition & 0 deletions blockhash/resources/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillow
92 changes: 92 additions & 0 deletions blockhash/resources/scripts/blockhash
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#! /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 sys
import os
_APP_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
sys.path.insert(0, _APP_PATH)

import argparse

import PIL.Image as Image

import blockhash.constants
import blockhash.core
import blockhash.utility

def _main():
parser = argparse.ArgumentParser()

parser.add_argument(
'--quick',
action='store_true',
help='Use quick hashing method. Default: False')

parser.add_argument(
'--bits',
type=int, default=blockhash.constants.DEFAULT_BITS,
help='Create hash of size N^2 bits.')

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.core.blockhash_even
else:
method = blockhash.core.blockhash

for fn in args.filenames:
with Image.open(fn) as im:
im = blockhash.utility.normalize_image(im)

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()
8 changes: 8 additions & 0 deletions blockhash/utility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def normalize_image(im):
# convert indexed/grayscale images to RGB
if im.mode == '1' or im.mode == 'L' or im.mode == 'P':
return im.convert('RGB')
elif im.mode == 'LA':
return im.convert('RGBA')

return im
1 change: 1 addition & 0 deletions requirements-testing.txt
1 change: 1 addition & 0 deletions requirements.txt
41 changes: 35 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,43 @@
#!/usr/bin/env python

from distutils.core import setup
import os
import setuptools

setup(
name='blockhash',
version='0.1',
import blockhash

_APP_PATH = os.path.dirname(blockhash.__file__)

with open(os.path.join(_APP_PATH, 'resources', 'README.md')) as f:
long_description = f.read()

with open(os.path.join(_APP_PATH, 'resources', 'requirements.txt')) as f:
install_requires = [s.strip() for s in f.readlines()]

with open(os.path.join(_APP_PATH, 'resources', 'requirements-testing.txt')) as f:
test_requires = [s.strip() for s in f.readlines()]

setuptools.setup(
name='phash-blockhashio',
version=blockhash.__version__,
description='Perceptual image hash calculation tool',
long_description=long_description,
long_description_content_type='text/markdown',
author='Commons Machinery',
author_email='[email protected]',
license='MIT',
scripts=['blockhash.py'],
requires=['pillow'],
scripts=[
'blockhash/resources/scripts/blockhash'
],
install_requires=install_requires,
tests_require=install_requires + test_requires,
url='https://github.com/dsoprea/blockhash-python',
packages=setuptools.find_packages(exclude=['tests']),
include_package_data=True,
package_data={
'blockhash': [
'resources/README.md',
'resources/requirements.txt',
'resources/requirements-testing.txt',
],
},
)
Empty file added test/command/__init__.py
Empty file.
46 changes: 46 additions & 0 deletions test/command/test_blockhash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import unittest
import os
import subprocess

_APP_PATH = os.path.join(os.path.dirname(__file__), '..', '..')
_ASSETS_PATH = os.path.join(_APP_PATH, 'assets')
_SCRIPT_FILEPATH = os.path.join(_APP_PATH, 'blockhash', 'resources', 'scripts', 'blockhash')


class TestCommand(unittest.TestCase):
def test_run__slow(self):
filepath = os.path.join(_ASSETS_PATH, 'swiss_army_knife.jpg')

cmd = [
_SCRIPT_FILEPATH,
filepath,
]

output = \
subprocess.check_output(
cmd,
stderr=subprocess.STDOUT,
universal_newlines=True)

parts = output.split()

self.assertEquals(parts[0], 'ff4ff907f801e800ff01fc83fc03f003e00fc20fe00ff1cff00ff81ffc1ffe7f')

def test_run__quick(self):
filepath = os.path.join(_ASSETS_PATH, 'swiss_army_knife.jpg')

cmd = [
_SCRIPT_FILEPATH,
'--quick',
filepath,
]

output = \
subprocess.check_output(
cmd,
stderr=subprocess.STDOUT,
universal_newlines=True)

parts = output.split()

self.assertEquals(parts[0], 'ffcff903f801e800ff01fc03fc03f803e00fc20fe00ff1cff00ff01ffc1ffe7f')
Loading