Skip to content

Commit

Permalink
Change package name to a currently available name
Browse files Browse the repository at this point in the history
Now "phash-blockhashio" instead of "blockhash", which is already taken.

- Packageified project
  - So it can both be imported from other projects.
  - So it can be packaged.
  - So it can be pushed to PyPI.

- Split library functionality from script functionality.
- Moved version to package-level variable.

- Migrated from distutils to setuptools.
  - Moved requirements to file. Renamed to install_requires. It was
    "requires" and it looks like distutils used "requires", but issue commonsmachinery#6
    stated that there might have been an issue with this.

  - Fixed-up required fields.

- Added minimal tests so that we can establish a verification build.

- Refactored --quick command-line option to be a store_true parameter
  rather than an argument-based parameter. It was confusing.
  • Loading branch information
dsoprea committed Aug 3, 2020
1 parent f28b741 commit 2b96735
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 92 deletions.
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.1'
63 changes: 0 additions & 63 deletions blockhash.py → blockhash/blockhash.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
#! /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

def median(data):
data = sorted(data)
Expand Down Expand Up @@ -151,59 +144,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("")
23 changes: 23 additions & 0 deletions blockhash/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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.

Usage
-----

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

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

Run `blockhash.py --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: dev@commonsmachinery.se
1 change: 1 addition & 0 deletions blockhash/resources/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pillow
95 changes: 95 additions & 0 deletions blockhash/resources/scripts/blockhash
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#! /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.blockhash

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=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.blockhash.blockhash_even
else:
method = blockhash.blockhash.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()
1 change: 1 addition & 0 deletions requirements.txt
37 changes: 31 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,39 @@
#!/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()]

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='dev@commonsmachinery.se',
license='MIT',
scripts=['blockhash.py'],
requires=['pillow'],
scripts=[
'blockhash/resources/scripts/blockhash'
],
install_requires=install_requires,
tests_require=install_requires + ['nose2'],
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',
],
},
)
Empty file added tests/__init__.py
Empty file.
Empty file added tests/command/__init__.py
Empty file.
38 changes: 38 additions & 0 deletions tests/command/test_blockhash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
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)

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)

parts = output.split()

self.assertEquals(parts[0], 'ffcff903f801e800ff01fc03fc03f803e00fc20fe00ff1cff00ff01ffc1ffe7f')
26 changes: 26 additions & 0 deletions tests/test_blockhash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
import os

import PIL.Image as Image

import blockhash.blockhash

_ASSETS_PATH = os.path.join(os.path.dirname(__file__), '..', 'assets')


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

with Image.open(filepath) as im:
digest = blockhash.blockhash.blockhash_even(im, 16)

self.assertEquals(digest, 'ffcff903f801e800ff01fc03fc03f803e00fc20fe00ff1cff00ff01ffc1ffe7f')

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

with Image.open(filepath) as im:
digest = blockhash.blockhash.blockhash(im, 16)

self.assertEquals(digest, 'ff4ff907f801e800ff01fc83fc03f003e00fc20fe00ff1cff00ff81ffc1ffe7f')

0 comments on commit 2b96735

Please sign in to comment.