phash.py
· 1.7 KiB · Python
Raw
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "imagehash"
# ]
# ///
import imagehash
import numpy
from PIL import Image
def from64bit_hash(value: int) -> imagehash.ImageHash:
"""
Converts a 64-bit unsigned long integer into an ImageHash object.
The integer is transformed into a 8x8 binary numpy array, which is the
standard representation for many perceptual hashes.
:param value: The 64-bit integer hash value.
Must be between 0 and 2**64 - 1.
:return: An ImageHash object representing the integer.
"""
if not (0 <= value < 2**64):
raise ValueError("Value must be a 64-bit unsigned integer (between 0 and 2**64 - 1).")
# 1. Convert the integer to its 64-bit binary string representation.
# numpy.binary_repr is perfect for this, as it handles padding.
# Example: 1 -> '000...001'
binary_string = numpy.binary_repr(value, width=64)
# 2. Convert the binary string into a numpy array of booleans.
# '1' becomes True, '0' becomes False.
binary_array = numpy.array([char == '1' for char in binary_string], dtype=bool)
# 3. Reshape the 64-element flat array into a 8x8 matrix.
matrix = binary_array.reshape((8, 8), order='F')
# 4. Create and return the ImageHash instance.
return imagehash.ImageHash(matrix)
def to64bit_hash(value: imagehash.ImageHash) -> int:
buffer = numpy.packbits(value.hash.flatten(order='F'))
return numpy.frombuffer(buffer, dtype='>u8').item()
if __name__ == '__main__':
h = imagehash.phash(Image.open('peppers.png'))
i = to64bit_hash(h)
#round trip test
print(h == from64bit_hash(i))
print(i == to64bit_hash(h) == i)
| 1 | #!/usr/bin/env -S uv run --script |
| 2 | # /// script |
| 3 | # requires-python = ">=3.12" |
| 4 | # dependencies = [ |
| 5 | # "imagehash" |
| 6 | # ] |
| 7 | # /// |
| 8 | |
| 9 | import imagehash |
| 10 | import numpy |
| 11 | from PIL import Image |
| 12 | |
| 13 | def from64bit_hash(value: int) -> imagehash.ImageHash: |
| 14 | """ |
| 15 | Converts a 64-bit unsigned long integer into an ImageHash object. |
| 16 | |
| 17 | The integer is transformed into a 8x8 binary numpy array, which is the |
| 18 | standard representation for many perceptual hashes. |
| 19 | |
| 20 | :param value: The 64-bit integer hash value. |
| 21 | Must be between 0 and 2**64 - 1. |
| 22 | :return: An ImageHash object representing the integer. |
| 23 | """ |
| 24 | if not (0 <= value < 2**64): |
| 25 | raise ValueError("Value must be a 64-bit unsigned integer (between 0 and 2**64 - 1).") |
| 26 | |
| 27 | # 1. Convert the integer to its 64-bit binary string representation. |
| 28 | # numpy.binary_repr is perfect for this, as it handles padding. |
| 29 | # Example: 1 -> '000...001' |
| 30 | binary_string = numpy.binary_repr(value, width=64) |
| 31 | |
| 32 | # 2. Convert the binary string into a numpy array of booleans. |
| 33 | # '1' becomes True, '0' becomes False. |
| 34 | binary_array = numpy.array([char == '1' for char in binary_string], dtype=bool) |
| 35 | |
| 36 | # 3. Reshape the 64-element flat array into a 8x8 matrix. |
| 37 | matrix = binary_array.reshape((8, 8), order='F') |
| 38 | |
| 39 | # 4. Create and return the ImageHash instance. |
| 40 | return imagehash.ImageHash(matrix) |
| 41 | |
| 42 | def to64bit_hash(value: imagehash.ImageHash) -> int: |
| 43 | buffer = numpy.packbits(value.hash.flatten(order='F')) |
| 44 | return numpy.frombuffer(buffer, dtype='>u8').item() |
| 45 | |
| 46 | if __name__ == '__main__': |
| 47 | h = imagehash.phash(Image.open('peppers.png')) |
| 48 | i = to64bit_hash(h) |
| 49 | |
| 50 | #round trip test |
| 51 | print(h == from64bit_hash(i)) |
| 52 | print(i == to64bit_hash(h) == i) |