pith-image 0.1.1b1__py3-none-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pith_image/__init__.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (c) 2026 pith-hash
|
|
3
|
+
"""pith-image SDK: the 64-bit image perceptual hash through ctypes.
|
|
4
|
+
|
|
5
|
+
The single Rust core (the ``pith-image`` cdylib built by
|
|
6
|
+
``cargo build --release``) is loaded at runtime; this package carries
|
|
7
|
+
no third-party dependency — ``ctypes`` is the standard library.
|
|
8
|
+
|
|
9
|
+
Discovery order (the suite's cdylib convention):
|
|
10
|
+
|
|
11
|
+
1. ``PITH_CDYLIB`` — an explicit cdylib *file* path;
|
|
12
|
+
2. ``PITH_CDYLIB_DIR`` — a *directory* scanned for the cdylib names
|
|
13
|
+
(the CD pipeline points this at ``target/release``);
|
|
14
|
+
3. the package directory itself (the built wheel ships the cdylib as
|
|
15
|
+
package data);
|
|
16
|
+
4. ``<repo root>/target/release`` — the repository working-tree layout,
|
|
17
|
+
so a source checkout runs against a local cargo build with no
|
|
18
|
+
configuration.
|
|
19
|
+
|
|
20
|
+
The vectors in ``reference.json`` at the repository root are the
|
|
21
|
+
hex-exact cross-language source of truth; the conformance tests under
|
|
22
|
+
``tests/`` replay all of them through this binding.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import ctypes
|
|
28
|
+
import os
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"FfiError",
|
|
33
|
+
"LibraryNotFoundError",
|
|
34
|
+
"find_cdylib",
|
|
35
|
+
"image_phash",
|
|
36
|
+
"LAYOUT_GRAY8",
|
|
37
|
+
"LAYOUT_GRAY16",
|
|
38
|
+
"LAYOUT_RGB8",
|
|
39
|
+
"LAYOUT_RGB16",
|
|
40
|
+
"LAYOUT_RGBA8",
|
|
41
|
+
"STATUS_OK",
|
|
42
|
+
"STATUS_INVALID",
|
|
43
|
+
"STATUS_REJECTED",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
#: Status: success.
|
|
47
|
+
STATUS_OK = 0
|
|
48
|
+
#: Status: a caller argument is invalid (null pointer, unknown layout
|
|
49
|
+
#: code, length/geometry mismatch).
|
|
50
|
+
STATUS_INVALID = -1
|
|
51
|
+
#: Status: the core pipeline refused the input.
|
|
52
|
+
STATUS_REJECTED = -2
|
|
53
|
+
|
|
54
|
+
#: Raw dump layouts, in ``RawLayout`` declaration order.
|
|
55
|
+
LAYOUT_GRAY8 = 0
|
|
56
|
+
LAYOUT_GRAY16 = 1
|
|
57
|
+
LAYOUT_RGB8 = 2
|
|
58
|
+
LAYOUT_RGB16 = 3
|
|
59
|
+
LAYOUT_RGBA8 = 4
|
|
60
|
+
|
|
61
|
+
#: Every cdylib file name cargo may drop into the build directory, per
|
|
62
|
+
#: platform (windows / linux / macOS).
|
|
63
|
+
CDYLIB_NAMES = ("pith_image.dll", "libpith_image.so", "libpith_image.dylib")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LibraryNotFoundError(OSError):
|
|
67
|
+
"""No cdylib was found through the discovery chain."""
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class FfiError(Exception):
|
|
71
|
+
"""A non-zero status code came back from the cdylib."""
|
|
72
|
+
|
|
73
|
+
def __init__(self, op: str, status: int) -> None:
|
|
74
|
+
kind = {
|
|
75
|
+
STATUS_INVALID: "invalid argument",
|
|
76
|
+
STATUS_REJECTED: "input rejected",
|
|
77
|
+
}.get(status, "unknown failure")
|
|
78
|
+
super().__init__(f"{op} failed: {kind} (status {status})")
|
|
79
|
+
#: The raw status code the FFI returned.
|
|
80
|
+
self.status = status
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def find_cdylib() -> Path:
|
|
84
|
+
"""Locates the cdylib through the suite's discovery chain."""
|
|
85
|
+
explicit = os.environ.get("PITH_CDYLIB")
|
|
86
|
+
if explicit:
|
|
87
|
+
p = Path(explicit)
|
|
88
|
+
if p.is_file():
|
|
89
|
+
return p
|
|
90
|
+
env_dir = os.environ.get("PITH_CDYLIB_DIR")
|
|
91
|
+
candidates: list[Path] = []
|
|
92
|
+
if env_dir:
|
|
93
|
+
env_dir_path = Path(env_dir)
|
|
94
|
+
candidates.append(env_dir_path)
|
|
95
|
+
if not env_dir_path.is_absolute():
|
|
96
|
+
# CD and local runs invoke tools from the repository root or
|
|
97
|
+
# from sdk/<lang>; resolve the env value against both.
|
|
98
|
+
candidates.append(Path.cwd() / env_dir_path)
|
|
99
|
+
candidates.append(Path(__file__).resolve().parents[3] / env_dir_path)
|
|
100
|
+
candidates.append(Path(__file__).resolve().parent) # packaged wheel
|
|
101
|
+
candidates.append(Path(__file__).resolve().parents[3] / "target" / "release")
|
|
102
|
+
for directory in candidates:
|
|
103
|
+
for name in CDYLIB_NAMES:
|
|
104
|
+
p = directory / name
|
|
105
|
+
if p.is_file():
|
|
106
|
+
return p
|
|
107
|
+
raise LibraryNotFoundError(
|
|
108
|
+
"no pith-image cdylib found (searched PITH_CDYLIB, PITH_CDYLIB_DIR, "
|
|
109
|
+
"the package directory and <repo>/target/release); "
|
|
110
|
+
"run `cargo build --release` first"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
_lib: ctypes.CDLL | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _load() -> ctypes.CDLL:
|
|
118
|
+
global _lib
|
|
119
|
+
if _lib is None:
|
|
120
|
+
lib = ctypes.CDLL(str(find_cdylib()))
|
|
121
|
+
lib.pith_image_phash.argtypes = [
|
|
122
|
+
ctypes.c_void_p, # data
|
|
123
|
+
ctypes.c_size_t, # len
|
|
124
|
+
ctypes.c_uint32, # width
|
|
125
|
+
ctypes.c_uint32, # height
|
|
126
|
+
ctypes.c_uint32, # layout
|
|
127
|
+
ctypes.POINTER(ctypes.c_uint64), # out hash
|
|
128
|
+
]
|
|
129
|
+
lib.pith_image_phash.restype = ctypes.c_int32
|
|
130
|
+
_lib = lib
|
|
131
|
+
return _lib
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def image_phash(dump: bytes, width: int, height: int, layout: int) -> int:
|
|
135
|
+
"""Computes the 64-bit perceptual hash of a raw pixel dump.
|
|
136
|
+
|
|
137
|
+
``dump`` is a flat, row-major, channel-interleaved buffer of
|
|
138
|
+
``width * height * channels`` samples (little-endian for the 16-bit
|
|
139
|
+
layouts) — the format the fixtures under ``tests/fixtures/phash/``
|
|
140
|
+
commit. Returns the hash as an ``int``; render it with
|
|
141
|
+
``format(hash, "016x")`` to compare against ``reference.json``.
|
|
142
|
+
"""
|
|
143
|
+
if width <= 0 or height <= 0:
|
|
144
|
+
raise ValueError("width and height must be positive")
|
|
145
|
+
if layout not in (LAYOUT_GRAY8, LAYOUT_GRAY16, LAYOUT_RGB8, LAYOUT_RGB16, LAYOUT_RGBA8):
|
|
146
|
+
raise ValueError(f"unknown layout code {layout}")
|
|
147
|
+
out = ctypes.c_uint64()
|
|
148
|
+
status = _load().pith_image_phash(dump, len(dump), width, height, layout, ctypes.byref(out))
|
|
149
|
+
if status != STATUS_OK:
|
|
150
|
+
raise FfiError("pith_image_phash", status)
|
|
151
|
+
return out.value
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pith-image
|
|
3
|
+
Version: 0.1.1b1
|
|
4
|
+
Summary: pith image lane SDK: 64-bit image perceptual hash via the pith-image Rust cdylib (ctypes, zero-dep)
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Repository, https://github.com/pith-hash/pith-image
|
|
7
|
+
Project-URL: Homepage, https://pith-image.n24q02m.com
|
|
8
|
+
Keywords: phash,image,hashing,perceptual-hash,pith
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Rust
|
|
11
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
12
|
+
Classifier: Topic :: Security :: Cryptography
|
|
13
|
+
Requires-Python: >=3.9
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
pith_image/__init__.py,sha256=4xE09x3oy4vPRbovEoPJkiSnUlReQGo2GHnJ4jlS-H8,5184
|
|
2
|
+
pith_image/pith_image.dll,sha256=dEHhqJ_BH-9fjcQrYAXKXOohva73Iki0YHHxaUW2nlI,158208
|
|
3
|
+
pith_image-0.1.1b1.dist-info/METADATA,sha256=7ufUcp-akPt5EHMUK8uL7IhDDqcFVpOqzKx_lI9ADXA,574
|
|
4
|
+
pith_image-0.1.1b1.dist-info/WHEEL,sha256=Zx98gwb_dQKckJK3HEOVKnxxD52EfU_PXDG_usMJ2ng,98
|
|
5
|
+
pith_image-0.1.1b1.dist-info/top_level.txt,sha256=KD_tHNh7_TBtQzOVdU3sV0EDLt6qH1yrDNXFzrmD3Is,11
|
|
6
|
+
pith_image-0.1.1b1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pith_image
|