From dee1889ca9d2f37fce91596e60de8a63dbabe61f Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 17:38:38 -0400 Subject: [PATCH 1/7] Renamed library to match project name, added setup.py from faisyl --- {python_phash => pyphash}/__init__.py | 0 {python_phash => pyphash}/core.py | 0 {python_phash => pyphash}/exceptions.py | 0 {python_phash => pyphash}/extensions.py | 0 {python_phash => pyphash}/helpers.py | 0 {python_phash => pyphash}/structs.py | 0 setup.py | 13 +++++++++++++ 7 files changed, 13 insertions(+) rename {python_phash => pyphash}/__init__.py (100%) rename {python_phash => pyphash}/core.py (100%) rename {python_phash => pyphash}/exceptions.py (100%) rename {python_phash => pyphash}/extensions.py (100%) rename {python_phash => pyphash}/helpers.py (100%) rename {python_phash => pyphash}/structs.py (100%) create mode 100644 setup.py diff --git a/python_phash/__init__.py b/pyphash/__init__.py similarity index 100% rename from python_phash/__init__.py rename to pyphash/__init__.py diff --git a/python_phash/core.py b/pyphash/core.py similarity index 100% rename from python_phash/core.py rename to pyphash/core.py diff --git a/python_phash/exceptions.py b/pyphash/exceptions.py similarity index 100% rename from python_phash/exceptions.py rename to pyphash/exceptions.py diff --git a/python_phash/extensions.py b/pyphash/extensions.py similarity index 100% rename from python_phash/extensions.py rename to pyphash/extensions.py diff --git a/python_phash/helpers.py b/pyphash/helpers.py similarity index 100% rename from python_phash/helpers.py rename to pyphash/helpers.py diff --git a/python_phash/structs.py b/pyphash/structs.py similarity index 100% rename from python_phash/structs.py rename to pyphash/structs.py diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..91073b4 --- /dev/null +++ b/setup.py @@ -0,0 +1,13 @@ +# setup.py contributed by Faisal Puthuparackat + +from setuptools import setup + +setup(name='pyphash', + version='0.1', + description='A python ctypes wrapper for the pHash library', + url='https://github.com/paulkiernan/pyphash', + author='Paul Kiernan', + author_email='hi@paulynomial.com', + license='GPLv3', + packages=['pyphash'], + zip_safe=False) From f6ad35e0306cd77f6a8fdfb069d7f67c40566b8c Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 17:59:47 -0400 Subject: [PATCH 2/7] Change sample image to one actually available and reduce rotations --- generate_images.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/generate_images.sh b/generate_images.sh index 4794793..74d61ed 100755 --- a/generate_images.sh +++ b/generate_images.sh @@ -4,22 +4,24 @@ set -eux mkdir -p test_images +SAMPLE_NAME=sample-city-park-400x300.jpg + cd test_images -wget http://s3.amazonaws.com/i.jpg.to/l/8453 -O tony.jpg +wget https://download.samplelib.com/jpeg/$SAMPLE_NAME -convert tony.jpg tony.png +convert $SAMPLE_NAME sample.png # Scaled -convert tony.png -resize 50% tony-scaled-50.png -convert tony.png -resize 75% tony-scaled-75.png -convert tony.png -resize 150% tony-scaled-150.png +convert sample.png -resize 50% sample-scaled-50.png +convert sample.png -resize 75% sample-scaled-75.png +convert sample.png -resize 150% sample-scaled-150.png # Blurs -convert tony.png -blur 2x2 tony-blur-2x2.png -convert tony.png -blur 5x2 tony-blur-5x2.png -convert tony.png -blur 0x4 tony-blur-0x4.png +convert sample.png -blur 2x2 sample-blur-2x2.png +convert sample.png -blur 5x2 sample-blur-5x2.png +convert sample.png -blur 0x4 sample-blur-0x4.png # Rotations -for i in $(seq 1 360); do - convert tony.png -rotate $i "tony-rotate-$i.png" +for i in $(seq 1 90); do + convert sample.png -rotate $i "sample-rotate-$i.png" done From acf258bfec251cdd58c4061d17d18485ee69cc06 Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 18:06:36 -0400 Subject: [PATCH 3/7] Added sample_images and backup files to ignore list --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 44ead8c..18c54e1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,12 @@ __pycache__/ *.py[cod] *$py.class +# Backups +*.bak + +# Test images +test_images + # C extensions *.so From 274b4c79c08f41dd2444cd6fea79f12a0a4c39e8 Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 18:06:52 -0400 Subject: [PATCH 4/7] Upgraded to Python3 and removed redundant MD5 hashing --- test_hashing.py | 66 +++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 41 deletions(-) diff --git a/test_hashing.py b/test_hashing.py index b573efc..2ddfe4f 100755 --- a/test_hashing.py +++ b/test_hashing.py @@ -4,74 +4,58 @@ """ phashlib Core C++ interface """ -# Future-proof -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from __future__ import unicode_literals - -# Standard Library Imports -from hashlib import md5 -from itertools import imap - # Third-party Libraries import matplotlib.pyplot as plt from IPython.display import Image, display # Custom libphash bindings -import python_phash as phash +import pyphash as phash print(phash) print(dir(phash)) TEST_IMAGES = { - 'base': 'test_images/tony.png', + 'base': 'test_images/sample.png', 'blurs': { - '2x2': 'test_images/tony-blur-2x2.png', - '5x2': 'test_images/tony-blur-5x2.png', - '0x4': 'test_images/tony-blur-0x4.png', + '2x2': 'test_images/sample-blur-2x2.png', + '5x2': 'test_images/sample-blur-5x2.png', + '0x4': 'test_images/sample-blur-0x4.png', }, 'rotations': { - '10°': 'test_images/tony-rotate-10.png', - '30°': 'test_images/tony-rotate-30.png', - '90°': 'test_images/tony-rotate-90.png', - '180°': 'test_images/tony-rotate-180.png', + '10°': 'test_images/sample-rotate-10.png', + '30°': 'test_images/sample-rotate-30.png', + '70°': 'test_images/sample-rotate-70.png', + '90°': 'test_images/sample-rotate-90.png', }, 'scaled': { - '50%': 'test_images/tony-scaled-50.png', - '75%': 'test_images/tony-scaled-75.png', - '150%': 'test_images/tony-scaled-150.png', + '50%': 'test_images/sample-scaled-50.png', + '75%': 'test_images/sample-scaled-75.png', + '150%': 'test_images/sample-scaled-150.png', } } # Helpers def hamming_distance(string1, string2): - return sum(imap(str.__ne__, string1, string2)) + return sum(map(str.__ne__, string1, string2)) def test_image_correlations(image_distortion_type, is_ipython_notebook=False): - base_image_path = TEST_IMAGES.get('base') base_image = Image(filename=base_image_path) base_phash_digest = phash.image_digest(base_image_path) base_phash_imagehash = phash.imagehash(base_image_path) - base_md5_digest = md5(open(base_image_path).read()).hexdigest() if is_ipython_notebook: display(base_image) - print("md5 Digest: {0}".format(base_md5_digest)) print("pHash Digest: {0}".format(base_phash_digest)) print("pHash DCT Hash: {0}".format(base_phash_imagehash)) - for distortion_info, image_path in TEST_IMAGES.get(image_distortion_type).iteritems(): + for distortion_info, image_path in TEST_IMAGES.get(image_distortion_type).items(): _image = Image(filename=image_path) if is_ipython_notebook: display(_image) _image_phash_digest = phash.image_digest(image_path) _image_phash_imagehash = phash.imagehash(image_path) - _image_md5_digest = md5(open(image_path).read()).hexdigest() - print("md5 Digest: {0}".format(_image_md5_digest)) - print("md5 Hamming Distance: {0}".format(hamming_distance(base_md5_digest, _image_md5_digest))) print("pHash Digest: {0}".format(_image_phash_digest)) print("pHash DCT Hash: {0}".format(_image_phash_imagehash)) print("pHash DCT Hash Hamming Distance: {0}".format( @@ -88,25 +72,25 @@ def test_image_correlations(image_distortion_type, is_ipython_notebook=False): def test_rotations(): - base_image_path = TEST_IMAGES.get('base') base_phash_digest = phash.image_digest(base_image_path) correlations = [] - for x in xrange(1, 361): - _image_path = "test_images/tony-rotate-{0}.png".format(x) + for x in range(1, 90): + _image_path = "test_images/sample-rotate-{0}.png".format(x) _image_phash_digest = phash.image_digest(_image_path) correlations.append(( x, phash.cross_correlation(base_phash_digest, _image_phash_digest) )) - return zip(*correlations) + return list(zip(*correlations)) -test_image_correlations('scaled', is_ipython_notebook=False) -test_image_correlations('blurs', is_ipython_notebook=False) -test_image_correlations('rotations', is_ipython_notebook=False) -xx, yy = test_rotations() -plt.plot(xx, yy) -plt.plot(xx[:10], yy[:10]) -plt.plot(xx[-10:], yy[-10:]) +if __name__ == "__main__": + test_image_correlations('scaled', is_ipython_notebook=False) + test_image_correlations('blurs', is_ipython_notebook=False) + test_image_correlations('rotations', is_ipython_notebook=False) + xx, yy = test_rotations() + plt.plot(xx, yy) + plt.plot(xx[:10], yy[:10]) + plt.plot(xx[-10:], yy[-10:]) From 16e1f5af46ccb74d2c8a8da5dfd09efe24f25e51 Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 18:14:35 -0400 Subject: [PATCH 5/7] Removed need for IPython in test program --- test_hashing.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test_hashing.py b/test_hashing.py index 2ddfe4f..ebf6ecc 100755 --- a/test_hashing.py +++ b/test_hashing.py @@ -6,7 +6,6 @@ # Third-party Libraries import matplotlib.pyplot as plt -from IPython.display import Image, display # Custom libphash bindings import pyphash as phash @@ -39,21 +38,15 @@ def hamming_distance(string1, string2): return sum(map(str.__ne__, string1, string2)) -def test_image_correlations(image_distortion_type, is_ipython_notebook=False): +def test_image_correlations(image_distortion_type): base_image_path = TEST_IMAGES.get('base') - base_image = Image(filename=base_image_path) base_phash_digest = phash.image_digest(base_image_path) base_phash_imagehash = phash.imagehash(base_image_path) - if is_ipython_notebook: - display(base_image) print("pHash Digest: {0}".format(base_phash_digest)) print("pHash DCT Hash: {0}".format(base_phash_imagehash)) for distortion_info, image_path in TEST_IMAGES.get(image_distortion_type).items(): - _image = Image(filename=image_path) - if is_ipython_notebook: - display(_image) _image_phash_digest = phash.image_digest(image_path) _image_phash_imagehash = phash.imagehash(image_path) print("pHash Digest: {0}".format(_image_phash_digest)) @@ -87,9 +80,9 @@ def test_rotations(): return list(zip(*correlations)) if __name__ == "__main__": - test_image_correlations('scaled', is_ipython_notebook=False) - test_image_correlations('blurs', is_ipython_notebook=False) - test_image_correlations('rotations', is_ipython_notebook=False) + test_image_correlations('scaled') + test_image_correlations('blurs') + test_image_correlations('rotations') xx, yy = test_rotations() plt.plot(xx, yy) plt.plot(xx[:10], yy[:10]) From 73f3125dfe95fe71d51323c8048691f4a72a3717 Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 18:15:48 -0400 Subject: [PATCH 6/7] Removed more third party library requirements --- test_hashing.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test_hashing.py b/test_hashing.py index ebf6ecc..93a353b 100755 --- a/test_hashing.py +++ b/test_hashing.py @@ -4,9 +4,6 @@ """ phashlib Core C++ interface """ -# Third-party Libraries -import matplotlib.pyplot as plt - # Custom libphash bindings import pyphash as phash print(phash) @@ -83,7 +80,3 @@ def test_rotations(): test_image_correlations('scaled') test_image_correlations('blurs') test_image_correlations('rotations') - xx, yy = test_rotations() - plt.plot(xx, yy) - plt.plot(xx[:10], yy[:10]) - plt.plot(xx[-10:], yy[-10:]) From e16450b46df73e291f9412b5724a35a69da3d759 Mon Sep 17 00:00:00 2001 From: "Michael T. Babcock" Date: Fri, 20 Aug 2021 18:24:24 -0400 Subject: [PATCH 7/7] Add simple image comparison routine --- compare.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 compare.py diff --git a/compare.py b/compare.py new file mode 100644 index 0000000..43ac766 --- /dev/null +++ b/compare.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" basic image compare """ + +import sys + +# Custom libphash bindings +import pyphash as phash + +# Helpers +def hamming_distance(string1, string2): + return sum(map(str.__ne__, string1, string2)) + +def get_image_hashes(imagename): + base_image_path = imagename + base_phash_digest = phash.image_digest(base_image_path) + base_phash_imagehash = phash.imagehash(base_image_path) + return (base_phash_digest, base_phash_imagehash) + +def test_image_correlations(image1, image2): + hashes1 = get_image_hashes(image1) + hashes2 = get_image_hashes(image2) + + print("{} pHash digest: {} and DCT: {}".format(image1, hashes1[0], hashes1[1])) + print("{} pHash digest: {} and DCT: {}".format(image2, hashes2[0], hashes2[1])) + + print("Cross-correlation: %5.2f%%" % ( + 100 * phash.cross_correlation(hashes1[0], hashes2[0]) + )) + +if __name__ == "__main__": + if len(sys.argv) < 3: + print("Please specify two images on the command-line") + sys.exit(1) + image1 = sys.argv[1] + image2 = sys.argv[2] + test_image_correlations(image1, image2) \ No newline at end of file