Last active 1763486415

Revision 5fadc5ccf2d1f7365cf7a352f13080c60a706f61

phash.py Raw
1#!/usr/bin/env -S uv run --script
2# /// script
3# requires-python = ">=3.12"
4# dependencies = [
5# "imagehash"
6# ]
7# ///
8
9import imagehash
10import numpy
11from PIL import Image
12
13def 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
42def to64bit_hash(value: imagehash.ImageHash) -> int:
43 buffer = numpy.packbits(value.hash.flatten(order='F'))
44 return numpy.frombuffer(buffer, dtype='>u8').item()
45
46if __name__ == '__main__':
47 ph = imagehash.phash(Image.open('peppers.png'))
48 x = to64bit_hash(ph)
49
50 #round trip test
51 print(ph == from64bit_hash(x))