Download code/hamer/native/__init__.py from changh95/hamer-p150: direct link, hf CLI and curl.
- Browser
- Download file 11.1 kB
-
https://huggingface.co/changh95/hamer-p150/resolve/main/code/hamer/native/__init__.py
- Command line
-
hf download hf://changh95/hamer-p150/code/hamer/native/__init__.py
-
curl -L -o __init__.py https://huggingface.co/changh95/hamer-p150/resolve/main/code/hamer/native/__init__.py
11.1 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Round 14 (P1): native host preprocess -- ``resize.c`` (crop + grey pad + PIL BILINEAR box resize + mirror in one call, | |
| bit-identical to the PIL path), built lazily with the host C compiler into a per-user cache and loaded with ctypes. | |
| Nothing here is required: if there is no compiler, the build fails, or the load-time self-check (random crops vs the PIL | |
| path, bit for bit) fails, :func:`crop_resize` is ``None`` and preprocess keeps the PIL path (same bytes, slower). | |
| ``HAMER_PRE_NATIVE=0`` disables it. | |
| """ | |
| from __future__ import annotations | |
| import ctypes | |
| import hashlib | |
| import os | |
| import subprocess | |
| import tempfile | |
| import threading | |
| from pathlib import Path | |
| from typing import Optional | |
| import numpy as np | |
| _SRCS = [Path(__file__).with_name("resize.c")] | |
| # no -ffast-math, no FP contraction: the filter coefficients must be computed with Pillow's exact double arithmetic | |
| _CFLAGS = ["-O3", "-march=native", "-ffp-contract=off", "-fno-fast-math", "-shared", "-fPIC"] | |
| _LOCK = threading.Lock() | |
| _FN = None | |
| _FN_PS = None # round 15: hamer_crop_resize_ps (any source pixel stride) when the zero-copy PIL path self-checked | |
| _IM2COL = None | |
| _TRIED = False | |
| STATUS = "not loaded" | |
| def _cache_dir() -> Path: | |
| d = os.environ.get("HAMER_NATIVE_DIR") | |
| if d: | |
| return Path(d) | |
| base = os.environ.get("XDG_CACHE_HOME") or str(Path.home() / ".cache") | |
| return Path(base) / "hamer-native" | |
| def _build() -> Optional[Path]: | |
| src = b"".join(p.read_bytes() for p in _SRCS) | |
| cc = os.environ.get("CC", "cc") | |
| tag = hashlib.sha1(src + " ".join(_CFLAGS).encode() + os.uname().machine.encode()).hexdigest()[:16] | |
| for d in (_cache_dir(), Path(tempfile.gettempdir()) / f"hamer-native-{os.getuid()}"): | |
| so = d / f"hamer_resize_{tag}.so" | |
| if so.exists(): | |
| return so | |
| try: | |
| d.mkdir(parents=True, exist_ok=True) | |
| tmp = d / f".{so.name}.{os.getpid()}" | |
| subprocess.run([cc, *_CFLAGS, "-o", str(tmp), *map(str, _SRCS)], check=True, capture_output=True, timeout=120) | |
| os.replace(tmp, so) | |
| return so | |
| except Exception: # noqa: BLE001 - no compiler / read-only dir: try the next dir, then give up | |
| continue | |
| return None | |
| def _self_check(fn) -> bool: | |
| """The native path must reproduce the PIL path bit for bit (random frames / boxes / both hands, edge-crossing boxes).""" | |
| from PIL import Image | |
| from hamer import preprocess as pp | |
| rng = np.random.default_rng(1234) | |
| for i in range(24): | |
| H, W = (int(v) for v in rng.integers(48, 400, 2)) | |
| a = rng.integers(0, 256, (H, W, 3), dtype=np.uint8) | |
| img = Image.fromarray(a) | |
| w, h = rng.uniform(4, W), rng.uniform(4, H) | |
| x1, y1 = rng.uniform(-w / 2, W - w / 2), rng.uniform(-h / 2, H - h / 2) | |
| bbox, rf, right = (x1, y1, x1 + w, y1 + h), float(rng.choice([2.0, 1.0, 3.3])), bool(i % 2) | |
| if pp.hamer_bbox_size(bbox, rf) < 2: | |
| continue | |
| crop, meta = pp._square_crop(img, bbox, rf) # noqa: SLF001 | |
| ref = pp.resize_crop_hw(crop, right, bands=1) | |
| got = _call(fn, a, bbox, rf, right) | |
| got_chw = _call(fn, a, bbox, rf, right, chw=True) | |
| if got is None or not np.array_equal(ref, got[0]) or got[1] != meta: | |
| return False | |
| if got_chw is None or not np.array_equal(ref.transpose(2, 0, 1), got_chw[0]) or got_chw[1] != meta: | |
| return False | |
| return True | |
| def _call(fn, a: np.ndarray, bbox, rescale_factor: float, is_right: bool, chw: bool = False): | |
| """(u8 HWC [256, 192, 3] or planar CHW [3, 256, 192] when chw, (cx, cy, bbox_size)) or None when the PIL path must handle it (degenerate boxes).""" | |
| if a.ndim != 3 or a.shape[2] != 3 or a.dtype != np.uint8 or a.strides[1:] != (3, 1): | |
| return None | |
| H, W = a.shape[:2] | |
| return _call_ptr(fn, a.ctypes.data, H, W, a.strides[0], 3, bbox, rescale_factor, is_right, chw) | |
| def _call_ptr(fn, ptr: int, H: int, W: int, row_stride: int, ps: int, bbox, rescale_factor: float, is_right: bool, | |
| chw: bool = False): | |
| """_call on a raw u8 image pointer with ``ps`` bytes per pixel (3: packed RGB; 4: Pillow's RGBX rows).""" | |
| from hamer.preprocess import hamer_bbox_size | |
| x1, y1, x2, y2 = bbox | |
| cx, cy = (x1 + x2) / 2.0, (y1 + y2) / 2.0 | |
| bs = hamer_bbox_size(bbox, rescale_factor) | |
| half = bs / 2.0 | |
| S = int(round(bs)) | |
| # exactly _square_crop's integer boxes | |
| l, t = max(0, int(round(cx - half))), max(0, int(round(cy - half))) | |
| r, b = min(W, int(round(cx + half))), min(H, int(round(cy + half))) | |
| px, py = max(0, int(round(-(cx - half)))), max(0, int(round(-(cy - half)))) | |
| if S < 2 or r <= l or b <= t: | |
| return None | |
| out = np.empty((3, 256, 192) if chw else (256, 192, 3), np.uint8) | |
| args = (S, l, t, r - l, b - t, px, py, 0 if is_right else 1, 1 if chw else 0, out.ctypes.data) | |
| rc = fn(ptr, H, W, row_stride, *args) if ps == 3 and fn is not _FN_PS else fn(ptr, H, W, row_stride, ps, *args) | |
| if rc != 0: | |
| return None | |
| return out, (cx, cy, bs) | |
| def load(): | |
| """The ctypes function (or None). Built / checked once per process.""" | |
| global _FN, _FN_PS, _IM2COL, _TRIED, STATUS | |
| with _LOCK: | |
| if _TRIED: | |
| return _FN | |
| _TRIED = True | |
| pre_off = os.environ.get("HAMER_PRE_NATIVE", "1").strip().lower() in ("0", "false", "no", "off") | |
| try: | |
| so = _build() | |
| if so is None: | |
| STATUS = "no compiler / build failed: PIL path" | |
| return None | |
| lib = ctypes.CDLL(str(so)) | |
| fn = lib.hamer_crop_resize | |
| fn.restype = ctypes.c_int | |
| fn.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_long] + [ctypes.c_int] * 9 + [ctypes.c_void_p] | |
| ic = lib.hamer_im2col_tile_bf16 | |
| ic.restype = ctypes.c_int | |
| ic.argtypes = [ctypes.c_void_p, ctypes.c_long, ctypes.c_long, ctypes.c_long, ctypes.c_int, ctypes.c_int, | |
| ctypes.c_int, ctypes.c_void_p] | |
| _IM2COL = ic # checked by its caller (TtHamer._setup_fast_io) against the torch path | |
| if pre_off: | |
| STATUS = "off (HAMER_PRE_NATIVE=0)" | |
| return None | |
| if not _self_check(fn): | |
| STATUS = "self-check FAILED (not bit-identical to PIL): PIL path" | |
| return None | |
| _FN = fn | |
| STATUS = f"on ({so.name}, self-check bit-identical)" | |
| if os.environ.get("HAMER_PRE_ZEROCOPY", "1").strip().lower() not in ("0", "false", "no", "off"): | |
| fps = lib.hamer_crop_resize_ps | |
| fps.restype = ctypes.c_int | |
| fps.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_int, ctypes.c_long, ctypes.c_int] \ | |
| + [ctypes.c_int] * 9 + [ctypes.c_void_p] | |
| _FN_PS = fps | |
| if _self_check_zc(): | |
| STATUS += "; zero-copy PIL buffer on" | |
| else: | |
| _FN_PS = None | |
| STATUS += "; zero-copy PIL buffer off (self-check failed)" | |
| except Exception as e: # noqa: BLE001 | |
| STATUS = f"load failed ({e!r}): PIL path" | |
| _FN = None | |
| return _FN | |
| def im2col_tile_fn(): | |
| """Round 14: ``hamer_im2col_tile_bf16`` (u8 crop -> bf16 TILE im2col of the centred crop) or None.""" | |
| load() | |
| return _IM2COL | |
| def crop_resize(a: np.ndarray, bbox, rescale_factor: float, is_right: bool, chw: bool = False): | |
| fn = load() | |
| if fn is None: | |
| return None | |
| return _call(fn, a, bbox, rescale_factor, is_right, chw) | |
| # --------------------------------------------------------------------------------------------------------------------- | |
| # round 15: the decoded frame read IN PLACE (no np.asarray copy, ~0.08 ms per 512x334 frame). Pillow stores an RGB image | |
| # as 4-byte RGBX pixels; its Arrow C export (Pillow >= 11.2, a single-block image) hands out the pixel buffer pointer. | |
| class _ArrowArray(ctypes.Structure): | |
| pass | |
| _ArrowArray._fields_ = [("length", ctypes.c_int64), ("null_count", ctypes.c_int64), ("offset", ctypes.c_int64), | |
| ("n_buffers", ctypes.c_int64), ("n_children", ctypes.c_int64), | |
| ("buffers", ctypes.POINTER(ctypes.c_void_p)), | |
| ("children", ctypes.POINTER(ctypes.POINTER(_ArrowArray))), ("dictionary", ctypes.c_void_p), | |
| ("release", ctypes.c_void_p), ("private_data", ctypes.c_void_p)] | |
| _CAP_GET = ctypes.pythonapi.PyCapsule_GetPointer | |
| _CAP_GET.restype = ctypes.c_void_p | |
| _CAP_GET.argtypes = [ctypes.py_object, ctypes.c_char_p] | |
| def _rgbx_view(img): | |
| """(capsules to keep alive, pointer, H, W) of an RGB PIL image's RGBX pixel buffer, or None.""" | |
| if img.mode != "RGB" or not hasattr(img, "__arrow_c_array__"): | |
| return None | |
| try: | |
| img.load() | |
| sch, arr = img.__arrow_c_array__() | |
| a = ctypes.cast(_CAP_GET(arr, b"arrow_array"), ctypes.POINTER(_ArrowArray)).contents | |
| W, H = img.size | |
| if a.n_children != 1 or a.length != H * W or a.offset != 0: | |
| return None | |
| c = a.children[0].contents | |
| if c.n_buffers != 2 or c.length != H * W * 4 or c.offset != 0 or not c.buffers[1]: | |
| return None | |
| return (sch, arr), c.buffers[1], H, W | |
| except Exception: # noqa: BLE001 - older Pillow / multi-block image: the copying path | |
| return None | |
| def _self_check_zc() -> bool: | |
| """The zero-copy path must give the bytes of the np.asarray path (random frames / boxes / both hands).""" | |
| from PIL import Image | |
| rng = np.random.default_rng(4321) | |
| for i in range(16): | |
| H, W = (int(v) for v in rng.integers(48, 400, 2)) | |
| a = rng.integers(0, 256, (H, W, 3), dtype=np.uint8) | |
| img = Image.fromarray(a) | |
| w, h = rng.uniform(4, W), rng.uniform(4, H) | |
| x1, y1 = rng.uniform(-w / 2, W - w / 2), rng.uniform(-h / 2, H - h / 2) | |
| bbox, rf, right = (x1, y1, x1 + w, y1 + h), float(rng.choice([2.0, 1.0, 3.3])), bool(i % 2) | |
| ref = _call(_FN, a, bbox, rf, right, chw=True) | |
| v = _rgbx_view(img) | |
| if v is None: | |
| return False | |
| got = _call_ptr(_FN_PS, v[1], v[2], v[3], v[3] * 4, 4, bbox, rf, right, chw=True) | |
| if (ref is None) != (got is None) or (ref is not None and (not np.array_equal(ref[0], got[0]) or ref[1] != got[1])): | |
| return False | |
| return True | |
| def crop_resize_image(img, bbox, rescale_factor: float, is_right: bool, chw: bool = False): | |
| """crop_resize on a decoded PIL image, reading its pixel buffer in place when possible (round 15).""" | |
| fn = load() | |
| if fn is None: | |
| return None | |
| if _FN_PS is not None: | |
| v = _rgbx_view(img) | |
| if v is not None: | |
| keep, ptr, H, W = v | |
| res = _call_ptr(_FN_PS, ptr, H, W, W * 4, 4, bbox, rescale_factor, is_right, chw) | |
| del keep | |
| return res | |
| return _call(fn, np.asarray(img), bbox, rescale_factor, is_right, chw) | |