added ruff code reivew
This commit is contained in:
@@ -5,9 +5,7 @@ Historical import path ``from orisys.Stitcher import Stitcher`` remains supporte
|
||||
|
||||
from .stitch import Stitcher
|
||||
from .stitch.backends.numpy_backend import blend_kernel_numpy as _blend_kernel_numpy
|
||||
from .stitch.config import BLEND_MODES as _BLEND_MODES
|
||||
from .stitch.config import DEFAULT_BLEND_MODE as _DEFAULT_BLEND_MODE
|
||||
from .stitch.config import numba_blend_available, resolve_blend_mode as _resolve_blend_mode
|
||||
from .stitch.config import numba_blend_available
|
||||
|
||||
# Backward-compatible aliases used by older call sites / tests.
|
||||
_blend_kernel = _blend_kernel_numpy
|
||||
|
||||
@@ -8,16 +8,11 @@ including image stitching, optical flow tracking, and deformation decomposition.
|
||||
__version__ = "0.5.3"
|
||||
__author__ = "Orisys"
|
||||
|
||||
from .sensor import Sensor
|
||||
from . import util
|
||||
from . import configs
|
||||
from . import calibration
|
||||
from . import finger
|
||||
from . import signal_processing
|
||||
from . import capture
|
||||
from .pipeline import FrameSnapshot, PipelineStats, StreamPipeline
|
||||
from . import calibration, capture, configs, finger, signal_processing, util
|
||||
from .export import StreamRecorder, mosaic_videos
|
||||
from .frame_processors import FFCFrameProcessor
|
||||
from .pipeline import FrameSnapshot, PipelineStats, StreamPipeline
|
||||
from .sensor import Sensor
|
||||
|
||||
CompoundEyeSensor = Sensor
|
||||
CameraSensor = Sensor
|
||||
|
||||
@@ -63,8 +63,8 @@ except ImportError:
|
||||
try:
|
||||
import pyopencl as cl
|
||||
import pyopencl.array as cla
|
||||
from pyvkfft.fft import rfftn as cl_rfftn
|
||||
from pyvkfft.fft import irfftn as cl_irfftn
|
||||
from pyvkfft.fft import rfftn as cl_rfftn
|
||||
_opencl_cl = cl
|
||||
_opencl_cla = cla
|
||||
_opencl_rfftn = cl_rfftn
|
||||
|
||||
@@ -8,11 +8,12 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import warnings
|
||||
from collections.abc import Iterable, Mapping
|
||||
from copy import deepcopy
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable, Mapping
|
||||
from typing import Any
|
||||
|
||||
CURRENT_SCHEMA = 2
|
||||
SUPPORTED_SCHEMAS = (1, 2)
|
||||
|
||||
@@ -25,21 +25,21 @@ from .source import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"MAX_SIMULTANEOUS_CAPTURES",
|
||||
"DEFAULT_MAX_CAMERA_INDEX",
|
||||
"DEFAULT_LAPTOP_WEBCAM_PATTERNS",
|
||||
"DEFAULT_MAX_CAMERA_INDEX",
|
||||
"MAX_SIMULTANEOUS_CAPTURES",
|
||||
"CameraFilter",
|
||||
"CameraInfo",
|
||||
"CaptureConfig",
|
||||
"CaptureHealth",
|
||||
"CaptureStatus",
|
||||
"ReconnectPolicy",
|
||||
"CaptureSource",
|
||||
"CaptureStatus",
|
||||
"CaptureWorker",
|
||||
"CameraInfo",
|
||||
"CameraFilter",
|
||||
"list_cameras",
|
||||
"pick_default_camera",
|
||||
"normalize_source",
|
||||
"ReconnectPolicy",
|
||||
"is_live_camera",
|
||||
"open_opencv_capture",
|
||||
"list_cameras",
|
||||
"normalize_source",
|
||||
"open_capture",
|
||||
"open_opencv_capture",
|
||||
"pick_default_camera",
|
||||
]
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any, Optional, Sequence, Tuple, Union
|
||||
from typing import Any, Union
|
||||
|
||||
MAX_SIMULTANEOUS_CAPTURES = 10
|
||||
|
||||
@@ -44,11 +45,11 @@ class ReconnectPolicy:
|
||||
"""
|
||||
|
||||
enabled: bool = False
|
||||
max_attempts: Optional[int] = None
|
||||
max_attempts: int | None = None
|
||||
initial_backoff_s: float = 0.25
|
||||
max_backoff_s: float = 5.0
|
||||
fail_threshold: int = 5
|
||||
stale_timeout_s: Optional[float] = 2.0
|
||||
stale_timeout_s: float | None = 2.0
|
||||
|
||||
def next_backoff(self, attempt: int) -> float:
|
||||
"""Exponential backoff capped at ``max_backoff_s`` (attempt is 0-based)."""
|
||||
@@ -62,19 +63,19 @@ class CaptureConfig:
|
||||
|
||||
source: SourceType
|
||||
backend: str = "auto"
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
fps: Optional[float] = None
|
||||
fourcc: Optional[str] = "MJPG"
|
||||
width: int | None = None
|
||||
height: int | None = None
|
||||
fps: float | None = None
|
||||
fourcc: str | None = "MJPG"
|
||||
background: bool = True
|
||||
reconnect: ReconnectPolicy = field(default_factory=ReconnectPolicy)
|
||||
verbose: bool = False
|
||||
# Optional discovery filters (used by list/open helpers, not open itself).
|
||||
name_include: Optional[Sequence[str]] = None
|
||||
name_exclude: Optional[Sequence[str]] = None
|
||||
vid_pid_allowlist: Optional[Sequence[Tuple[int, int]]] = None
|
||||
min_width: Optional[int] = None
|
||||
min_height: Optional[int] = None
|
||||
name_include: Sequence[str] | None = None
|
||||
name_exclude: Sequence[str] | None = None
|
||||
vid_pid_allowlist: Sequence[tuple[int, int]] | None = None
|
||||
min_width: int | None = None
|
||||
min_height: int | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not isinstance(self.reconnect, ReconnectPolicy):
|
||||
@@ -86,10 +87,10 @@ class CaptureConfig:
|
||||
source: SourceType,
|
||||
camera: dict[str, Any],
|
||||
*,
|
||||
reconnect: Optional[ReconnectPolicy] = None,
|
||||
reconnect: ReconnectPolicy | None = None,
|
||||
verbose: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> "CaptureConfig":
|
||||
) -> CaptureConfig:
|
||||
"""Build config from a sensor JSON ``camera`` section."""
|
||||
# Accept deprecated queue/worker kwargs from older callers without error.
|
||||
kwargs.pop("mode", None)
|
||||
@@ -117,9 +118,9 @@ class CaptureStatus:
|
||||
"""Snapshot of capture health for diagnostics / multi-camera UIs."""
|
||||
|
||||
health: CaptureHealth = CaptureHealth.CLOSED
|
||||
source: Optional[SourceType] = None
|
||||
last_error: Optional[str] = None
|
||||
last_frame_time: Optional[float] = None
|
||||
source: SourceType | None = None
|
||||
last_error: str | None = None
|
||||
last_frame_time: float | None = None
|
||||
last_seq: int = 0
|
||||
dropped_frames: int = 0
|
||||
reconnect_attempts: int = 0
|
||||
|
||||
@@ -3,21 +3,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections.abc import Iterable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Iterable, Optional, Sequence, Tuple
|
||||
|
||||
import cv2
|
||||
|
||||
from .config import MAX_SIMULTANEOUS_CAPTURES
|
||||
from .source import _backend_candidates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_MAX_CAMERA_INDEX = MAX_SIMULTANEOUS_CAPTURES
|
||||
|
||||
# Common built-in laptop webcam name fragments (case-insensitive).
|
||||
DEFAULT_LAPTOP_WEBCAM_PATTERNS: Tuple[str, ...] = (
|
||||
DEFAULT_LAPTOP_WEBCAM_PATTERNS: tuple[str, ...] = (
|
||||
r"integrated",
|
||||
r"built[- ]?in",
|
||||
r"laptop",
|
||||
@@ -49,8 +52,8 @@ class CameraInfo:
|
||||
height: int
|
||||
name: str = ""
|
||||
path: str = ""
|
||||
vendor_id: Optional[int] = None
|
||||
product_id: Optional[int] = None
|
||||
vendor_id: int | None = None
|
||||
product_id: int | None = None
|
||||
bus: str = ""
|
||||
|
||||
@property
|
||||
@@ -64,7 +67,7 @@ class CameraInfo:
|
||||
return f"{self.index} ({self.width}x{self.height}) [{self.backend_name}]"
|
||||
|
||||
@property
|
||||
def vid_pid(self) -> Optional[Tuple[int, int]]:
|
||||
def vid_pid(self) -> tuple[int, int] | None:
|
||||
if self.vendor_id is None or self.product_id is None:
|
||||
return None
|
||||
return (int(self.vendor_id), int(self.product_id))
|
||||
@@ -74,13 +77,13 @@ class CameraInfo:
|
||||
class CameraFilter:
|
||||
"""Optional filters for ``list_cameras`` / ``pick_default_camera``."""
|
||||
|
||||
name_include: Optional[Sequence[str]] = None
|
||||
name_exclude: Optional[Sequence[str]] = None
|
||||
name_include: Sequence[str] | None = None
|
||||
name_exclude: Sequence[str] | None = None
|
||||
exclude_laptop_webcams: bool = False
|
||||
laptop_patterns: Sequence[str] = field(default_factory=lambda: DEFAULT_LAPTOP_WEBCAM_PATTERNS)
|
||||
vid_pid_allowlist: Optional[Sequence[Tuple[int, int]]] = None
|
||||
min_width: Optional[int] = None
|
||||
min_height: Optional[int] = None
|
||||
vid_pid_allowlist: Sequence[tuple[int, int]] | None = None
|
||||
min_width: int | None = None
|
||||
min_height: int | None = None
|
||||
prefer_named: bool = True
|
||||
|
||||
def matches(self, cam: CameraInfo) -> bool:
|
||||
@@ -143,8 +146,15 @@ def _suppress_opencv_noise():
|
||||
if set_level is not None and prev_level is not None:
|
||||
try:
|
||||
set_level(prev_level)
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Restoring OpenCV's log level is best-effort, but a silent failure
|
||||
# here leaves probing noise suppressed for the rest of the process,
|
||||
# which hides exactly the diagnostics a failed enumeration needs.
|
||||
logger.warning(
|
||||
"capture discovery: could not restore OpenCV log level %r: %s",
|
||||
prev_level,
|
||||
exc,
|
||||
)
|
||||
|
||||
|
||||
def _linux_device_metadata(index: int) -> dict:
|
||||
@@ -152,7 +162,7 @@ def _linux_device_metadata(index: int) -> dict:
|
||||
meta = {"name": "", "path": f"/dev/video{index}", "vendor_id": None, "product_id": None, "bus": ""}
|
||||
name_path = f"/sys/class/video4linux/video{index}/name"
|
||||
try:
|
||||
with open(name_path, "r", encoding="utf-8", errors="ignore") as fh:
|
||||
with open(name_path, encoding="utf-8", errors="ignore") as fh:
|
||||
meta["name"] = fh.read().strip()
|
||||
except OSError:
|
||||
pass
|
||||
@@ -165,9 +175,9 @@ def _linux_device_metadata(index: int) -> dict:
|
||||
vend = os.path.join(cur, "idVendor")
|
||||
prod = os.path.join(cur, "idProduct")
|
||||
if os.path.isfile(vend) and os.path.isfile(prod):
|
||||
with open(vend, "r", encoding="utf-8") as fh:
|
||||
with open(vend, encoding="utf-8") as fh:
|
||||
meta["vendor_id"] = int(fh.read().strip(), 16)
|
||||
with open(prod, "r", encoding="utf-8") as fh:
|
||||
with open(prod, encoding="utf-8") as fh:
|
||||
meta["product_id"] = int(fh.read().strip(), 16)
|
||||
meta["bus"] = "usb"
|
||||
break
|
||||
@@ -229,7 +239,7 @@ def _windows_device_names() -> dict[int, dict]:
|
||||
return {}
|
||||
|
||||
|
||||
def _device_metadata(index: int, windows_names: Optional[dict[int, dict]] = None) -> dict:
|
||||
def _device_metadata(index: int, windows_names: dict[int, dict] | None = None) -> dict:
|
||||
if sys.platform.startswith("linux"):
|
||||
return _linux_device_metadata(index)
|
||||
if sys.platform == "win32" and windows_names is not None:
|
||||
@@ -238,7 +248,7 @@ def _device_metadata(index: int, windows_names: Optional[dict[int, dict]] = None
|
||||
return {"name": "", "path": "", "vendor_id": None, "product_id": None, "bus": ""}
|
||||
|
||||
|
||||
def _try_open(index: int, backend_name: str, backend_api: int, meta: dict) -> Optional[CameraInfo]:
|
||||
def _try_open(index: int, backend_name: str, backend_api: int, meta: dict) -> CameraInfo | None:
|
||||
cap = cv2.VideoCapture(index, backend_api)
|
||||
if not cap.isOpened():
|
||||
cap.release()
|
||||
@@ -272,13 +282,13 @@ def _try_open(index: int, backend_name: str, backend_api: int, meta: dict) -> Op
|
||||
def list_cameras(
|
||||
max_index: int = DEFAULT_MAX_CAMERA_INDEX,
|
||||
*,
|
||||
camera_filter: Optional[CameraFilter] = None,
|
||||
camera_filter: CameraFilter | None = None,
|
||||
exclude_laptop_webcams: bool = False,
|
||||
name_include: Optional[Sequence[str]] = None,
|
||||
name_exclude: Optional[Sequence[str]] = None,
|
||||
vid_pid_allowlist: Optional[Sequence[Tuple[int, int]]] = None,
|
||||
min_width: Optional[int] = None,
|
||||
min_height: Optional[int] = None,
|
||||
name_include: Sequence[str] | None = None,
|
||||
name_exclude: Sequence[str] | None = None,
|
||||
vid_pid_allowlist: Sequence[tuple[int, int]] | None = None,
|
||||
min_width: int | None = None,
|
||||
min_height: int | None = None,
|
||||
backend: str = "auto",
|
||||
) -> list[CameraInfo]:
|
||||
"""Probe indices ``0 .. max_index`` and return working cameras.
|
||||
@@ -302,10 +312,22 @@ def list_cameras(
|
||||
windows_names = _windows_device_names() if sys.platform == "win32" else None
|
||||
|
||||
with _suppress_opencv_noise():
|
||||
for index in range(0, max_index + 1):
|
||||
for index in range(max_index + 1):
|
||||
meta = _device_metadata(index, windows_names)
|
||||
for backend_name, backend_api in backends:
|
||||
info = _try_open(index, backend_name, backend_api, meta)
|
||||
try:
|
||||
info = _try_open(index, backend_name, backend_api, meta)
|
||||
except Exception as exc:
|
||||
# One bad backend/device must not abort the whole probe: an
|
||||
# escaping error here made every *other* camera invisible, not
|
||||
# just this index. Skip the candidate and keep enumerating.
|
||||
logger.warning(
|
||||
"capture discovery: probing index %d via %s failed: %s",
|
||||
index,
|
||||
backend_name,
|
||||
exc,
|
||||
)
|
||||
continue
|
||||
if info is None:
|
||||
continue
|
||||
if index in seen_indices:
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Optional, Protocol, runtime_checkable
|
||||
from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -19,6 +20,8 @@ from .config import (
|
||||
SourceType,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class VideoCaptureLike(Protocol):
|
||||
@@ -83,7 +86,7 @@ def _backend_candidates(preferred: str = "auto") -> list[tuple[str, int]]:
|
||||
return candidates
|
||||
|
||||
|
||||
def _fourcc_code(fourcc: Optional[str]) -> Optional[int]:
|
||||
def _fourcc_code(fourcc: str | None) -> int | None:
|
||||
if not fourcc:
|
||||
return None
|
||||
text = str(fourcc).strip()
|
||||
@@ -111,10 +114,10 @@ def _read_stream_meta(cap: VideoCaptureLike) -> tuple[int, int, float, str]:
|
||||
def _apply_stream_props(
|
||||
cap: VideoCaptureLike,
|
||||
*,
|
||||
width: Optional[int],
|
||||
height: Optional[int],
|
||||
fps: Optional[float],
|
||||
fourcc: Optional[str],
|
||||
width: int | None,
|
||||
height: int | None,
|
||||
fps: float | None,
|
||||
fourcc: str | None,
|
||||
) -> None:
|
||||
"""Apply live-camera stream properties (width/height before fourcc on DSHOW)."""
|
||||
code = _fourcc_code(fourcc)
|
||||
@@ -143,10 +146,10 @@ def open_opencv_capture(
|
||||
source: SourceType,
|
||||
*,
|
||||
backend: str = "auto",
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
fps: Optional[float] = None,
|
||||
fourcc: Optional[str] = "MJPG",
|
||||
width: int | None = None,
|
||||
height: int | None = None,
|
||||
fps: float | None = None,
|
||||
fourcc: str | None = "MJPG",
|
||||
apply_stream_props: bool = True,
|
||||
) -> tuple[VideoCaptureLike, str]:
|
||||
"""Open a ``cv2.VideoCapture`` trying preferred backends in order.
|
||||
@@ -212,16 +215,16 @@ class CaptureSource:
|
||||
):
|
||||
self.config = config
|
||||
self._factory = factory or self._default_factory
|
||||
self._cap: Optional[VideoCaptureLike] = None
|
||||
self._cap: VideoCaptureLike | None = None
|
||||
self._backend_name = ""
|
||||
self._status = CaptureStatus(source=normalize_source(config.source))
|
||||
self._reconnect_attempt = 0
|
||||
self._lock = threading.Lock()
|
||||
self._frame_lock = threading.Lock()
|
||||
self._frame_cond = threading.Condition(self._frame_lock)
|
||||
self._latest_frame: Optional[np.ndarray] = None
|
||||
self._latest_frame: np.ndarray | None = None
|
||||
self._latest_seq = 0
|
||||
self._thread: Optional[threading.Thread] = None
|
||||
self._thread: threading.Thread | None = None
|
||||
self._background_running = False
|
||||
|
||||
@staticmethod
|
||||
@@ -238,7 +241,7 @@ class CaptureSource:
|
||||
|
||||
# Compatibility with older CaptureWorker API.
|
||||
@property
|
||||
def source(self) -> "CaptureSource":
|
||||
def source(self) -> CaptureSource:
|
||||
return self
|
||||
|
||||
@property
|
||||
@@ -265,7 +268,7 @@ class CaptureSource:
|
||||
)
|
||||
|
||||
@property
|
||||
def cap(self) -> Optional[VideoCaptureLike]:
|
||||
def cap(self) -> VideoCaptureLike | None:
|
||||
"""Raw capture handle (compat with Sensor.cap)."""
|
||||
return self._cap
|
||||
|
||||
@@ -324,8 +327,14 @@ class CaptureSource:
|
||||
if self._cap is not None:
|
||||
try:
|
||||
self._cap.release()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# A failed release is not fatal, but it must not be invisible: the
|
||||
# handle below is dropped either way, so a live-camera handle that
|
||||
# OpenCV refused to free would otherwise leak silently until the
|
||||
# process exits. Report it and keep tearing down.
|
||||
logger.warning(
|
||||
"capture: %r.release() failed: %s", self._backend_name or "capture", exc
|
||||
)
|
||||
self._cap = None
|
||||
with self._frame_cond:
|
||||
self._latest_frame = None
|
||||
@@ -341,8 +350,11 @@ class CaptureSource:
|
||||
self._status.height = height
|
||||
self._status.fps = fps
|
||||
self._status.fourcc = fourcc
|
||||
except Exception:
|
||||
pass
|
||||
except Exception as exc:
|
||||
# Metadata is advisory: a backend that cannot answer these get() calls
|
||||
# still delivers frames, so do not fail the open. Record why the
|
||||
# reported stream geometry is left at its previous/default values.
|
||||
logger.warning("capture: could not read stream metadata: %s", exc)
|
||||
|
||||
def warmup_frames(self, count: int = 3) -> None:
|
||||
"""Discard frames so the driver/format stabilizes (sync reads only)."""
|
||||
@@ -351,7 +363,7 @@ class CaptureSource:
|
||||
for _ in range(max(0, int(count))):
|
||||
self.read_raw()
|
||||
|
||||
def read_raw(self) -> tuple[bool, Optional[np.ndarray]]:
|
||||
def read_raw(self) -> tuple[bool, np.ndarray | None]:
|
||||
"""Read one frame directly from the underlying capture (no reconnect)."""
|
||||
cap = self._cap
|
||||
if cap is None:
|
||||
@@ -379,7 +391,7 @@ class CaptureSource:
|
||||
*,
|
||||
copy: bool = True,
|
||||
wait_s: float = 0.0,
|
||||
) -> tuple[bool, Optional[np.ndarray]]:
|
||||
) -> tuple[bool, np.ndarray | None]:
|
||||
"""Return the newest frame from the background thread, or read sync."""
|
||||
if not self._background_running:
|
||||
ok, frame = self.read_raw()
|
||||
@@ -397,7 +409,7 @@ class CaptureSource:
|
||||
frame = self._latest_frame.copy() if copy else self._latest_frame
|
||||
return True, frame
|
||||
|
||||
def read(self, *, copy: bool = True, wait_s: float = 1.0) -> tuple[bool, Optional[np.ndarray]]:
|
||||
def read(self, *, copy: bool = True, wait_s: float = 1.0) -> tuple[bool, np.ndarray | None]:
|
||||
"""Compat alias used by finger runtime and older worker callers."""
|
||||
wait = wait_s if wait_s > 0 else 1.0
|
||||
return self.read_latest(copy=copy, wait_s=wait)
|
||||
@@ -531,8 +543,8 @@ def open_capture(
|
||||
config: CaptureConfig,
|
||||
*,
|
||||
factory=None,
|
||||
start_background: Optional[bool] = None,
|
||||
start_worker: Optional[bool] = None,
|
||||
start_background: bool | None = None,
|
||||
start_worker: bool | None = None,
|
||||
) -> CaptureSource:
|
||||
"""Open a capture source and optionally start its background thread.
|
||||
|
||||
|
||||
@@ -191,7 +191,7 @@ def _config_files() -> list[Path]:
|
||||
|
||||
|
||||
def _load_json(path: Path) -> dict:
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
@@ -433,7 +433,7 @@ def load_config(config_name_or_path):
|
||||
for candidate in _config_files():
|
||||
try:
|
||||
data = _load_json(candidate)
|
||||
except Exception:
|
||||
except Exception: # noqa: S112 - skip unreadable config during alias scan
|
||||
continue
|
||||
if data.get("name") in candidates:
|
||||
return data
|
||||
|
||||
@@ -42,8 +42,8 @@ from __future__ import annotations
|
||||
import os
|
||||
import queue
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Callable, Iterable, Sequence
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -243,7 +243,7 @@ class StreamRecorder:
|
||||
"fourcc": self.fourcc,
|
||||
}
|
||||
|
||||
def __enter__(self) -> "StreamRecorder":
|
||||
def __enter__(self) -> StreamRecorder:
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> bool:
|
||||
|
||||
@@ -18,8 +18,8 @@ from .mesh import (
|
||||
lift_uv_deformation_to_3d,
|
||||
lift_uv_flow_to_3d,
|
||||
mesh_uv_to_world_scale,
|
||||
scalar_map_vertex_colors,
|
||||
sample_field_at_mesh_vertices,
|
||||
scalar_map_vertex_colors,
|
||||
)
|
||||
from .runtime import (
|
||||
FingerGeometry,
|
||||
@@ -39,8 +39,8 @@ __all__ = [
|
||||
"FingerGeometry",
|
||||
"FingerRuntime",
|
||||
"TactileProcessor",
|
||||
"UVSurfaceMesh",
|
||||
"UVMeshViewer",
|
||||
"UVSurfaceMesh",
|
||||
"build_uv_surface_mesh",
|
||||
"compute_uv_surface_frames",
|
||||
"deform_mesh_vertices",
|
||||
|
||||
@@ -12,15 +12,15 @@ class UVSurfaceMesh:
|
||||
"""Triangle mesh built from a UV flatten grid and world_xyz surface map."""
|
||||
|
||||
__slots__ = (
|
||||
"rest_vertices",
|
||||
"triangles",
|
||||
"vertex_ys",
|
||||
"vertex_xs",
|
||||
"index_map",
|
||||
"valid_mask",
|
||||
"shape",
|
||||
"world_xyz",
|
||||
"base_colors",
|
||||
"index_map",
|
||||
"rest_vertices",
|
||||
"shape",
|
||||
"triangles",
|
||||
"valid_mask",
|
||||
"vertex_xs",
|
||||
"vertex_ys",
|
||||
"world_xyz",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -246,7 +246,7 @@ class FingerRuntime:
|
||||
mirror=False,
|
||||
auto_reconnect: bool = False,
|
||||
capture_mode: str = "latest",
|
||||
) -> "FingerRuntime":
|
||||
) -> FingerRuntime:
|
||||
"""Open a live finger runtime from export bundle, config, and video source."""
|
||||
del capture_mode # deprecated; latest-frame capture only
|
||||
if camera_id is not None:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
|
||||
from .unroll import UVRemap
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import numpy as np
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from .backend_availability import CUPY_AVAILABLE, get_cupy
|
||||
|
||||
cp = get_cupy()
|
||||
# 缓存形态学操作的核,避免重复创建
|
||||
_morphology_kernels = {}
|
||||
|
||||
@@ -20,4 +20,4 @@ from .grid import StructuredGrid
|
||||
from .poisson import PoissonSolver
|
||||
from .timing import Timer
|
||||
|
||||
__all__ = ["nHHD", "StructuredGrid", "PoissonSolver", "Timer"]
|
||||
__all__ = ["PoissonSolver", "StructuredGrid", "Timer", "nHHD"]
|
||||
|
||||
@@ -2,16 +2,16 @@ import numpy
|
||||
|
||||
from ..backend_availability import (
|
||||
CUPY_AVAILABLE,
|
||||
OPENCL_AVAILABLE,
|
||||
KERNEL_AVAILABLE,
|
||||
cp,
|
||||
OPENCL_AVAILABLE,
|
||||
_torque_kernel,
|
||||
cp,
|
||||
)
|
||||
from .grid import StructuredGrid
|
||||
from .poisson import PoissonSolver
|
||||
|
||||
|
||||
class nHHD(object):
|
||||
class nHHD:
|
||||
|
||||
# constructor
|
||||
def __init__(self, cuda=True, **kwargs):
|
||||
@@ -25,9 +25,7 @@ class nHHD(object):
|
||||
backend = 'cupy' if CUPY_AVAILABLE else ('opencl' if OPENCL_AVAILABLE else 'cpu')
|
||||
|
||||
# Validate backend availability and fallback to 'cpu' if needed
|
||||
if backend == 'cupy' and not CUPY_AVAILABLE:
|
||||
backend = 'cpu'
|
||||
elif backend == 'opencl' and not OPENCL_AVAILABLE:
|
||||
if backend == 'cupy' and not CUPY_AVAILABLE or backend == 'opencl' and not OPENCL_AVAILABLE:
|
||||
backend = 'cpu'
|
||||
|
||||
self.debug = kwargs.get('debug', False)
|
||||
|
||||
+13
-11
@@ -56,7 +56,9 @@ def build_masked_factor(lap):
|
||||
options=dict(SymmetricMode=True),
|
||||
)
|
||||
return lu.solve, numpy.float32
|
||||
except Exception:
|
||||
except Exception: # noqa: S112 - try the next pivoting strategy
|
||||
# Probe: try the next pivoting strategy; the float64 fallback below is
|
||||
# returned if every candidate fails, so this failure is not lost.
|
||||
continue
|
||||
# Last resort: the original pivoting LU on the float64 matrix.
|
||||
return sparse_linalg.factorized(lap_csc), numpy.float64
|
||||
@@ -82,20 +84,20 @@ class MaskedDomain:
|
||||
"""Reduced Laplacian system on active mask pixels (2D only)."""
|
||||
|
||||
__slots__ = (
|
||||
"shape",
|
||||
"mask",
|
||||
"index_map",
|
||||
"factor",
|
||||
"factor_dtype",
|
||||
"lap",
|
||||
"num_active",
|
||||
"pinned",
|
||||
"active_coords",
|
||||
"active_linear_indices",
|
||||
"_rhs_buffer",
|
||||
"_rhs_pair_buffer",
|
||||
"_scatter_buffer",
|
||||
"_scatter_pair_buffer",
|
||||
"active_coords",
|
||||
"active_linear_indices",
|
||||
"factor",
|
||||
"factor_dtype",
|
||||
"index_map",
|
||||
"lap",
|
||||
"mask",
|
||||
"num_active",
|
||||
"pinned",
|
||||
"shape",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import numpy
|
||||
import timeit
|
||||
import sys
|
||||
# import cv2
|
||||
from scipy import signal, spatial
|
||||
|
||||
import numpy
|
||||
|
||||
# import cv2
|
||||
from ..backend_availability import (
|
||||
CUPY_AVAILABLE, OPENCL_AVAILABLE, KERNEL_AVAILABLE,
|
||||
cp, cp_signal, _torque_kernel,
|
||||
cl, cla, cl_rfftn, cl_irfftn
|
||||
CUPY_AVAILABLE,
|
||||
cp,
|
||||
)
|
||||
from .timing import Timer
|
||||
|
||||
class StructuredGrid(object):
|
||||
|
||||
class StructuredGrid:
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@@ -50,7 +50,7 @@ from .timing import Timer
|
||||
'''
|
||||
|
||||
|
||||
class PoissonSolver(object):
|
||||
class PoissonSolver:
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
args = kwargs.keys()
|
||||
|
||||
@@ -2,4 +2,4 @@ from .capacitance import CapacitanceFFTSolver
|
||||
from .masked_sparse import MaskedSparseSolver
|
||||
from .rectangular import RectangularFFTSolver
|
||||
|
||||
__all__ = ["RectangularFFTSolver", "MaskedSparseSolver", "CapacitanceFFTSolver"]
|
||||
__all__ = ["CapacitanceFFTSolver", "MaskedSparseSolver", "RectangularFFTSolver"]
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import numpy
|
||||
import timeit
|
||||
|
||||
import numpy
|
||||
from scipy import signal
|
||||
|
||||
from ...backend_availability import (
|
||||
CUPY_AVAILABLE,
|
||||
OPENCL_AVAILABLE,
|
||||
cl,
|
||||
cl_irfftn,
|
||||
cl_rfftn,
|
||||
cla,
|
||||
cp,
|
||||
cp_signal,
|
||||
cl,
|
||||
cla,
|
||||
cl_rfftn,
|
||||
cl_irfftn,
|
||||
)
|
||||
from ..timing import Timer
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import numpy
|
||||
import timeit
|
||||
|
||||
class Timer(object):
|
||||
import numpy
|
||||
|
||||
|
||||
class Timer:
|
||||
|
||||
def display(self):
|
||||
|
||||
|
||||
@@ -66,9 +66,10 @@ from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import Callable, Iterable, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from queue import Empty, Full, Queue
|
||||
from typing import Any, Callable, Iterable, Sequence
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -155,7 +156,7 @@ class StreamPipeline:
|
||||
contact: bool = False,
|
||||
depth: int = 2,
|
||||
include: Sequence[str] | Iterable[str] = ("image",),
|
||||
on_frame: Callable[["FrameSnapshot"], None] | None = None,
|
||||
on_frame: Callable[[FrameSnapshot], None] | None = None,
|
||||
verbose: bool = False,
|
||||
fps_window: int = 30,
|
||||
) -> None:
|
||||
@@ -189,7 +190,7 @@ class StreamPipeline:
|
||||
|
||||
# ------------------------------------------------------------------ ctors
|
||||
@classmethod
|
||||
def for_finger_runtime(cls, runtime: Any, **kwargs: Any) -> "StreamPipeline":
|
||||
def for_finger_runtime(cls, runtime: Any, **kwargs: Any) -> StreamPipeline:
|
||||
"""Build a pipeline for a ``FingerRuntime`` (capture + UV unroll stage)."""
|
||||
|
||||
def produce() -> np.ndarray | None:
|
||||
@@ -199,7 +200,7 @@ class StreamPipeline:
|
||||
return cls(runtime.tactile.raw_sensor, produce, **kwargs)
|
||||
|
||||
# --------------------------------------------------------------- lifecycle
|
||||
def start(self) -> "StreamPipeline":
|
||||
def start(self) -> StreamPipeline:
|
||||
if self._running:
|
||||
return self
|
||||
self._running = True
|
||||
@@ -232,7 +233,7 @@ class StreamPipeline:
|
||||
err, self._error = self._error, None
|
||||
raise err
|
||||
|
||||
def __enter__(self) -> "StreamPipeline":
|
||||
def __enter__(self) -> StreamPipeline:
|
||||
return self.start()
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> bool:
|
||||
|
||||
+21
-19
@@ -7,23 +7,19 @@ and Helmholtz-Hodge decomposition.
|
||||
"""
|
||||
|
||||
# import os # only used by commented-out stitch_cal
|
||||
import numpy as np
|
||||
import cv2
|
||||
import time
|
||||
from enum import Enum, auto
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
# from threading import Thread, Event
|
||||
# from queue import Queue, Empty
|
||||
|
||||
from .Stitcher import Stitcher
|
||||
from .tracker import Tracker
|
||||
from .hhd import nHHD
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from . import calibration as cal_mod
|
||||
from . import configs
|
||||
from .get_depth import Contact3D
|
||||
from .backend_availability import (check_cupy_available, check_opencl_available)
|
||||
from . import configs
|
||||
from .backend_availability import check_cupy_available, check_opencl_available
|
||||
from .frame_processors import create_frame_processor
|
||||
from .get_depth import Contact3D
|
||||
from .hhd import nHHD
|
||||
from .signal_processing import (
|
||||
DEFAULT_SAMPLE_RATE_HZ,
|
||||
CausalMedianFilter,
|
||||
@@ -31,6 +27,11 @@ from .signal_processing import (
|
||||
SampleRateEstimator,
|
||||
)
|
||||
|
||||
# from threading import Thread, Event
|
||||
# from queue import Queue, Empty
|
||||
from .Stitcher import Stitcher
|
||||
from .tracker import Tracker
|
||||
|
||||
# Default signal-conditioning settings (config: top-level "filters" block).
|
||||
#
|
||||
# A key a config does not state is **off**, not "whatever we thought was
|
||||
@@ -101,6 +102,7 @@ from .capture import (
|
||||
open_capture,
|
||||
)
|
||||
|
||||
|
||||
class Sensor:
|
||||
"""
|
||||
Optical tactile sensor interface with deformation analysis.
|
||||
@@ -137,7 +139,7 @@ class Sensor:
|
||||
|
||||
def __init__(self, vid_src, cuda=True, isstitch=True, verbose=True, debug=False,
|
||||
config_name="prototype", cal_path=None, override=False, box=None, mirror=True,
|
||||
backend='auto', capture_config: Optional[CaptureConfig] = None,
|
||||
backend='auto', capture_config: CaptureConfig | None = None,
|
||||
auto_reconnect: bool = False, capture_mode: str = "latest",
|
||||
capture_queue_size: int = 1, capture_overflow: str = "drop_oldest",
|
||||
**_deprecated_capture_kwargs):
|
||||
@@ -215,11 +217,11 @@ class Sensor:
|
||||
self.n_boxes = None
|
||||
self.fnormal = self.fshearx = self.fsheary = self.ftor = 0.0
|
||||
self._force_raw = (0.0, 0.0, 0.0)
|
||||
self.calibration: Optional[cal_mod.Calibration] = None
|
||||
self._force_cal_sensor_name: Optional[str] = None
|
||||
self.calibration: cal_mod.Calibration | None = None
|
||||
self._force_cal_sensor_name: str | None = None
|
||||
#: Why no calibration was applied, when a bundle existed but carried no
|
||||
#: usable coefficients. ``None`` means the question never came up.
|
||||
self._force_cal_attempts: Optional[list[dict]] = None
|
||||
self._force_cal_attempts: list[dict] | None = None
|
||||
self._init_force_calibration()
|
||||
|
||||
# ---------------------------------------------------------------
|
||||
@@ -413,7 +415,7 @@ class Sensor:
|
||||
# self._av_stream = None
|
||||
# self._av_decoder = None
|
||||
self.cap = None # compat alias for _capture_worker.source.cap
|
||||
self._capture_source: Optional[CaptureSource] = None
|
||||
self._capture_source: CaptureSource | None = None
|
||||
self._is_live_camera = (
|
||||
is_live_camera(self.vid_src) and not self._external_only
|
||||
)
|
||||
@@ -1013,7 +1015,7 @@ class Sensor:
|
||||
return result
|
||||
|
||||
|
||||
def read_info(self, *args) -> Tuple:
|
||||
def read_info(self, *args) -> tuple:
|
||||
"""
|
||||
Get sensor data by info type.
|
||||
|
||||
@@ -1323,7 +1325,7 @@ class Sensor:
|
||||
return
|
||||
try:
|
||||
attempts.append(configs.describe_bundle_resolution(name))
|
||||
except Exception: # noqa: BLE001 - diagnostics must never break loading
|
||||
except Exception: # noqa: BLE001, S112 - diagnostics must never break loading
|
||||
continue
|
||||
# Deliberately NOT setting ``_force_cal_sensor_name``: callers read a
|
||||
# non-None value there as "this sensor is calibrated", and a bundle
|
||||
@@ -1700,6 +1702,6 @@ class Sensor:
|
||||
for k, v in timings.items():
|
||||
if k != 'total':
|
||||
print(f" {k}: {v:7.2f} ms")
|
||||
print(f" ─────────────────────────")
|
||||
print(" ─────────────────────────")
|
||||
print(f" TOTAL: {timings['total']:7.2f} ms ({1000/timings['total']:.1f} FPS)")
|
||||
|
||||
|
||||
@@ -15,19 +15,19 @@ from .ops import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Stitcher",
|
||||
"StitchStatus",
|
||||
"BLEND_MODES",
|
||||
"resolve_blend_mode",
|
||||
"validate_box_grid",
|
||||
"get_blend_backend",
|
||||
"StitchStatus",
|
||||
"Stitcher",
|
||||
"available_blend_backends",
|
||||
"tile_weight_map",
|
||||
"paste_tile_clipped",
|
||||
"copy_into_tile_buffer",
|
||||
"build_blend_coef",
|
||||
"compute_final_crop_rect",
|
||||
"copy_into_tile_buffer",
|
||||
"coverage_ratio",
|
||||
"get_blend_backend",
|
||||
"largest_fully_covered_rect",
|
||||
"match_bgr_brightness",
|
||||
"coverage_ratio",
|
||||
"paste_tile_clipped",
|
||||
"resolve_blend_mode",
|
||||
"tile_weight_map",
|
||||
"validate_box_grid",
|
||||
]
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
from . import boxes as boxes_mod
|
||||
from . import feature as feature_mod
|
||||
from . import tiles as tiles_mod
|
||||
from .backends import get_blend_backend
|
||||
from .config import (
|
||||
BLEND_MODES,
|
||||
@@ -14,10 +16,7 @@ from .config import (
|
||||
numba_blend_available,
|
||||
resolve_blend_mode,
|
||||
)
|
||||
from . import boxes as boxes_mod
|
||||
from . import feature as feature_mod
|
||||
from .ops import build_blend_coef
|
||||
from . import tiles as tiles_mod
|
||||
|
||||
|
||||
class Stitcher:
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, Optional
|
||||
from collections.abc import Callable
|
||||
from typing import Optional
|
||||
|
||||
from ..config import numba_blend_available
|
||||
from .base import BlendBackend
|
||||
from .numba_backend import NumbaBlendBackend
|
||||
from .numpy_backend import NumpyBlendBackend
|
||||
from ..config import numba_blend_available
|
||||
|
||||
_BACKENDS = {
|
||||
"numpy": NumpyBlendBackend,
|
||||
@@ -18,7 +19,7 @@ _BACKENDS = {
|
||||
def get_blend_backend(
|
||||
name: str,
|
||||
*,
|
||||
on_numba_failure: Optional[Callable[[BaseException], None]] = None,
|
||||
on_numba_failure: Callable[[BaseException], None] | None = None,
|
||||
) -> BlendBackend:
|
||||
"""Return a blend backend instance by name.
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import numpy as np
|
||||
from .numpy_backend import NumpyBlendBackend, blend_kernel_numpy
|
||||
|
||||
try:
|
||||
from orisys_blend.kernel import _NUMBA_AVAILABLE, blend_kernel as _blend_kernel_numba
|
||||
from orisys_blend.kernel import _NUMBA_AVAILABLE
|
||||
from orisys_blend.kernel import blend_kernel as _blend_kernel_numba
|
||||
except Exception:
|
||||
_NUMBA_AVAILABLE = False
|
||||
_blend_kernel_numba = None
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import List, Optional, Sequence, Tuple
|
||||
from collections.abc import Sequence
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -38,13 +38,13 @@ def correct_edge_bloom(img, method: str = "divide", kernel_size=None, clip: bool
|
||||
|
||||
|
||||
def filter_boxes_by_size(
|
||||
boxes: Sequence[Tuple[int, int, int, int]],
|
||||
boxes: Sequence[tuple[int, int, int, int]],
|
||||
img_shape,
|
||||
*,
|
||||
min_area_frac: float,
|
||||
max_area_frac: float,
|
||||
max_aspect_ratio: float,
|
||||
) -> List[Tuple[int, int, int, int]]:
|
||||
) -> list[tuple[int, int, int, int]]:
|
||||
h, w = img_shape
|
||||
img_area = h * w
|
||||
min_area = img_area * float(min_area_frac)
|
||||
@@ -72,7 +72,7 @@ def detect_nonblack_regions(
|
||||
show: bool = False,
|
||||
row_tol: float = 0.4,
|
||||
save_preview_path: str = "./config/compound_eye.png",
|
||||
) -> Tuple[int, List[Tuple[int, int, int, int]]]:
|
||||
) -> tuple[int, list[tuple[int, int, int, int]]]:
|
||||
"""Detect tile boxes; returns ``(n_boxes, boxes as x0,y0,x1,y1)``."""
|
||||
if img is None:
|
||||
raise ValueError("Input image is None.")
|
||||
@@ -80,7 +80,7 @@ def detect_nonblack_regions(
|
||||
h, w = img.shape[:2]
|
||||
n_boxes = 0
|
||||
bk_thresh = 0
|
||||
boxes: List[Tuple[int, int, int, int]] = []
|
||||
boxes: list[tuple[int, int, int, int]] = []
|
||||
|
||||
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
v = hsv[:, :, 2]
|
||||
@@ -114,9 +114,7 @@ def detect_nonblack_regions(
|
||||
bk_thresh += bk_thresh_step
|
||||
|
||||
print(
|
||||
"Detected {} candidate boxes with bk_thresh={}".format(
|
||||
n_boxes, bk_thresh - bk_thresh_step
|
||||
)
|
||||
f"Detected {n_boxes} candidate boxes with bk_thresh={bk_thresh - bk_thresh_step}"
|
||||
)
|
||||
if n_boxes == 0:
|
||||
return 0, []
|
||||
@@ -194,7 +192,7 @@ def find_stitch_boxes(
|
||||
nrow: int,
|
||||
ncol: int,
|
||||
show: bool = False,
|
||||
n_boxes_target: Optional[int] = None,
|
||||
n_boxes_target: int | None = None,
|
||||
):
|
||||
"""Run bloom correction + detection; return ``(n_boxes, boxes, grid_ok)``.
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Mapping, Optional
|
||||
from typing import Any
|
||||
|
||||
# JSON ``stitcher.blend`` / ``blend_list``: none = fast paste; numpy/numba = feathered.
|
||||
BLEND_MODES = frozenset({"none", "numpy", "numba"})
|
||||
@@ -19,7 +20,7 @@ def numba_blend_available() -> bool:
|
||||
return bool(_NUMBA_AVAILABLE and blend_kernel is not None)
|
||||
|
||||
|
||||
def resolve_blend_mode(config_stitcher: Optional[Mapping[str, Any]] = None):
|
||||
def resolve_blend_mode(config_stitcher: Mapping[str, Any] | None = None):
|
||||
"""Resolve stitcher blend mode from env, then JSON ``blend``.
|
||||
|
||||
Returns ``(mode, fell_back_from_numba)`` where mode is one of
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional, Sequence
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -530,14 +531,14 @@ def stitch_feature_optimized(
|
||||
nrow: int,
|
||||
ncol: int,
|
||||
tile_list: Sequence,
|
||||
cache: Dict[str, Any],
|
||||
cache: dict[str, Any],
|
||||
blend_backend: BlendBackend,
|
||||
blend: bool = True,
|
||||
match_brightness: bool = False,
|
||||
pad: int = 5,
|
||||
print_every: int = 30,
|
||||
verbose: bool = False,
|
||||
frame_idx_holder: Optional[Dict[str, int]] = None,
|
||||
frame_idx_holder: dict[str, int] | None = None,
|
||||
):
|
||||
"""Estimate transforms once and composite the current tile grid."""
|
||||
plan = get_feature_stitch_plan(isstitch, index, nrow, ncol, tile_list)
|
||||
|
||||
@@ -6,8 +6,6 @@ instance state so future Torch/CuPy backends can reuse the same call shapes.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
@@ -74,7 +72,7 @@ def match_bgr_brightness(tile_bgr, ref_mean_gray: float):
|
||||
return cv2.LUT(tile_bgr, lut)
|
||||
|
||||
|
||||
def largest_fully_covered_rect(cov) -> Optional[Tuple[int, int, int, int]]:
|
||||
def largest_fully_covered_rect(cov) -> tuple[int, int, int, int] | None:
|
||||
if cov.size == 0:
|
||||
return None
|
||||
|
||||
@@ -104,7 +102,7 @@ def largest_fully_covered_rect(cov) -> Optional[Tuple[int, int, int, int]]:
|
||||
return best_rect
|
||||
|
||||
|
||||
def compute_final_crop_rect(cov) -> Optional[Tuple[int, int, int, int]]:
|
||||
def compute_final_crop_rect(cov) -> tuple[int, int, int, int] | None:
|
||||
valid = cov > 0
|
||||
if not np.any(valid):
|
||||
return None
|
||||
@@ -126,7 +124,7 @@ def coverage_ratio(cov) -> float:
|
||||
return float(np.mean(cov > 0))
|
||||
|
||||
|
||||
def translation_from_H_to_ref(H) -> Tuple[float, float]:
|
||||
def translation_from_H_to_ref(H) -> tuple[float, float]:
|
||||
if H is None:
|
||||
return 0.0, 0.0
|
||||
H = np.asarray(H, dtype=np.float64)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Iterable, List, Optional, Sequence, Tuple
|
||||
from collections.abc import Iterable, Sequence
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -11,10 +11,10 @@ from .ops import copy_into_tile_buffer
|
||||
|
||||
def extract_units_from_boxes(
|
||||
img,
|
||||
boxes: Sequence[Tuple[int, int, int, int]],
|
||||
boxes: Sequence[tuple[int, int, int, int]],
|
||||
*,
|
||||
selected_index: Iterable[int],
|
||||
units: Optional[list] = None,
|
||||
units: list | None = None,
|
||||
flip_x: bool = True,
|
||||
flip_y: bool = True,
|
||||
) -> list:
|
||||
@@ -49,7 +49,7 @@ def extract_units_from_boxes(
|
||||
return flipped_units
|
||||
|
||||
|
||||
def get_tile_by_index(units: Sequence, idx) -> List:
|
||||
def get_tile_by_index(units: Sequence, idx) -> list:
|
||||
if isinstance(idx, int):
|
||||
idx = [idx]
|
||||
if len(idx) > len(units):
|
||||
@@ -61,7 +61,7 @@ def get_tile_by_index(units: Sequence, idx) -> List:
|
||||
return tile_list
|
||||
|
||||
|
||||
def get_tile_index_by_position(units: Sequence, x, y, tol: int = 20) -> Optional[int]:
|
||||
def get_tile_index_by_position(units: Sequence, x, y, tol: int = 20) -> int | None:
|
||||
i = 0
|
||||
for xi, yi, _tile in units:
|
||||
if abs(int(x) - int(xi)) <= tol and abs(int(y) - int(yi)) <= tol:
|
||||
@@ -70,7 +70,7 @@ def get_tile_index_by_position(units: Sequence, x, y, tol: int = 20) -> Optional
|
||||
return None
|
||||
|
||||
|
||||
def get_unit_pos(units: Sequence, idx) -> List[Tuple[int, int]]:
|
||||
def get_unit_pos(units: Sequence, idx) -> list[tuple[int, int]]:
|
||||
if isinstance(idx, int):
|
||||
idx = [idx]
|
||||
if len(idx) > len(units):
|
||||
@@ -78,7 +78,7 @@ def get_unit_pos(units: Sequence, idx) -> List[Tuple[int, int]]:
|
||||
return [(units[i][0], units[i][1]) for i in idx]
|
||||
|
||||
|
||||
def normalize_index(index) -> Tuple[int, ...]:
|
||||
def normalize_index(index) -> tuple[int, ...]:
|
||||
if isinstance(index, int):
|
||||
return (index,)
|
||||
if isinstance(index, np.ndarray):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import time
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import time
|
||||
|
||||
|
||||
class Tracker:
|
||||
@@ -255,7 +256,6 @@ class Tracker:
|
||||
"""
|
||||
Setup a simpler version using available models with performance optimizations
|
||||
"""
|
||||
import torchvision.transforms as T
|
||||
from torchvision.models.optical_flow import raft_large
|
||||
# Load model and move to GPU
|
||||
model = raft_large(pretrained=True, progress=False).to(self.__raft_device)
|
||||
|
||||
@@ -6,7 +6,17 @@ Runtime: live camera frame -> FrameUnwarper -> Sensor (with valid_mask)
|
||||
"""
|
||||
|
||||
from .domain import UVDomain
|
||||
from .mesh_uv import align_frame_to_uv_map, barycentric_grid, face_uvs_from_loop_data, uv_triangle_to_image
|
||||
from .geometry import (
|
||||
resolve_uv_runtime_sizes,
|
||||
scale_camera_geometry,
|
||||
scaled_map_cache_path,
|
||||
)
|
||||
from .mesh_uv import (
|
||||
align_frame_to_uv_map,
|
||||
barycentric_grid,
|
||||
face_uvs_from_loop_data,
|
||||
uv_triangle_to_image,
|
||||
)
|
||||
from .precompute import (
|
||||
build_or_load_domain,
|
||||
build_uv_domain,
|
||||
@@ -15,7 +25,6 @@ from .precompute import (
|
||||
load_exported_data,
|
||||
recommend_edge_expand,
|
||||
)
|
||||
from .geometry import resolve_uv_runtime_sizes, scale_camera_geometry, scaled_map_cache_path
|
||||
from .remap import UVRemap, flatten_frame, remap_valid_ratio
|
||||
from .surface import UVSurfaceMap
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ build_or_load_domain, tests, and 3D viewer tools.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -22,7 +21,7 @@ class UVDomain:
|
||||
"""Image-to-UV remap plus optional UV-to-world surface map."""
|
||||
|
||||
remap: UVRemap
|
||||
surface: Optional[UVSurfaceMap] = None
|
||||
surface: UVSurfaceMap | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: dict) -> "UVDomain":
|
||||
|
||||
@@ -31,7 +31,7 @@ def load_calib_json(json_path):
|
||||
|
||||
returns: (K, dist_coeffs, (width, height))
|
||||
"""
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
with open(json_path, encoding="utf-8") as f:
|
||||
calib = json.load(f)
|
||||
K = np.array(calib["camera_matrix"], dtype=np.float64)
|
||||
dist = np.array(calib["distortion"], dtype=np.float64).flatten()
|
||||
|
||||
@@ -5,7 +5,7 @@ convert source camera frames into the UV flatten domain.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from typing import Any
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
@@ -18,9 +18,9 @@ class UVRemap:
|
||||
map_x: np.ndarray
|
||||
map_y: np.ndarray
|
||||
valid_mask: np.ndarray
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
_map1_fixed: Optional[np.ndarray] = field(default=None, repr=False, compare=False)
|
||||
_map2_fixed: Optional[np.ndarray] = field(default=None, repr=False, compare=False)
|
||||
metadata: dict[str, Any] = field(default_factory=dict)
|
||||
_map1_fixed: np.ndarray | None = field(default=None, repr=False, compare=False)
|
||||
_map2_fixed: np.ndarray | None = field(default=None, repr=False, compare=False)
|
||||
_border_only_invalid: bool = field(default=False, repr=False, compare=False)
|
||||
|
||||
@property
|
||||
@@ -153,7 +153,7 @@ def _cache_matches(
|
||||
return True
|
||||
|
||||
|
||||
def _remap_maps(remap) -> Tuple[np.ndarray, np.ndarray, np.ndarray, bool]:
|
||||
def _remap_maps(remap) -> tuple[np.ndarray, np.ndarray, np.ndarray, bool]:
|
||||
if isinstance(remap, UVRemap):
|
||||
remap.ensure_fast_maps()
|
||||
valid_mask = remap.valid_mask
|
||||
|
||||
Reference in New Issue
Block a user