add back GUI 2D display with optional raw / pure color background
This commit is contained in:
+3
-2
@@ -4,13 +4,14 @@ __pycache__/
|
||||
.pytest_cache/
|
||||
|
||||
# Packaging / build outputs
|
||||
/build/
|
||||
/build/nuitka/
|
||||
/build/lib/orisys/
|
||||
/build/pyinstaller_work/
|
||||
/dist/
|
||||
/dist_obfuscated/
|
||||
/dist_pyd/
|
||||
/dist_single_pyd/
|
||||
/nuitka_wheel/
|
||||
/build_nuitka/
|
||||
/release/
|
||||
/SDK/
|
||||
*.egg-info/
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,44 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['D:\\repo\\orisyssdk_git\\examples\\finger_pipeline_3dview.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[('D:\\repo\\orisyssdk_git\\config', 'config'), ('D:\\repo\\orisyssdk_git\\assets', 'assets')],
|
||||
hiddenimports=['PyQt5', 'pyqtgraph', 'open3d', 'cv2', 'orisys', 'gui', 'pipeline', 'viz', 'app_paths', 'plotly', '_plotly_utils', 'open3d.cpu.pybind'],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=['cupy', 'cupyx', 'pyopencl', 'tkinter', 'pip', 'wheel', 'matplotlib'],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
[],
|
||||
exclude_binaries=True,
|
||||
name='OrisysDashboard',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
console=False,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
coll = COLLECT(
|
||||
exe,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
name='OrisysDashboard',
|
||||
)
|
||||
@@ -0,0 +1,194 @@
|
||||
"""Build a CPU-only standalone Windows folder for Orisys 3D dashboard using PyInstaller.
|
||||
|
||||
Usage:
|
||||
conda activate py310 # or a clean virtualenv with only required packages
|
||||
pip install pyinstaller
|
||||
python build/build_gui_win_pyinstaller.py
|
||||
|
||||
Output:
|
||||
dist/OrisysDashboard/OrisysDashboard.exe (+ bundled Python, DLLs, config/, assets/)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# ---------- Configuration ----------
|
||||
ENTRY_SCRIPT = Path("examples/finger_pipeline_3dview.py").resolve()
|
||||
EXE_NAME = "OrisysDashboard.exe"
|
||||
DIST_NAME = "OrisysDashboard" # final folder name
|
||||
repo_root = Path(__file__).parent.parent
|
||||
|
||||
# Packages you want to force-include (PyInstaller often auto-detects them, but explicit avoids misses)
|
||||
HIDDEN_IMPORTS = [
|
||||
"PyQt5",
|
||||
"pyqtgraph",
|
||||
"open3d",
|
||||
"cv2",
|
||||
"orisys",
|
||||
"gui",
|
||||
"pipeline",
|
||||
"viz",
|
||||
"app_paths",
|
||||
"plotly",
|
||||
"_plotly_utils",
|
||||
# sometimes needed for open3d:
|
||||
"open3d.cpu.pybind", # adjust to actual module if needed
|
||||
]
|
||||
|
||||
# Modules to completely exclude (they won't be imported anyway if absent in venv)
|
||||
EXCLUDE_MODULES = [
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"tkinter",
|
||||
# "unittest",
|
||||
# "email",
|
||||
# "http",
|
||||
# "xmlrpc",
|
||||
# "pkg_resources",
|
||||
# "setuptools",
|
||||
"pip",
|
||||
"wheel",
|
||||
"matplotlib", # unless you use it
|
||||
# "scipy", # unless you use it
|
||||
]
|
||||
|
||||
# Data directories: (source relative to repo root, destination in bundle)
|
||||
DATA_DIRS = [
|
||||
("config", "config"),
|
||||
("assets", "assets"),
|
||||
]
|
||||
|
||||
# Optional: UPX directory (download from https://upx.github.io)
|
||||
UPX_DIR = r"C:\tools\upx" # set to None if not available
|
||||
# ---------- End Configuration ----------
|
||||
|
||||
|
||||
def clean_dist(dist_path: Path) -> None:
|
||||
if dist_path.exists():
|
||||
shutil.rmtree(dist_path)
|
||||
dist_path.mkdir(parents=True)
|
||||
|
||||
|
||||
def build_with_pyinstaller():
|
||||
# Ensure we are in a clean, minimal venv
|
||||
print("Using Python:", sys.executable)
|
||||
|
||||
# Output folder for PyInstaller work files
|
||||
work_dir = Path("build/pyinstaller_work")
|
||||
dist_dir = Path("dist") / DIST_NAME
|
||||
clean_dist(work_dir)
|
||||
clean_dist(dist_dir)
|
||||
|
||||
# Build PyInstaller command
|
||||
cmd = [
|
||||
sys.executable, "-m", "PyInstaller",
|
||||
"--onedir", # produce a folder, not a single exe
|
||||
"--name", DIST_NAME,
|
||||
f"--distpath={dist_dir.parent}",
|
||||
f"--workpath={work_dir}",
|
||||
f"--specpath=build",
|
||||
"--noconfirm",
|
||||
"--clean",
|
||||
"--windowed", # or "--console" if you need terminal
|
||||
]
|
||||
|
||||
# Add data files
|
||||
for src_rel, dest_rel in DATA_DIRS:
|
||||
src = Path(src_rel).resolve()
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory missing: {src}")
|
||||
cmd.append(f"--add-data={src};{dest_rel}")
|
||||
|
||||
# Logo file if exists
|
||||
logo = Path("logo.png")
|
||||
if logo.exists():
|
||||
cmd.append(f"--add-data={logo.resolve()};.")
|
||||
|
||||
# Hidden imports
|
||||
for mod in HIDDEN_IMPORTS:
|
||||
cmd.append(f"--hidden-import={mod}")
|
||||
|
||||
# Exclude modules
|
||||
for mod in EXCLUDE_MODULES:
|
||||
cmd.append(f"--exclude-module={mod}")
|
||||
|
||||
# UPX if available
|
||||
if UPX_DIR and Path(UPX_DIR).is_dir():
|
||||
cmd.append(f"--upx-dir={UPX_DIR}")
|
||||
|
||||
# Entry point
|
||||
cmd.append(str(ENTRY_SCRIPT))
|
||||
|
||||
# Environment (same as Nuitka script)
|
||||
env = os.environ.copy()
|
||||
env["PYTHONPATH"] = str(Path("src").resolve()) + os.pathsep + str(Path("examples").resolve()) + os.pathsep + env.get("PYTHONPATH", "")
|
||||
|
||||
print("\n=== Running PyInstaller ===")
|
||||
print("Command:", " ".join(cmd))
|
||||
subprocess.check_call(cmd, env=env)
|
||||
|
||||
# After build, copy data directories explicitly
|
||||
# PyInstaller output folder
|
||||
dist_dir = Path("dist") / DIST_NAME
|
||||
final_exe = dist_dir / EXE_NAME
|
||||
if not final_exe.is_file():
|
||||
raise RuntimeError(f"Executable not found at {final_exe}")
|
||||
print("\n=== Copying data directories ===")
|
||||
for src_rel, dest_rel in DATA_DIRS:
|
||||
src = repo_root / src_rel # use an absolute reference to repo root
|
||||
dest = dist_dir / dest_rel
|
||||
if not src.is_dir():
|
||||
raise FileNotFoundError(f"Data directory not found: {src}")
|
||||
if dest.exists():
|
||||
shutil.rmtree(dest)
|
||||
shutil.copytree(src, dest, dirs_exist_ok=True) # Python 3.8+ use dirs_exist_ok=True
|
||||
print(f" {src} -> {dest}")
|
||||
|
||||
# Copy logo
|
||||
logo_src = repo_root / "logo.png"
|
||||
if logo_src.is_file():
|
||||
shutil.copy2(logo_src, dist_dir / "logo.png")
|
||||
print(f" Logo copied")
|
||||
|
||||
# Write README (same as Nuitka script)
|
||||
readme = dist_dir / "README.txt"
|
||||
readme.write_text(
|
||||
"\n".join([
|
||||
"Orisys Finger Dashboard (standalone)",
|
||||
"",
|
||||
"Run:",
|
||||
f" {EXE_NAME}",
|
||||
f" {EXE_NAME} --video path\\to\\video.mp4",
|
||||
f" {EXE_NAME} --config config\\dd02-ov.json",
|
||||
"",
|
||||
"Bundled folders:",
|
||||
" config/ sensor JSON configs",
|
||||
" assets/ finger mesh and visualization data",
|
||||
"",
|
||||
"CPU-only build (no CUDA/CuPy).",
|
||||
]),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
print(f"\n[SUCCESS] Standalone GUI folder: {dist_dir}")
|
||||
print(f"Executable: {final_exe}")
|
||||
|
||||
# Optional smoke test (same as Nuitka script)
|
||||
try:
|
||||
from verify import verify_gui_dist
|
||||
print("\n=== Smoke test ===")
|
||||
verify_gui_dist(dist_dir)
|
||||
except Exception as exc:
|
||||
print(f"\n[WARNING] Smoke test failed: {exc}")
|
||||
|
||||
return dist_dir
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build_with_pyinstaller()
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Build a Nuitka-compiled orisys wheel and publish it to SDK/{version}/.
|
||||
|
||||
Pipeline:
|
||||
1. Compile src/orisys → .pyd
|
||||
2. Stage wheel tree (compiled module + bundled files from pyproject.toml)
|
||||
3. Run bdist_wheel and move output to SDK/{version}/
|
||||
4. Smoke-test imports in a temporary venv
|
||||
|
||||
Run from repo root: python build/build_nuitka.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
from nuitka_compile import compile_orisys, copy_compiled_orisys, install_nuitka
|
||||
from paths import NUITKA_COMPILE_DIR, NUITKA_WHEEL_DIR, REPO_ROOT, repo_path
|
||||
from pyproject_utils import bundle_items, extras_require, get_version, install_requires
|
||||
from verify import verify_built_wheel
|
||||
from wheel_build import publish_wheel_to_sdk, run_bdist_wheel, write_wheel_setup
|
||||
from wheel_layout import copy_bundle_items, package_data_from_bundle
|
||||
|
||||
|
||||
def _reset_dir(path) -> None:
|
||||
"""Remove and recreate a build output directory."""
|
||||
if path.exists():
|
||||
shutil.rmtree(path)
|
||||
path.mkdir(parents=True)
|
||||
|
||||
|
||||
def build() -> None:
|
||||
# --- 1. Compile ---
|
||||
install_nuitka()
|
||||
|
||||
version = get_version(REPO_ROOT)
|
||||
_reset_dir(NUITKA_COMPILE_DIR)
|
||||
_reset_dir(NUITKA_WHEEL_DIR)
|
||||
|
||||
print("\n=== Compiling with Nuitka ===")
|
||||
env = os.environ.copy()
|
||||
src_dir = repo_path("src")
|
||||
env["PYTHONPATH"] = src_dir.as_posix() + os.pathsep + env.get("PYTHONPATH", "")
|
||||
ext_file = compile_orisys(NUITKA_COMPILE_DIR, env)
|
||||
|
||||
# --- 2. Stage wheel contents ---
|
||||
print("\n=== Preparing Wheel Structure ===")
|
||||
pkg_dir = NUITKA_WHEEL_DIR / "wheel_root" / "orisys"
|
||||
pkg_dir.mkdir(parents=True)
|
||||
|
||||
ext_suffix = copy_compiled_orisys(pkg_dir, ext_file)
|
||||
|
||||
# Bundle rules live in pyproject.toml → [[tool.orisys.bundle.items]]
|
||||
bundle = bundle_items(REPO_ROOT)
|
||||
copied_counts = copy_bundle_items(REPO_ROOT, pkg_dir, bundle)
|
||||
print(f"Copied {copied_counts['configs']} JSON config files")
|
||||
print(f"Copied {copied_counts['examples']} example files to orisys/examples/")
|
||||
print(f"Copied {copied_counts['docs']} doc files to orisys/docs/")
|
||||
print(f"Copied {copied_counts['assets']} finger asset files to orisys/assets/")
|
||||
|
||||
write_wheel_setup(
|
||||
NUITKA_WHEEL_DIR,
|
||||
version=version,
|
||||
package_data=package_data_from_bundle(ext_suffix, bundle),
|
||||
install_requires=install_requires(REPO_ROOT),
|
||||
extras_require=extras_require(REPO_ROOT),
|
||||
)
|
||||
|
||||
# --- 3. Build wheel and publish to SDK/ ---
|
||||
wheel_path = run_bdist_wheel(NUITKA_WHEEL_DIR)
|
||||
if wheel_path is None:
|
||||
print("\n[ERROR] Wheel not found.")
|
||||
return
|
||||
|
||||
final_wheel = publish_wheel_to_sdk(wheel_path, version)
|
||||
print(f"\n[SUCCESS] Wheel created and moved to: {final_wheel}")
|
||||
|
||||
# # --- 4. Verify (isolated venv; does not touch the active environment) ---
|
||||
# print("\n=== Verifying finger mesh/viewer imports ===")
|
||||
# try:
|
||||
# verify_built_wheel(final_wheel)
|
||||
# except subprocess.CalledProcessError as exc:
|
||||
# print(f"\n[WARNING] Wheel verify failed: {exc}")
|
||||
# print("The wheel was built successfully; verification runs in an isolated temp venv.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -0,0 +1,70 @@
|
||||
@echo off
|
||||
REM Build Nuitka wheels for Python 3.10, 3.11, and 3.12.
|
||||
REM Expects conda envs named py310, py311, py312 (or set CONDA_ENVS_ROOT).
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
cd /d "%~dp0\.."
|
||||
if errorlevel 1 (
|
||||
echo ERROR: Could not change to repository root.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if not defined CONDA_ENVS_ROOT (
|
||||
if exist "%USERPROFILE%\anaconda3\envs" (
|
||||
set "CONDA_ENVS_ROOT=%USERPROFILE%\anaconda3\envs"
|
||||
) else if exist "%USERPROFILE%\miniconda3\envs" (
|
||||
set "CONDA_ENVS_ROOT=%USERPROFILE%\miniconda3\envs"
|
||||
) else if exist "%LOCALAPPDATA%\anaconda3\envs" (
|
||||
set "CONDA_ENVS_ROOT=%LOCALAPPDATA%\anaconda3\envs"
|
||||
)
|
||||
)
|
||||
|
||||
where conda >nul 2>&1
|
||||
set "HAS_CONDA=0"
|
||||
if not errorlevel 1 set "HAS_CONDA=1"
|
||||
|
||||
set FAILED=0
|
||||
for %%E in (py310 py311 py312) do (
|
||||
echo.
|
||||
echo ========================================
|
||||
echo Building orisys wheel: %%E
|
||||
echo ========================================
|
||||
|
||||
set "OK=0"
|
||||
if !HAS_CONDA! equ 1 (
|
||||
call conda run --no-capture-output -n %%E python build\build_nuitka.py
|
||||
if not errorlevel 1 set "OK=1"
|
||||
)
|
||||
|
||||
if !OK! equ 0 (
|
||||
if not defined CONDA_ENVS_ROOT (
|
||||
echo ERROR: Could not find conda env %%E. Set CONDA_ENVS_ROOT or add conda to PATH.
|
||||
) else (
|
||||
set "PYEXE=!CONDA_ENVS_ROOT!\%%E\python.exe"
|
||||
if not exist "!PYEXE!" (
|
||||
echo ERROR: Python not found: !PYEXE!
|
||||
) else (
|
||||
"!PYEXE!" build\build_nuitka.py
|
||||
if not errorlevel 1 set "OK=1"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if !OK! equ 0 set FAILED=1
|
||||
)
|
||||
|
||||
echo.
|
||||
if !FAILED! equ 1 (
|
||||
echo One or more wheel builds failed.
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo SUCCESS: all wheels built under SDK\
|
||||
for /f "delims=" %%V in ('dir /b /ad SDK 2^>nul') do (
|
||||
for %%W in ("SDK\%%V\orisys-*-cp310-*.whl" "SDK\%%V\orisys-*-cp311-*.whl" "SDK\%%V\orisys-*-cp312-*.whl") do (
|
||||
if exist %%W echo %%~nxW
|
||||
)
|
||||
)
|
||||
|
||||
endlocal
|
||||
exit /b 0
|
||||
@@ -0,0 +1,87 @@
|
||||
"""Nuitka compilation of the orisys package."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from importlib.machinery import EXTENSION_SUFFIXES
|
||||
from pathlib import Path
|
||||
|
||||
from paths import repo_path
|
||||
|
||||
NUITKA_INCLUDE_PACKAGES = (
|
||||
"orisys",
|
||||
"orisys.finger",
|
||||
"orisys.unroll",
|
||||
"orisys.hhd",
|
||||
"orisys.hhd.solvers",
|
||||
)
|
||||
|
||||
NUITKA_NOFOLLOW_IMPORTS = (
|
||||
"cupy",
|
||||
"cupyx",
|
||||
"pyopencl",
|
||||
"open3d",
|
||||
)
|
||||
|
||||
|
||||
def install_nuitka() -> None:
|
||||
print("Checking Nuitka installation...")
|
||||
try:
|
||||
import nuitka # noqa: F401
|
||||
print("Nuitka is installed.")
|
||||
except ImportError:
|
||||
print("Installing Nuitka...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, "-m", "pip", "install", "nuitka", "ordered-set", "zstandard"]
|
||||
)
|
||||
|
||||
|
||||
def find_compiled_extension(output_dir: Path, module_name: str) -> Path:
|
||||
candidates = []
|
||||
for suffix in EXTENSION_SUFFIXES:
|
||||
candidates.extend(output_dir.glob(f"{module_name}*{suffix}"))
|
||||
if not candidates:
|
||||
all_files = ", ".join(p.name for p in output_dir.iterdir()) if output_dir.exists() else "(none)"
|
||||
raise RuntimeError(
|
||||
f"Nuitka did not produce a compiled extension for {module_name}. Found: {all_files}"
|
||||
)
|
||||
return sorted(candidates, key=lambda p: len(p.name))[0]
|
||||
|
||||
|
||||
def compile_orisys(build_root: Path, env: dict[str, str]) -> Path:
|
||||
module_output_dir = build_root / "orisys"
|
||||
if module_output_dir.exists():
|
||||
shutil.rmtree(module_output_dir)
|
||||
module_output_dir.mkdir(parents=True)
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
"-m",
|
||||
"nuitka",
|
||||
"--module",
|
||||
"--output-dir=" + str(module_output_dir),
|
||||
"--remove-output",
|
||||
"--no-pyi-file",
|
||||
repo_path("src/orisys").as_posix(),
|
||||
]
|
||||
for package_name in NUITKA_INCLUDE_PACKAGES:
|
||||
cmd.append(f"--include-package={package_name}")
|
||||
for import_name in NUITKA_NOFOLLOW_IMPORTS:
|
||||
cmd.append(f"--nofollow-import-to={import_name}")
|
||||
|
||||
print(f"Running: {' '.join(cmd)}")
|
||||
subprocess.check_call(cmd, env=env)
|
||||
|
||||
ext_file = find_compiled_extension(module_output_dir, "orisys")
|
||||
print(f"Generated compiled extension for orisys: {ext_file}")
|
||||
return ext_file
|
||||
|
||||
|
||||
def copy_compiled_orisys(pkg_dir: Path, ext_file: Path) -> str:
|
||||
ext_suffix = "".join(ext_file.suffixes)
|
||||
dest_ext = pkg_dir / f"__init__{ext_suffix}"
|
||||
shutil.copy(ext_file, dest_ext)
|
||||
print(f"Copied compiled module orisys to {dest_ext}")
|
||||
return ext_suffix
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Repository and Nuitka build output paths."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
NUITKA_COMPILE_DIR = REPO_ROOT / "build" / "nuitka" / "compile"
|
||||
NUITKA_WHEEL_DIR = REPO_ROOT / "build" / "nuitka" / "wheel"
|
||||
NUITKA_GUI_DIR = REPO_ROOT / "build" / "nuitka" / "gui"
|
||||
GUI_DIST_DIR = REPO_ROOT / "dist" / "OrisysDashboard"
|
||||
|
||||
|
||||
def repo_path(*parts: str) -> Path:
|
||||
return REPO_ROOT.joinpath(*parts)
|
||||
|
||||
|
||||
def sdk_dir(version: str) -> Path:
|
||||
return REPO_ROOT / "SDK" / version
|
||||
@@ -0,0 +1,100 @@
|
||||
"""Read project metadata from pyproject.toml for packaging scripts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _load_toml(text: str) -> dict:
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
|
||||
return tomllib.loads(text)
|
||||
try:
|
||||
import tomli
|
||||
except ImportError:
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", "tomli", "-q"])
|
||||
import tomli
|
||||
return tomli.loads(text)
|
||||
|
||||
|
||||
def load_pyproject(repo_root: Path) -> dict:
|
||||
path = repo_root / "pyproject.toml"
|
||||
return _load_toml(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def project_metadata(repo_root: Path) -> dict:
|
||||
return load_pyproject(repo_root)["project"]
|
||||
|
||||
|
||||
def install_requires(repo_root: Path) -> list[str]:
|
||||
return list(project_metadata(repo_root)["dependencies"])
|
||||
|
||||
|
||||
def extras_require(repo_root: Path) -> dict[str, list[str]]:
|
||||
optional = project_metadata(repo_root).get("optional-dependencies", {})
|
||||
return {name: list(requirements) for name, requirements in optional.items()}
|
||||
|
||||
|
||||
def _resolve_version_attr(repo_root: Path, attr: str) -> str:
|
||||
"""Resolve a setuptools dynamic version attr (e.g. orisys.__version__)."""
|
||||
src_dir = str(repo_root / "src")
|
||||
if src_dir not in sys.path:
|
||||
sys.path.insert(0, src_dir)
|
||||
module_name, _, attr_name = attr.partition(".")
|
||||
if not attr_name:
|
||||
raise RuntimeError(f"Invalid dynamic version attr: {attr!r}")
|
||||
module = importlib.import_module(module_name)
|
||||
value = module
|
||||
for name in attr_name.split("."):
|
||||
value = getattr(value, name)
|
||||
return str(value)
|
||||
|
||||
|
||||
def get_version(repo_root: Path) -> str:
|
||||
"""Return project version from pyproject.toml (static or dynamic)."""
|
||||
project = project_metadata(repo_root)
|
||||
if "version" in project:
|
||||
return str(project["version"])
|
||||
|
||||
dynamic = load_pyproject(repo_root).get("tool", {}).get("setuptools", {}).get("dynamic", {})
|
||||
version_cfg = dynamic.get("version")
|
||||
if isinstance(version_cfg, dict) and "attr" in version_cfg:
|
||||
return _resolve_version_attr(repo_root, version_cfg["attr"])
|
||||
|
||||
raise RuntimeError("Project version is not defined in pyproject.toml")
|
||||
|
||||
|
||||
def bundle_items(repo_root: Path) -> list[dict]:
|
||||
"""Return bundle rules from [[tool.orisys.bundle.items]] in pyproject.toml."""
|
||||
items = load_pyproject(repo_root).get("tool", {}).get("orisys", {}).get("bundle", {}).get("items")
|
||||
if not items:
|
||||
raise RuntimeError("Missing [[tool.orisys.bundle.items]] in pyproject.toml")
|
||||
return list(items)
|
||||
|
||||
|
||||
def bundle_data_files(repo_root: Path) -> list[tuple[str, list[str]]]:
|
||||
"""Map bundle items marked dev_install=true to setuptools data_files entries."""
|
||||
from wheel_layout import resolve_bundle_source_files
|
||||
|
||||
grouped: dict[str, list[str]] = {}
|
||||
for item in bundle_items(repo_root):
|
||||
if not item.get("dev_install", False):
|
||||
continue
|
||||
|
||||
src_dir = repo_root / item["source"]
|
||||
base_dest = f"orisys/{item['target']}".replace("\\", "/")
|
||||
|
||||
for src_file in resolve_bundle_source_files(repo_root, item):
|
||||
rel_src = src_file.relative_to(repo_root).as_posix()
|
||||
if item.get("preserve_tree", False):
|
||||
rel_parent = src_file.relative_to(src_dir).parent.as_posix()
|
||||
dest = base_dest if rel_parent == "." else f"{base_dest}/{rel_parent}"
|
||||
else:
|
||||
dest = base_dest
|
||||
grouped.setdefault(dest, []).append(rel_src)
|
||||
|
||||
return [(dest, sorted(files)) for dest, files in sorted(grouped.items())]
|
||||
@@ -0,0 +1,82 @@
|
||||
"""Post-build smoke tests for packaged wheels and standalone GUI builds."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import venv
|
||||
from pathlib import Path
|
||||
|
||||
from paths import GUI_DIST_DIR
|
||||
|
||||
|
||||
def _venv_python(venv_dir: Path) -> Path:
|
||||
if sys.platform == "win32":
|
||||
return venv_dir / "Scripts" / "python.exe"
|
||||
return venv_dir / "bin" / "python"
|
||||
|
||||
|
||||
def verify_built_wheel(wheel_path: Path) -> None:
|
||||
"""Install the wheel in a temporary venv and smoke-test finger imports."""
|
||||
wheel_path = wheel_path.resolve()
|
||||
with tempfile.TemporaryDirectory(prefix="orisys-wheel-verify-") as tmp:
|
||||
venv_dir = Path(tmp) / "venv"
|
||||
venv.create(venv_dir, with_pip=True)
|
||||
python = _venv_python(venv_dir)
|
||||
|
||||
subprocess.check_call(
|
||||
[python, "-m", "pip", "install", "-q", str(wheel_path)],
|
||||
)
|
||||
subprocess.check_call(
|
||||
[
|
||||
python,
|
||||
"-c",
|
||||
(
|
||||
"from orisys.finger import UVMeshViewer, build_uv_surface_mesh, FingerRuntime; "
|
||||
"from orisys.finger.mesh import UVSurfaceMesh; "
|
||||
"from orisys.finger.viewer import init_uv_mesh_viewer; "
|
||||
"print('finger mesh/viewer imports OK')"
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def verify_gui_dist(dist_dir: Path | None = None) -> None:
|
||||
"""Smoke-test a standalone GUI folder produced by build_gui_win.py."""
|
||||
dist_dir = (dist_dir or GUI_DIST_DIR).resolve()
|
||||
exe = dist_dir / "OrisysDashboard.exe"
|
||||
if not exe.is_file():
|
||||
raise FileNotFoundError(f"GUI executable not found: {exe}")
|
||||
|
||||
required = [
|
||||
dist_dir / "config" / "dd02-ov.json",
|
||||
dist_dir / "assets" / "finger" / "visualization" / "world_to_cam.npy",
|
||||
dist_dir / "assets" / "finger" / "30fps" / "mesh.npz",
|
||||
]
|
||||
missing = [str(path.relative_to(dist_dir)) for path in required if not path.is_file()]
|
||||
if missing:
|
||||
raise FileNotFoundError(f"Missing bundled files in {dist_dir}: {', '.join(missing)}")
|
||||
|
||||
subprocess.check_call([str(exe), "--help"], cwd=dist_dir)
|
||||
print(f"GUI dist smoke test OK: {exe}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="Verify packaged Orisys artifacts.")
|
||||
parser.add_argument("--gui-dist", action="store_true", help="Verify dist/OrisysDashboard/")
|
||||
parser.add_argument("--dist-dir", type=Path, default=None, help="Override GUI dist folder")
|
||||
parser.add_argument("wheel", nargs="?", type=Path, help="Wheel path for import smoke test")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.gui_dist:
|
||||
verify_gui_dist(args.dist_dir)
|
||||
return
|
||||
if args.wheel is None:
|
||||
parser.error("Provide a wheel path or pass --gui-dist")
|
||||
verify_built_wheel(args.wheel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
"""Assemble and build the Nuitka wheel with setuptools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from paths import sdk_dir
|
||||
|
||||
|
||||
def write_wheel_setup(
|
||||
wheel_dir: Path,
|
||||
*,
|
||||
version: str,
|
||||
package_data: dict[str, list[str]],
|
||||
install_requires: list[str],
|
||||
extras_require: dict[str, list[str]],
|
||||
) -> Path:
|
||||
setup_py_path = wheel_dir / "setup.py"
|
||||
setup_content = f"""
|
||||
from setuptools import setup, Distribution
|
||||
|
||||
class BinaryDistribution(Distribution):
|
||||
def has_ext_modules(self):
|
||||
return True
|
||||
|
||||
setup(
|
||||
name="orisys",
|
||||
version="{version}",
|
||||
description="Orisys - Optical Tactile Sensor SDK (Nuitka Compiled)",
|
||||
author="Orisys",
|
||||
packages=['orisys', 'orisys.examples'],
|
||||
package_dir={{'': 'wheel_root'}},
|
||||
package_data={package_data},
|
||||
include_package_data=True,
|
||||
install_requires={install_requires!r},
|
||||
extras_require={extras_require!r},
|
||||
distclass=BinaryDistribution,
|
||||
zip_safe=False,
|
||||
)
|
||||
"""
|
||||
setup_py_path.write_text(setup_content, encoding="utf-8")
|
||||
return setup_py_path
|
||||
|
||||
|
||||
def run_bdist_wheel(wheel_dir: Path) -> Path | None:
|
||||
print("\n=== Building Wheel ===")
|
||||
subprocess.check_call([sys.executable, "setup.py", "bdist_wheel"], cwd=wheel_dir)
|
||||
wheels = sorted((wheel_dir / "dist").glob("*.whl"))
|
||||
return wheels[0] if wheels else None
|
||||
|
||||
|
||||
def publish_wheel_to_sdk(wheel_path: Path, version: str) -> Path:
|
||||
sdk_version_dir = sdk_dir(version)
|
||||
sdk_version_dir.mkdir(parents=True, exist_ok=True)
|
||||
dest_wheel = sdk_version_dir / wheel_path.name
|
||||
if dest_wheel.exists():
|
||||
dest_wheel.unlink()
|
||||
shutil.move(str(wheel_path), str(dest_wheel))
|
||||
return dest_wheel
|
||||
@@ -0,0 +1,91 @@
|
||||
"""Copy bundled non-code files into the Nuitka wheel tree."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def resolve_bundle_source_files(repo_root: Path, item: dict) -> list[Path]:
|
||||
"""Resolve explicit files and/or glob patterns for one bundle item."""
|
||||
src_dir = repo_root / item["source"]
|
||||
patterns = item.get("patterns", [])
|
||||
files = item.get("files", [])
|
||||
if not patterns and not files:
|
||||
raise ValueError(f"Bundle item {item['name']!r} requires patterns and/or files")
|
||||
|
||||
seen: set[Path] = set()
|
||||
resolved: list[Path] = []
|
||||
|
||||
def add(path: Path) -> None:
|
||||
key = path.resolve()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
resolved.append(path)
|
||||
|
||||
for pattern in patterns:
|
||||
for path in sorted(src_dir.glob(pattern)):
|
||||
if path.is_file():
|
||||
add(path)
|
||||
|
||||
for name in files:
|
||||
path = src_dir / name
|
||||
if not path.is_file():
|
||||
raise FileNotFoundError(f"Bundle file not found for {item['name']}: {path}")
|
||||
add(path)
|
||||
|
||||
return resolved
|
||||
|
||||
|
||||
def package_data_paths_for_item(item: dict) -> list[str]:
|
||||
"""Return setuptools package_data paths relative to the orisys package."""
|
||||
target = str(item["target"]).replace("\\", "/")
|
||||
entries = [f"{target}/{pattern}" for pattern in item.get("patterns", [])]
|
||||
entries.extend(f"{target}/{name}" for name in item.get("files", []))
|
||||
return entries
|
||||
|
||||
|
||||
def data_file_entries_for_item(item: dict) -> list[str]:
|
||||
"""Return setuptools data_files paths relative to the repo root."""
|
||||
source = str(item["source"]).replace("\\", "/")
|
||||
entries = [f"{source}/{pattern}" for pattern in item.get("patterns", [])]
|
||||
entries.extend(f"{source}/{name}" for name in item.get("files", []))
|
||||
return entries
|
||||
|
||||
|
||||
def copy_bundle_items(repo_root: Path, pkg_dir: Path, items: list[dict]) -> dict[str, int]:
|
||||
copied_counts: dict[str, int] = {}
|
||||
for item in items:
|
||||
name = item["name"]
|
||||
src_dir = repo_root / item["source"]
|
||||
dst_dir = pkg_dir / item["target"]
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
copied = 0
|
||||
for src_file in resolve_bundle_source_files(repo_root, item):
|
||||
if item.get("preserve_tree", False):
|
||||
dst_file = dst_dir / src_file.relative_to(src_dir)
|
||||
dst_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
else:
|
||||
dst_file = dst_dir / src_file.name
|
||||
shutil.copy(src_file, dst_file)
|
||||
copied += 1
|
||||
print(f"Copied {name}: {src_file}")
|
||||
|
||||
if name == "examples":
|
||||
(dst_dir / "__init__.py").write_text("# Examples for orisys package\n", encoding="utf-8")
|
||||
|
||||
copied_counts[name] = copied
|
||||
return copied_counts
|
||||
|
||||
|
||||
def package_data_from_bundle(ext_suffix: str, items: list[dict]) -> dict[str, list[str]]:
|
||||
package_data: dict[str, list[str]] = {
|
||||
"orisys": [f"*{ext_suffix}"],
|
||||
"orisys.examples": ["*.py", "README.md", "app_paths.py", "viz/**/*.py", "pipeline/**/*.py", "gui/**/*.py"],
|
||||
}
|
||||
for item in items:
|
||||
if item["name"] == "examples":
|
||||
continue
|
||||
package_data["orisys"].extend(package_data_paths_for_item(item))
|
||||
return package_data
|
||||
@@ -102,12 +102,16 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.settings_panel.set_video_source(str(self.config.video))
|
||||
self.settings_panel.set_config_path(self.config.config)
|
||||
self.settings_panel.set_calibration_path(self.config.calib or "")
|
||||
self.settings_panel.set_finger_export_dir(self.config.export_dir)
|
||||
|
||||
self.settings_panel.log_visibility_changed.connect(self._set_log_panel_visible)
|
||||
self.settings_panel.free_rotation_changed.connect(self.mesh_viewport.set_free_rotation)
|
||||
if self.arrow_panel is not None:
|
||||
self.settings_panel.arrow_background_changed.connect(self.arrow_panel.set_background)
|
||||
self.settings_panel.view_reset_requested.connect(self.mesh_viewport.reset_view)
|
||||
self.settings_panel.browse_video_requested.connect(self._select_camera)
|
||||
self.settings_panel.browse_config_requested.connect(self._select_config)
|
||||
self.settings_panel.browse_finger_assets_requested.connect(self._select_finger_assets)
|
||||
self.settings_panel.btn_play_pause.clicked.connect(self._on_play_pause_clicked)
|
||||
self.settings_panel.btn_reset_tracker.clicked.connect(self._on_reset)
|
||||
self.settings_panel.set_play_pause_state("run")
|
||||
@@ -212,12 +216,14 @@ class FingerDashboardWindow(QMainWindow):
|
||||
if self._running:
|
||||
return
|
||||
self._set_play_pause_button("starting")
|
||||
self.settings_panel.set_finger_export_dir_enabled(False)
|
||||
QApplication.processEvents()
|
||||
|
||||
self.config.video = self.settings_panel.video_source()
|
||||
self.config.config = self.settings_panel.config_path()
|
||||
cal = self.settings_panel.calibration_path()
|
||||
self.config.calib = cal or None
|
||||
self.config.export_dir = self.settings_panel.finger_export_dir()
|
||||
|
||||
try:
|
||||
self.pipeline = FingerPipeline(self.config)
|
||||
@@ -228,6 +234,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
except Exception as exc:
|
||||
self.mesh_viewport.close_renderer()
|
||||
self._set_play_pause_button("run")
|
||||
self.settings_panel.set_finger_export_dir_enabled(True)
|
||||
print(f"启动失败: {exc}")
|
||||
return
|
||||
|
||||
@@ -239,7 +246,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
self.settings_panel.btn_reset_tracker.setEnabled(True)
|
||||
print(
|
||||
f"▶ 启动 — 摄像机={self.config.video}, 配置={self.config.config}, "
|
||||
f"标定={self.config.calib or '(none)'}"
|
||||
f"标定={self.config.calib or '(none)'}, 资产={self.config.export_dir}"
|
||||
)
|
||||
|
||||
def _on_stop(self) -> None:
|
||||
@@ -256,6 +263,7 @@ class FingerDashboardWindow(QMainWindow):
|
||||
|
||||
self._set_play_pause_button("run")
|
||||
self.settings_panel.btn_reset_tracker.setEnabled(False)
|
||||
self.settings_panel.set_finger_export_dir_enabled(True)
|
||||
|
||||
def _on_reset(self) -> None:
|
||||
if not self._running:
|
||||
@@ -353,6 +361,15 @@ class FingerDashboardWindow(QMainWindow):
|
||||
if path:
|
||||
self.settings_panel.set_config_path(path)
|
||||
|
||||
def _select_finger_assets(self) -> None:
|
||||
path = QFileDialog.getExistingDirectory(
|
||||
self,
|
||||
"选择 finger 资产目录",
|
||||
self.settings_panel.finger_export_dir() or "assets/finger",
|
||||
)
|
||||
if path:
|
||||
self.settings_panel.set_finger_export_dir(path)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
key = event.key()
|
||||
if key == Qt.Key_Escape:
|
||||
|
||||
@@ -85,7 +85,7 @@ class PanelSetup:
|
||||
TOP_ROW = PanelRow(
|
||||
slots=(
|
||||
PanelSlot("mesh", "3D mesh", width_px=600, height_px=600),
|
||||
PanelSlot("arrows", "UV Unrolled", width_px=500, height_px=500, enabled=False),
|
||||
PanelSlot("arrows", "UV Unrolled", width_px=500, height_px=500, enabled=True),
|
||||
PanelSlot("charts", "Force charts", width_px=400, height_px=500),
|
||||
PanelSlot("settings", "Settings", width_px=200, height_px=0),
|
||||
),
|
||||
@@ -223,7 +223,7 @@ class DashboardLayout:
|
||||
panel_setup: PanelSetup = PANEL_SETUP
|
||||
|
||||
window_title: str = "Orisys Finger Dashboard"
|
||||
window_width: int = 1100
|
||||
window_width: int = 1600
|
||||
window_height: int = 620
|
||||
|
||||
root_margin_px: int = 6
|
||||
@@ -241,8 +241,8 @@ class DashboardLayout:
|
||||
|
||||
# UV arrow panel: min flow magnitude to draw an arrow (display only; not dd02-ov.json contact.flow_threshold)
|
||||
arrow_flow_threshold: float = 1.0
|
||||
# "unrolled" = sensor texture; "white" = solid canvas (tune colors via arrow_canvas_colors)
|
||||
arrow_background: ArrowBackground = "white"
|
||||
# "unrolled" = sensor texture; "pure_color" = solid canvas (tune colors via arrow_canvas_colors)
|
||||
arrow_background: ArrowBackground = "unrolled"
|
||||
arrow_canvas_colors: ArrowCanvasColors = ARROW_CANVAS_COLORS
|
||||
|
||||
mesh_lighting: MeshLighting = MESH_LIGHTING
|
||||
|
||||
@@ -38,6 +38,9 @@ class ArrowPanelWidget(ImagePanel):
|
||||
def set_flow_threshold(self, threshold: float) -> None:
|
||||
self.flow_threshold = threshold
|
||||
|
||||
def set_background(self, background: ArrowBackground) -> None:
|
||||
self.background = background
|
||||
|
||||
def update_frame(self, frame: FingerFrameView) -> None:
|
||||
arrows = render_uv_arrows(
|
||||
frame,
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
from PyQt5.QtCore import QSize, Qt, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
@@ -14,6 +15,9 @@ from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
from viz.render_2d import ArrowBackground
|
||||
|
||||
from ..layout import DashboardLayout, LAYOUT
|
||||
from ..mesh_view_state import MeshViewState
|
||||
from ..transport_icons import reset_icon, transport_icon
|
||||
@@ -32,9 +36,11 @@ class SettingsPanelWidget(QWidget):
|
||||
|
||||
log_visibility_changed = pyqtSignal(bool)
|
||||
free_rotation_changed = pyqtSignal(bool)
|
||||
arrow_background_changed = pyqtSignal(str)
|
||||
view_reset_requested = pyqtSignal()
|
||||
browse_video_requested = pyqtSignal()
|
||||
browse_config_requested = pyqtSignal()
|
||||
browse_finger_assets_requested = pyqtSignal()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -83,6 +89,17 @@ class SettingsPanelWidget(QWidget):
|
||||
self.show_flow_arrows_check.toggled.connect(self._mesh_state.set_flow_arrows_enabled)
|
||||
root.addWidget(self.show_flow_arrows_check)
|
||||
|
||||
root.addWidget(self._caption("UV arrow background"))
|
||||
self.arrow_background_combo = QComboBox()
|
||||
for label, value in (
|
||||
("Unrolled", "unrolled"),
|
||||
("Pure color", "pure_color"),
|
||||
):
|
||||
self.arrow_background_combo.addItem(label, value)
|
||||
self.set_arrow_background(layout.arrow_background)
|
||||
self.arrow_background_combo.currentIndexChanged.connect(self._emit_arrow_background_changed)
|
||||
root.addWidget(self.arrow_background_combo)
|
||||
|
||||
root.addWidget(self._caption("Video source"))
|
||||
video_row = QHBoxLayout()
|
||||
video_row.setSpacing(6)
|
||||
@@ -113,6 +130,19 @@ class SettingsPanelWidget(QWidget):
|
||||
root.addWidget(self._caption("Calibration"))
|
||||
root.addWidget(self.cal_edit)
|
||||
|
||||
root.addWidget(self._caption("Finger assets"))
|
||||
finger_assets_row = QHBoxLayout()
|
||||
finger_assets_row.setSpacing(6)
|
||||
self.finger_assets_edit = QLineEdit(DEFAULT_FINGER_EXPORT_DIR)
|
||||
self.finger_assets_edit.setPlaceholderText("Blender export bundle directory")
|
||||
finger_assets_row.addWidget(self.finger_assets_edit, 1)
|
||||
self.btn_browse_finger_assets = QPushButton("…")
|
||||
self.btn_browse_finger_assets.setFixedWidth(32)
|
||||
self.btn_browse_finger_assets.setToolTip("Browse finger asset directory")
|
||||
self.btn_browse_finger_assets.clicked.connect(self.browse_finger_assets_requested.emit)
|
||||
finger_assets_row.addWidget(self.btn_browse_finger_assets)
|
||||
root.addLayout(finger_assets_row)
|
||||
|
||||
root.addSpacing(8)
|
||||
rotate_row = QHBoxLayout()
|
||||
rotate_row.setSpacing(6)
|
||||
@@ -174,6 +204,30 @@ class SettingsPanelWidget(QWidget):
|
||||
def set_calibration_path(self, value: str) -> None:
|
||||
self.cal_edit.setText(value or "")
|
||||
|
||||
def set_finger_export_dir(self, value: str) -> None:
|
||||
self.finger_assets_edit.setText(value)
|
||||
|
||||
def set_finger_export_dir_enabled(self, enabled: bool) -> None:
|
||||
self.finger_assets_edit.setEnabled(enabled)
|
||||
self.btn_browse_finger_assets.setEnabled(enabled)
|
||||
|
||||
def _emit_arrow_background_changed(self, _index: int) -> None:
|
||||
value = self.arrow_background_combo.currentData()
|
||||
if value:
|
||||
self.arrow_background_changed.emit(value)
|
||||
|
||||
def set_arrow_background(self, value: ArrowBackground) -> None:
|
||||
idx = self.arrow_background_combo.findData(value)
|
||||
if idx < 0:
|
||||
return
|
||||
self.arrow_background_combo.blockSignals(True)
|
||||
self.arrow_background_combo.setCurrentIndex(idx)
|
||||
self.arrow_background_combo.blockSignals(False)
|
||||
|
||||
def arrow_background(self) -> ArrowBackground:
|
||||
value = self.arrow_background_combo.currentData()
|
||||
return value if value else "unrolled"
|
||||
|
||||
def video_source(self) -> str:
|
||||
return self.video_edit.text().strip()
|
||||
|
||||
@@ -182,3 +236,6 @@ class SettingsPanelWidget(QWidget):
|
||||
|
||||
def calibration_path(self) -> str:
|
||||
return self.cal_edit.text().strip()
|
||||
|
||||
def finger_export_dir(self) -> str:
|
||||
return self.finger_assets_edit.text().strip()
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from orisys.finger import DEFAULT_FINGER_EXPORT_DIR
|
||||
from viz.mesh_camera import DEFAULT_FINGER_VIZ_POSE
|
||||
|
||||
|
||||
@@ -18,6 +19,7 @@ class PipelineConfig:
|
||||
video: str = "0"
|
||||
config: str = "./config/dd02-ov.json"
|
||||
calib: str | None = None
|
||||
export_dir: str = DEFAULT_FINGER_EXPORT_DIR
|
||||
verbose: bool = False
|
||||
mesh_map: str = "depth"
|
||||
threshold: float | None = None
|
||||
|
||||
@@ -55,6 +55,7 @@ class FingerPipeline:
|
||||
if self.runtime is not None:
|
||||
return
|
||||
self.runtime = FingerRuntime.open(
|
||||
export_dir=self.config.export_dir,
|
||||
config=self.config.config,
|
||||
calib=self.config.calib,
|
||||
video=self.config.video,
|
||||
|
||||
@@ -12,12 +12,12 @@ import orisys
|
||||
if TYPE_CHECKING:
|
||||
from pipeline.frame import FingerFrameView
|
||||
|
||||
ArrowBackground = Literal["unrolled", "white"]
|
||||
ArrowBackground = Literal["unrolled", "pure_color"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ArrowCanvasColors:
|
||||
"""BGR fill colors for synthetic UV arrow background (``background='white'``)."""
|
||||
"""BGR fill colors for synthetic UV arrow background (``background='pure_color'``)."""
|
||||
|
||||
outside_bgr: tuple[int, int, int] = (0, 0, 0)
|
||||
uv_bgr: tuple[int, int, int] = (255, 255, 255)
|
||||
@@ -67,6 +67,8 @@ def _arrow_background(
|
||||
"""Return the BGR canvas for UV arrow drawing."""
|
||||
if background == "unrolled":
|
||||
return frame.unrolled
|
||||
if background != "pure_color":
|
||||
raise ValueError(f"unknown arrow background: {background!r}")
|
||||
h, w = frame.unrolled.shape[:2]
|
||||
outside = np.array(_as_bgr(canvas_colors.outside_bgr), dtype=np.uint8)
|
||||
uv = np.array(_as_bgr(canvas_colors.uv_bgr), dtype=np.uint8)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user