diff --git a/README.md b/README.md index e21d31b..a30b9d2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ and several runnable examples for camera and video-based workflows. - `src/orisys/`: installable SDK package - `config/`: default and lab/runtime JSON configurations -- `examples/`: user-facing example scripts +- `examples/`: product demos (`products/ory_box`, `ory_mini`, `ory_finger`) plus shared `gui/`, `pipeline/`, `viz/` - `scripts/`: maintenance, calibration, packaging, and demo scripts - `tools/`: non-package tooling such as patch-generation utilities - `tests/`: automated pytest suite @@ -49,18 +49,36 @@ pytest ### 4. Run an example +ORY-BOX: + ```bash -python examples/basic_pipeline.py -v 0 -c ./config/ddjx01.json +python examples/products/ory_box/basic_pipeline.py -v 0 -c ddjx01 ``` +ORY-MINI: + +```bash +python examples/products/ory_mini/basic_pipeline.py -v 0 -c mini +``` + +ORY-FINGER: + +```bash +python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 +``` + +Compatibility wrappers remain at `examples/basic_pipeline.py` and +`examples/finger_pipeline*.py`. + ## Configuration The canonical configuration files now live in the repo-level `config/` folder. The package loader `orisys.configs` resolves configs from: 1. an explicit JSON file path -2. a config file stem under `config/` -3. a backward-compatible JSON `"name"` match, such as `"prototype"` +2. a config file stem under `config/` (for example `ddjx01`, `mini`, `dd02`) +3. a product alias such as `dd02` -> `dd02-ov` +4. a backward-compatible JSON `"name"` match, such as `"prototype"` Examples: diff --git a/build/build_gui_win_pyinstaller.py b/build/build_gui_win_pyinstaller.py index b336c1e..cc53708 100644 --- a/build/build_gui_win_pyinstaller.py +++ b/build/build_gui_win_pyinstaller.py @@ -18,7 +18,7 @@ import sys from pathlib import Path # ---------- Configuration ---------- -ENTRY_SCRIPT = Path("examples/finger_pipeline_3dview.py").resolve() +ENTRY_SCRIPT = Path("examples/products/ory_finger/finger_pipeline_3dview.py").resolve() EXE_NAME = "OrisysDashboard.exe" DIST_NAME = "OrisysDashboard" # final folder name repo_root = Path(__file__).parent.parent diff --git a/config/ddjx01_wuxi.json b/config/box.json similarity index 66% rename from config/ddjx01_wuxi.json rename to config/box.json index cbbe434..af88413 100644 --- a/config/ddjx01_wuxi.json +++ b/config/box.json @@ -2,24 +2,24 @@ "name": "prototype", "version": "1.0", "camera": { - "resolution_raw": [1280, 720], - "fps": 15, + "resolution_raw": [1920, 1080], + "fps": 30, "units": 15, "units_used": 9, - "resolution_stitch": [263, 263], + "resolution_stitch": [365, 365], "stitch_res_list": [365,263,123,63] }, "stitcher": { "min_area_frac": 0.01, - "max_area_frac": 0.04, - "max_aspect_ratio": 25, + "max_area_frac": 0.05, + "max_aspect_ratio": 50, "index": [1,2,3,6,7,8,11,12,13], "margin": 10, - "nominal_ov_pos": 67, + "nominal_ov_pos": 100, "n": [3,3], "row_tol": 0.6, - "box_size": [180, 180], - "blend":"numpy", + "box_size": [250, 250], + "blend":"numba", "blend_list": ["numpy", "numba","none"] }, "tracker": { @@ -31,16 +31,15 @@ "dis_preset": "medium", "dis_mode": 3, "algorithm_list": ["raft", "dis"], - "hhd_downsample": 3, - "backend": "cpu" + "hhd_downsample": 3 }, "contact": { "min_contour_area": 3000, - "flow_threshold":1, - "div_threshold":0.02, + "flow_threshold":5, + "div_threshold":0.05, "expansion_threshold": 0.01, "kernel_size": [5,5], - "denoise_sigma": 10 + "denoise_sigma": 12 } } diff --git a/config/finger.json b/config/finger.json new file mode 100644 index 0000000..545a74a --- /dev/null +++ b/config/finger.json @@ -0,0 +1,35 @@ +{ + "name": "dd02-ov", + "type": "single_camera", + "version": "1.0", + "camera": { + "resolution_raw": [1280, 720], + "fps": 30, + "resolution_stitch": [420, 450], + "source_scale": 0.4, + "old":[672, 728, 1,0.5], + "work": [504, 546,1], + "work2": [420, 450,0.8], + "work3": [350, 375,4,0.7], + "work4": [280, 300,4,0.4,0.2] + }, + "tracker": { + "adaptive": true, + "lower_threshold": 12.0, + "upper_threshold": 18.0, + "loop_closure_interval": 1, + "algorithm": "dis", + "dis_preset": "medium", + "dis_mode": 3, + "algorithm_list": ["raft", "dis"], + "hhd_downsample": 5 + }, + "contact": { + "min_contour_area": 3000, + "flow_threshold": 0.2, + "div_threshold": 0.022, + "expansion_threshold": 0.01, + "kernel_size": [5, 5], + "denoise_sigma": 12 + } +} diff --git a/docs/api.md b/docs/api.md index ed626f0..78f26e1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -238,7 +238,7 @@ flow = sensor.read_info(sensor.info.VRAW) ## `orisys.finger` 手指 UV pipeline -`examples/finger_pipeline.py` 展示了一个更适合手指/单相机场景的高层 API: +`examples/products/ory_finger/finger_pipeline.py` 展示了一个更适合手指/单相机场景的高层 API: 1. 使用 `FingerRuntime.open()` 打开视频源、加载 Blender 导出的资产,并构建 UV 展开映射。 2. 用 `grab_and_process()` 或 `read_source() + process_frame()` 获取一帧展开后的触觉图像。 @@ -262,7 +262,7 @@ flow = sensor.read_info(sensor.info.VRAW) #### 输入参数 * **export_dir** (`str`,可选): 手指导出资产目录,默认值来自 `orisys.finger.DEFAULT_FINGER_EXPORT_DIR`。目录下通常需要包含 `mesh.npz`、`camera_intrinsics.npz`、`world_to_cam.npy`。 -* **config** (`str`,可选): Orisys 配置文件路径,默认 `./config/dd02-ov.json`。 +* **config** (`str`,可选): Orisys 配置文件名称或路径,默认 `dd02`(兼容解析到 `dd02-ov`)。 * **map_cache** (`str`,可选): UV 展开缓存文件路径,默认值来自 `orisys.finger.DEFAULT_FINGER_MAP_CACHE`。 * **source_scale** (`float`,可选): 输入相机分辨率缩放比例;未提供时根据配置自动推断。 * **calib** (`str | None`,可选): 外部相机标定文件路径;未提供时优先使用导出资产中的内参。 @@ -282,7 +282,7 @@ flow = sensor.read_info(sensor.info.VRAW) from orisys.finger import FingerRuntime runtime = FingerRuntime.open( - config="./config/dd02-ov.json", + config="dd02", video=0, verbose=True, ) @@ -312,7 +312,7 @@ runtime = FingerRuntime.open( #### 描述 -对一帧原始输入执行源图预处理、UV 展开,并把展开后的结果送入内部 `TactileProcessor`。这是 `examples/finger_pipeline.py` 中 profile 模式使用的方法。 +对一帧原始输入执行源图预处理、UV 展开,并把展开后的结果送入内部 `TactileProcessor`。这是 `examples/products/ory_finger/finger_pipeline.py` 中 profile 模式使用的方法。 #### 输入参数 @@ -327,7 +327,7 @@ runtime = FingerRuntime.open( #### 描述 -先读取一帧视频源,再调用 `process_frame()` 完成 UV 展开并推入内部触觉处理器。非 profile 模式下,`examples/finger_pipeline.py` 使用该方法作为主循环入口。 +先读取一帧视频源,再调用 `process_frame()` 完成 UV 展开并推入内部触觉处理器。非 profile 模式下,`examples/products/ory_finger/finger_pipeline.py` 使用该方法作为主循环入口。 #### 输入参数 @@ -357,7 +357,7 @@ runtime = FingerRuntime.open( #### 描述 -先执行一次采集与 UV 展开,然后调用 `runtime.tactile.warmup()` 预热内部光流/HHD 相关缓存。`examples/finger_pipeline.py` 在进入主循环前会先执行一次 warmup。 +先执行一次采集与 UV 展开,然后调用 `runtime.tactile.warmup()` 预热内部光流/HHD 相关缓存。`examples/products/ory_finger/finger_pipeline.py` 在进入主循环前会先执行一次 warmup。 #### 输入参数 @@ -386,7 +386,7 @@ runtime = FingerRuntime.open( * **raw_sensor**: 底层 `Sensor` 对象。 * **info**: 与 `Sensor.info` 相同的枚举入口。 * **img / flow / vnormal / vshear / depth_map**: 当前帧处理结果。 -* **mask_stats**: 有效 UV 区域统计信息;`examples/finger_pipeline.py` 会读取 `mask_stats["coverage"]` 输出当前掩码覆盖率。 +* **mask_stats**: 有效 UV 区域统计信息;`examples/products/ory_finger/finger_pipeline.py` 会读取 `mask_stats["coverage"]` 输出当前掩码覆盖率。 * **valid_mask**: 当前有效区域掩码。 #### 说明 @@ -395,7 +395,7 @@ runtime = FingerRuntime.open( ### 10. 最小手指 pipeline 示例 -下面的流程与 `examples/finger_pipeline.py` 一致: +下面的流程与 `examples/products/ory_finger/finger_pipeline.py` 一致: ```python import time @@ -404,7 +404,7 @@ import orisys from orisys.finger import FingerRuntime runtime = FingerRuntime.open( - config="./config/dd02-ov.json", + config="dd02", video=0, verbose=True, ) @@ -462,7 +462,7 @@ finally: ### 11. 关于 profile 模式 -`examples/finger_pipeline.py` 中的 `--profile` / `--profile-every` 是示例脚本参数,不属于 `orisys` SDK 核心 API。本质上它只是把主循环拆成: +`examples/products/ory_finger/finger_pipeline.py` 中的 `--profile` / `--profile-every` 是示例脚本参数,不属于 `orisys` SDK 核心 API。本质上它只是把主循环拆成: 1. `runtime.read_source()` 2. `runtime.process_frame(...)` diff --git a/docs/get_started.md b/docs/get_started.md index 1459600..daaa7cd 100644 --- a/docs/get_started.md +++ b/docs/get_started.md @@ -112,13 +112,16 @@ print(orisys.__version__) # 使用方法 ## 运行示例程序 -套件里面包含了多个例子,建议都在仓库根目录运行(即 `config/`、`examples/`、`assets/` 同级目录)。常见入口分为两类: +套件里面包含了多个例子,建议都在仓库根目录运行(即 `config/`、`examples/`、`assets/` 同级目录)。按产品入口如下: -1. `examples/basic_pipeline.py`:复眼/拼接传感器基础 OpenCV 示例 -2. `examples/finger_pipeline.py`:手指传感器2维显示最小流程示例 -3. `examples/finger_pipeline_3dview.py`:手指传感器 Qt 3D 可视化界面 +1. `examples/products/ory_box/basic_pipeline.py`:ORY-BOX 复眼/拼接传感器基础 OpenCV 示例(默认配置 `ddjx01`) +2. `examples/products/ory_mini/basic_pipeline.py`:ORY-MINI 基础 OpenCV 示例(默认配置 `mini`) +3. `examples/products/ory_finger/finger_pipeline.py`:ORY-FINGER 2D UV 最小流程(默认配置 `dd02`) +4. `examples/products/ory_finger/finger_pipeline_3dview.py`:ORY-FINGER Qt 3D 可视化界面(默认配置 `dd02`) -下面先介绍最基础的 `basic_pipeline.py` 运行方式。 +`dd02` 是当前 ORY-FINGER 的稳定别名,实际会解析到 `dd02-ov`。旧路径 `examples/basic_pipeline.py` / `examples/finger_pipeline*.py` 仍可作为兼容入口。 + +下面先介绍最基础的 ORY-BOX 运行方式。 ### Windows: 1. 检查是否有其他正在连接的USB摄像头设备:打开powershell, 运行命令`Get-PnpDevice -PresentOnly | Where-Object { $_.Class -match '^Camera' }`,如果没有的话不会有输出。 @@ -126,7 +129,7 @@ print(orisys.__version__) 3. 如果第一步没有输出,传感器的ID就是“0”,如果第一步有一个设备,传感器的ID就是“1”,如此类推。 4. 在确定了传感器的ID(下方例子假设为“0”)后,打开Anaconda Prompt, 切换到工作路径,然后在命令行中输入: ``` bash - python examples/basic_pipeline.py -v 0 + python examples/products/ory_box/basic_pipeline.py -v 0 -c ddjx01 ``` 5. 程序会运行,并自动打开可视化窗口和打印资料,在窗口上按键盘”q”键可以停止运行,按”r”键可以重设数据 @@ -136,22 +139,22 @@ print(orisys.__version__) 3. 根据新增的输出,例如”/dev/video0”,确定传感器的ID为“0”, 如果是”/dev/video3”的话ID为“3” ,如此类推。 4. 在确定了传感器的ID(下方例子假设为“0”)后,打开终端, 激活相应的python环境,切换到工作路径,然后在命令行中输入: ``` bash - python examples/basic_pipeline.py -v 0 + python examples/products/ory_box/basic_pipeline.py -v 0 -c ddjx01 ``` 5. 程序会运行,并自动打开可视化窗口和打印资料,在窗口上按键盘”q”键可以停止运行,按”r”键可以重设数据 -### 手指传感器 UV 示例 +### ORY-FINGER UV 示例 -如果您使用的是手指/单相机 UV 展开流程,可运行: +如果您使用的是 ORY-FINGER(当前配置别名 `dd02`),可运行: ``` bash -python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json +python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 ``` 如果输入来自视频文件,也可以改用: ``` bash -python examples/finger_pipeline.py --video ./data/sample.mp4 --config ./config/dd02-ov.json +python examples/products/ory_finger/finger_pipeline.py --video ./data/sample.mp4 --config dd02 ``` 该示例会: @@ -168,10 +171,10 @@ python examples/finger_pipeline.py --video ./data/sample.mp4 --config ./config/d 例如: ``` bash -python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json +python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 ``` -### 手指 3D 可视化界面 +### ORY-FINGER 3D 可视化界面 如果要使用 GUI ,安装GUI依赖: ``` bash @@ -180,7 +183,7 @@ pip install pyqt5 pyqtgraph ``` 然后运行: ``` bash -python examples/finger_pipeline_3dview.py --video 0 --config ./config/dd02-ov.json +python examples/products/ory_finger/finger_pipeline_3dview.py --video 0 --config dd02 ``` 该界面会提供: diff --git a/examples/README.md b/examples/README.md index 00ac50f..913c9f1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,54 +1,66 @@ # Examples -Top-level demo scripts under `examples/`. Run them from the repository root. +Product-facing demos live under `examples/products/`. Shared helpers stay in +`pipeline/`, `viz/`, and `gui/`. Run commands from the repository root. -## Top-Level Files +## Product Entry Points -- `README.md` — this overview of the root-level example entry points -- `basic_pipeline.py` — basic single-sensor OpenCV demo for the compound-eye pipeline -- `plot_data.py` — single-sensor demo with OpenCV views plus PyQtGraph force charts -- `two_sensor_pipeline.py` — two-sensor multiprocessing demo for parallel acquisition / display -- `finger_pipeline.py` — minimal finger UV-unroll pipeline with optional profiling -- `finger_pipeline_3dview.py` — main finger dashboard entry point with the unified Qt UI +| Product | Entry | Default config | +|---------|-------|----------------| +| ORY-BOX | `products/ory_box/basic_pipeline.py` | `ddjx01` | +| ORY-MINI | `products/ory_mini/basic_pipeline.py` | `mini` | +| ORY-FINGER (2D) | `products/ory_finger/finger_pipeline.py` | `dd02` | +| ORY-FINGER (3D Qt) | `products/ory_finger/finger_pipeline_3dview.py` | `dd02` | + +`dd02` is the stable ORY-FINGER alias and currently resolves to `dd02-ov`. + +Compatibility wrappers still exist at the `examples/` root +(`basic_pipeline.py`, `finger_pipeline.py`, `finger_pipeline_3dview.py`) and +forward to the product entries above. ## Typical Usage -For the Qt dashboard: +ORY-BOX: + +```bash +python examples/products/ory_box/basic_pipeline.py -v 0 -c ddjx01 +``` + +ORY-MINI: + +```bash +python examples/products/ory_mini/basic_pipeline.py -v 0 -c mini +``` + +ORY-FINGER 2D: + +```bash +python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 +``` + +ORY-FINGER video file: + +```bash +python examples/products/ory_finger/finger_pipeline.py --video ./data/sample.mp4 --config dd02 +``` + +ORY-FINGER 3D Qt dashboard: ```bash pip install -e ".[gui]" -python examples/finger_pipeline_3dview.py --video 0 --config ./config/dd02-ov.json +python examples/products/ory_finger/finger_pipeline_3dview.py --video 0 --config dd02 ``` -For the minimal finger pipeline: +## Other Root Examples -```bash -python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json -``` - -For the minimal finger pipeline with profiling: - -```bash -python examples/finger_pipeline.py --camera_id 0 --config ./config/dd02-ov.json --profile --profile-every 30 -``` - -For a video-file finger run: - -```bash -python examples/finger_pipeline.py --video ./data/sample.mp4 --config ./config/dd02-ov.json -``` - -For the basic compound-eye pipeline: - -```bash -python examples/basic_pipeline.py -v 0 -c ./config/ddjx01.json -``` +- `plot_data.py` — single-sensor OpenCV views plus PyQtGraph force charts +- `two_sensor_pipeline.py` — two-sensor multiprocessing demo ## Requirements - SDK installed from the repository root, for example `pip install -e .` - camera index or test video path -- matching config files under `./config/` +- matching config names under `./config/` (or packaged `orisys/configs/`) Qt / PyQtGraph examples additionally require: @@ -58,8 +70,10 @@ pip install -e ".[gui]" ## Notes -- `finger_pipeline_3dview.py` is the main interactive demo. It uses `gui/app_paths.py` so the same entry point works in editable and packaged runs. -- Dashboard controls: click **启动** to start acquisition; use **Record CSV / Stop CSV** and the CSV save-directory browser to log contact/force data; `Q` quits, `R` resets, `S` starts video recording, `E` stops video recording, and `Esc` toggles fullscreen. -- `finger_pipeline.py` is the leanest finger example if you want to validate UV unroll + force/contact processing without the full dashboard. -- `finger_pipeline.py` uses `orisys.finger.FingerRuntime` as the high-level entry point, then runs tactile analysis through `runtime.tactile`. -- In `--profile` mode, the example splits the loop into capture, UV unroll, deformation, contact, and display stages so you can measure each step separately. +- `products/ory_finger/finger_pipeline_3dview.py` is the main interactive finger demo. + It uses `gui/app_paths.py` so the same entry works in editable and packaged runs. +- Dashboard controls: click **启动** to start acquisition; use **Record CSV / Stop CSV** + and the CSV save-directory browser to log contact/force data; `Q` quits, `R` resets, + `S` starts video recording, `E` stops video recording, and `Esc` toggles fullscreen. +- `products/ory_finger/finger_pipeline.py` is the leanest finger example if you want to + validate UV unroll + force/contact processing without the full dashboard. diff --git a/examples/basic_pipeline.py b/examples/basic_pipeline.py index 6541d29..13983a2 100644 --- a/examples/basic_pipeline.py +++ b/examples/basic_pipeline.py @@ -1,115 +1,21 @@ +#!/usr/bin/env python3 +"""Compatibility wrapper for ORY-BOX. + +Prefer: + python examples/products/ory_box/basic_pipeline.py -v 0 -c ddjx01 """ -示例 1:单传感器基础流程 -本示例演示 Orisys SDK 的标准处理流程: -1. 初始化传感器(摄像头或视频文件) -2. 获取图像并计算形变 -3. 按需计算接触区域 -4. 读取并可视化结果 +from __future__ import annotations -SDK version: 0.4.2 -""" -import os -os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" -import cv2 -import orisys -import numpy as np -import argparse +import sys +from pathlib import Path -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径") - parser.add_argument("--config","-c", type=str, default="./config/ddjx01.json", help="配置文件名称或路径") - parser.add_argument("--verbose","-verbose", type=bool, default=True, help="是否输出详细日志") - args = parser.parse_args() +_EXAMPLES_DIR = Path(__file__).resolve().parent +if str(_EXAMPLES_DIR) not in sys.path: + sys.path.insert(0, str(_EXAMPLES_DIR)) - # 步骤 1:创建传感器对象(必需) - # config_name 可以是内置配置名称,也可以是自定义配置文件路径 - # cal_path 为标定文件路径;每台设备建议使用独立的标定文件 - - # 输入既可以是摄像头编号,也可以是视频文件路径 - try: - input = int(args.video) - is_camera = True - sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose) - except: - is_camera = False - sensor = orisys.Sensor(args.video, config_name=args.config, verbose=args.verbose) - - print("\n按键说明:'q' 退出程序,'r' 重置追踪器。") - - while True: - # 步骤 2:数据处理 - # 步骤 2.1:获取并拼接图像(必需) - sensor.get_img() - # 步骤 2.2:计算形变场(必需);返回值表示当前帧是否检测到接触/运动 - is_contact_now = sensor.compute_deformation(check_motion=True, threshold=0) - # 步骤 2.3:仅在当前帧检测到接触/运动时计算接触区域 - # if is_contact_now: - sensor.compute_contact() - - - # 步骤 2.4:读取结果 - fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = sensor.read_info( - sensor.info.FPS, - sensor.info.VRAW, - sensor.info.VNORMAL, - sensor.info.IMG, - sensor.info.CONTOUR, - sensor.info.CENTROID, - sensor.info.DEPTH, - sensor.info.FNORMAL, - sensor.info.FSHEARX, - sensor.info.FSHEARY - ) - - # 步骤 3:可视化显示 - # 步骤 3.1:显示形变矢量场 - arrows = orisys.util.draw_arrows( - img, - flow, - threshold=2, - grid_spacing=20, - arrow_scale=1.0 - ) - cv2.imshow("形变矢量场", arrows) - - # 步骤 3.2:显示接触区域和质心 - image_with_foe = orisys.util.draw_contact(img, contour, centroid) - cv2.imshow("接触区域与质心", image_with_foe) - - # 步骤 3.3:显示深度图 - # 将深度值归一化到 0-255 范围 - div_abs = depth_map - if div_abs.max() > div_abs.min(): - div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8) - else: - div_normalized = np.zeros_like(div_abs, dtype=np.uint8) - - # 应用伪彩色映射 - cv2.imshow("深度图", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET)) - - # 步骤 3.4:打印关键结果 - print( - f"FPS={fps:.2f}, 法向力={fnormal:.4f}, 切向力X={fshearx:.4f}, " - f"切向力Y={fsheary:.4f}, 深度图尺寸={depth_map.shape}, 原始分辨率={sensor.img_size_raw}" - ) - - # 步骤 3.5:键盘控制 - key = cv2.waitKey(1) & 0xFF - if key == ord("q"): - print("\n正在退出示例程序...") - break - if key == ord("r"): - # 当光流追踪出现漂移时,可手动重置追踪器 - sensor.reset() - print("追踪器已重置。") - - # ========================================================================= - # 4. 释放资源 - # ========================================================================= - sensor.disconnect() # 释放视频源及相关资源 +from products.ory_box.basic_pipeline import main -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/finger_pipeline.py b/examples/finger_pipeline.py index deeaddf..836531d 100644 --- a/examples/finger_pipeline.py +++ b/examples/finger_pipeline.py @@ -1,108 +1,20 @@ #!/usr/bin/env python3 -""" -Minimum UV-unroll finger tactile pipeline. +"""Compatibility wrapper for ORY-FINGER 2D pipeline. -Offline: load Blender export -> build_or_load_domain -> unroll_map_uv.npz -Runtime: camera frame -> FrameUnwarper -> TactileProcessor -> forces/contact - -For the full demo (UV debug panels, dashboard, benchmarks) see: - tools/3d_patch_generation_blender/unroll_realtime.py +Prefer: + python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 """ -import argparse -import os +from __future__ import annotations + import sys +from pathlib import Path -os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0") +_EXAMPLES_DIR = Path(__file__).resolve().parent +if str(_EXAMPLES_DIR) not in sys.path: + sys.path.insert(0, str(_EXAMPLES_DIR)) -import cv2 -import orisys -from orisys.finger import FingerRuntime - - -def main(): - parser = argparse.ArgumentParser(description="Minimum UV-unroll finger tactile pipeline") - parser.add_argument("--camera_id", type=int, default=0) - parser.add_argument("--video", help="Video file path (overrides camera)") - parser.add_argument("--config", "-c", default="./config/dd02-ov.json", help="Orisys SDK config") - parser.add_argument("--show", action="store_true", default=True, help="Show unrolled image window") - parser.add_argument("--verbose", action="store_true", help="SDK verbose logs") - args = parser.parse_args() - - try: - video = args.camera_id - if args.video: - video = args.video - runtime = FingerRuntime.open( - config=args.config, - video=video, - verbose=args.verbose, - ) - except RuntimeError as exc: - print(str(exc), file=sys.stderr) - sys.exit(1) - - if args.verbose: - geometry = runtime.geometry - print( - f"UV geometry: native={geometry.native_width}x{geometry.native_height}, " - f"source={geometry.source_width}x{geometry.source_height} " - f"output={geometry.out_width}x{geometry.out_height} " - ) - - runtime.warmup(color=False) - tactile = runtime.tactile - - print("Press 'q' to quit, 'r' to reset tracker.") - try: - while True: - ok, unrolled = runtime.grab_and_process(color=False) - if not ok: - break - - tactile.compute_deformation(check_motion=True, threshold=0) - tactile.compute_contact() - - fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = tactile.read_info( - tactile.info.FPS, - tactile.info.VRAW, - tactile.info.VNORMAL, - tactile.info.IMG, - tactile.info.CONTOUR, - tactile.info.CENTROID, - tactile.info.DEPTH, - tactile.info.FNORMAL, - tactile.info.FSHEARX, - tactile.info.FSHEARY, - ) - - status = ( - f"FPS={fps:.2f} normal={fnormal:.2f} shear=({fshearx:.2f}, {fsheary:.2f}) " - f"mask={tactile.mask_stats['coverage']:.2f}" - ) - - if args.show: - arrows = orisys.util.draw_arrows( - unrolled, - flow, - threshold=0.4, - grid_spacing=10, - arrow_scale=1.0, - ) - cv2.imshow("UV Unrolled", arrows) - - print(status, end="\r") - - key = cv2.waitKey(1) & 0xFF - if key == ord("q"): - print("\nExiting.") - break - if key == ord("r"): - tactile.reset() - print("\nTracker reset.") - finally: - runtime.close() - cv2.destroyAllWindows() +from products.ory_finger.finger_pipeline import main if __name__ == "__main__": diff --git a/examples/finger_pipeline_3dview.py b/examples/finger_pipeline_3dview.py index ca0362d..04a8d86 100644 --- a/examples/finger_pipeline_3dview.py +++ b/examples/finger_pipeline_3dview.py @@ -1,75 +1,20 @@ #!/usr/bin/env python3 -""" -UV-unroll finger tactile pipeline with unified Qt dashboard. +"""Compatibility wrapper for ORY-FINGER 3D Qt dashboard. -Where to customize the GUI: - Layout (sizes, proportions) -> examples/gui/layout.py <-- edit here - Colors / QSS -> examples/gui/styles.py - Widget rendering logic -> examples/gui/widgets/ - Window behavior -> examples/gui/dashboard.py +Prefer: + python examples/products/ory_finger/finger_pipeline_3dview.py --video 0 --config dd02 """ -import argparse -import os +from __future__ import annotations + import sys +from pathlib import Path -os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0") +_EXAMPLES_DIR = Path(__file__).resolve().parent +if str(_EXAMPLES_DIR) not in sys.path: + sys.path.insert(0, str(_EXAMPLES_DIR)) -from gui.app_paths import configure_frozen_runtime, default_config_path, ensure_examples_import_path - -configure_frozen_runtime() -ensure_examples_import_path() - -from gui import run_dashboard -from pipeline import PipelineConfig -from viz import DEFAULT_FINGER_VIZ_POSE - - -def _parse_args(): - parser = argparse.ArgumentParser( - description="UV-unroll finger tactile pipeline with unified Qt dashboard" - ) - parser.add_argument("--video", "-v", default="0", help="Camera index or video file path") - parser.add_argument("--config", "-c", default=default_config_path(), help="Orisys SDK config") - parser.add_argument("--cal", default="", help="Optional calibration file path") - parser.add_argument("--verbose", action="store_true", help="SDK verbose logs") - parser.add_argument( - "--mesh-map", - choices=["flow", "depth"], - default="depth", - help="Color the 3D mesh from flow magnitude or contact depth.", - ) - parser.add_argument("--threshold", type=float, default=None, help="Mesh/arrow threshold") - parser.add_argument("--no-3d", action="store_true", help="Disable 3D mesh view") - parser.add_argument( - "--viz-pose", - default=DEFAULT_FINGER_VIZ_POSE, - help="world_to_cam.npy for the default view; pass 'none' to skip", - ) - parser.add_argument( - "--cutoff", - type=float, - default=5.0, - help="Low-pass cutoff for force curves (Hz)", - ) - parser.add_argument( - "--profile", - action="store_true", - help="Print per-stage timing to the log panel / console", - ) - parser.add_argument( - "--profile-every", - type=int, - default=30, - help="When --profile is set, print rolling averages every N frames", - ) - return parser.parse_args() - - -def main(): - args = _parse_args() - config = PipelineConfig.from_args(args) - sys.exit(run_dashboard(config)) +from products.ory_finger.finger_pipeline_3dview import main if __name__ == "__main__": diff --git a/examples/gui/app_paths.py b/examples/gui/app_paths.py index e227e3b..998640a 100644 --- a/examples/gui/app_paths.py +++ b/examples/gui/app_paths.py @@ -27,7 +27,8 @@ def resource_path(*parts: str) -> Path: def default_config_path() -> str: - return str(resource_path("config", "dd02-ov.json")) + """Default ORY-FINGER config name (resolves to dd02-ov via alias).""" + return "dd02" def configure_frozen_runtime() -> None: diff --git a/examples/gui/viewer3d/widgets/settings_panel.py b/examples/gui/viewer3d/widgets/settings_panel.py index 539a5a8..92ccb3f 100644 --- a/examples/gui/viewer3d/widgets/settings_panel.py +++ b/examples/gui/viewer3d/widgets/settings_panel.py @@ -137,7 +137,7 @@ class SettingsPanelWidget(QWidget): root.addWidget(self._caption("Config")) config_row = QHBoxLayout() config_row.setSpacing(6) - self.config_edit = QLineEdit("./config/dd02-ov.json") + self.config_edit = QLineEdit("dd02") config_row.addWidget(self.config_edit, 1) self.btn_browse_config = QPushButton("…") self.btn_browse_config.setFixedWidth(32) diff --git a/examples/pipeline/config.py b/examples/pipeline/config.py index a199319..a11a75e 100644 --- a/examples/pipeline/config.py +++ b/examples/pipeline/config.py @@ -17,7 +17,7 @@ def mesh_threshold(mesh_map: str, threshold: float | None) -> float: @dataclass class PipelineConfig: video: str = "0" - config: str = "./config/dd02-ov.json" + config: str = "dd02" calib: str | None = None export_dir: str = DEFAULT_FINGER_EXPORT_DIR csv_save_dir: str = "./outputs/csv" diff --git a/examples/products/__init__.py b/examples/products/__init__.py new file mode 100644 index 0000000..f8e6c8d --- /dev/null +++ b/examples/products/__init__.py @@ -0,0 +1 @@ +"""Product-facing example entry points for ORY-BOX / ORY-MINI / ORY-FINGER.""" diff --git a/examples/products/ory_box/basic_pipeline.py b/examples/products/ory_box/basic_pipeline.py new file mode 100644 index 0000000..d6c8b82 --- /dev/null +++ b/examples/products/ory_box/basic_pipeline.py @@ -0,0 +1,115 @@ +""" +ORY-BOX:单传感器基础流程 + +本示例演示 Orisys SDK 的标准处理流程: +1. 初始化传感器(摄像头或视频文件) +2. 获取图像并计算形变 +3. 按需计算接触区域 +4. 读取并可视化结果 + +SDK version: 0.4.2 +""" +import os +os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" +import cv2 +import orisys +import numpy as np +import argparse + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径") + parser.add_argument("--config","-c", type=str, default="ddjx01", help="配置文件名称或路径") + parser.add_argument("--verbose","-verbose", type=bool, default=True, help="是否输出详细日志") + args = parser.parse_args() + + # 步骤 1:创建传感器对象(必需) + # config_name 可以是内置配置名称,也可以是自定义配置文件路径 + # cal_path 为标定文件路径;每台设备建议使用独立的标定文件 + + # 输入既可以是摄像头编号,也可以是视频文件路径 + try: + input = int(args.video) + is_camera = True + sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose) + except: + is_camera = False + sensor = orisys.Sensor(args.video, config_name=args.config, verbose=args.verbose) + + print("\n按键说明:'q' 退出程序,'r' 重置追踪器。") + + while True: + # 步骤 2:数据处理 + # 步骤 2.1:获取并拼接图像(必需) + sensor.get_img() + # 步骤 2.2:计算形变场(必需);返回值表示当前帧是否检测到接触/运动 + is_contact_now = sensor.compute_deformation(check_motion=True, threshold=0) + # 步骤 2.3:仅在当前帧检测到接触/运动时计算接触区域 + # if is_contact_now: + sensor.compute_contact() + + + # 步骤 2.4:读取结果 + fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = sensor.read_info( + sensor.info.FPS, + sensor.info.VRAW, + sensor.info.VNORMAL, + sensor.info.IMG, + sensor.info.CONTOUR, + sensor.info.CENTROID, + sensor.info.DEPTH, + sensor.info.FNORMAL, + sensor.info.FSHEARX, + sensor.info.FSHEARY + ) + + # 步骤 3:可视化显示 + # 步骤 3.1:显示形变矢量场 + arrows = orisys.util.draw_arrows( + img, + flow, + threshold=2, + grid_spacing=20, + arrow_scale=1.0 + ) + cv2.imshow("形变矢量场", arrows) + + # 步骤 3.2:显示接触区域和质心 + image_with_foe = orisys.util.draw_contact(img, contour, centroid) + cv2.imshow("接触区域与质心", image_with_foe) + + # 步骤 3.3:显示深度图 + # 将深度值归一化到 0-255 范围 + div_abs = depth_map + if div_abs.max() > div_abs.min(): + div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8) + else: + div_normalized = np.zeros_like(div_abs, dtype=np.uint8) + + # 应用伪彩色映射 + cv2.imshow("深度图", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET)) + + # 步骤 3.4:打印关键结果 + print( + f"FPS={fps:.2f}, 法向力={fnormal:.4f}, 切向力X={fshearx:.4f}, " + f"切向力Y={fsheary:.4f}, 深度图尺寸={depth_map.shape}, 原始分辨率={sensor.img_size_raw}" + ) + + # 步骤 3.5:键盘控制 + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + print("\n正在退出示例程序...") + break + if key == ord("r"): + # 当光流追踪出现漂移时,可手动重置追踪器 + sensor.reset() + print("追踪器已重置。") + + # ========================================================================= + # 4. 释放资源 + # ========================================================================= + sensor.disconnect() # 释放视频源及相关资源 + + +if __name__ == '__main__': + main() diff --git a/examples/products/ory_finger/finger_pipeline.py b/examples/products/ory_finger/finger_pipeline.py new file mode 100644 index 0000000..6aba015 --- /dev/null +++ b/examples/products/ory_finger/finger_pipeline.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +ORY-FINGER:Minimum UV-unroll finger tactile pipeline. + +Offline: load Blender export -> build_or_load_domain -> unroll_map_uv.npz +Runtime: camera frame -> FrameUnwarper -> TactileProcessor -> forces/contact + +Recommended entry: + python examples/products/ory_finger/finger_pipeline.py --camera_id 0 --config dd02 + +Current config alias: ``dd02`` resolves to ``dd02-ov`` for compatibility. +""" + +import argparse +import os +import sys + +os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0") + +import cv2 +import orisys +from orisys.finger import FingerRuntime + + +def main(): + parser = argparse.ArgumentParser(description="Minimum UV-unroll finger tactile pipeline") + parser.add_argument("--camera_id", type=int, default=0) + parser.add_argument("--video", help="Video file path (overrides camera)") + parser.add_argument("--config", "-c", default="dd02", help="Orisys SDK config name or path") + parser.add_argument("--show", action="store_true", default=True, help="Show unrolled image window") + parser.add_argument("--verbose", action="store_true", help="SDK verbose logs") + args = parser.parse_args() + + try: + video = args.camera_id + if args.video: + video = args.video + runtime = FingerRuntime.open( + config=args.config, + video=video, + verbose=args.verbose, + ) + except RuntimeError as exc: + print(str(exc), file=sys.stderr) + sys.exit(1) + + if args.verbose: + geometry = runtime.geometry + print( + f"UV geometry: native={geometry.native_width}x{geometry.native_height}, " + f"source={geometry.source_width}x{geometry.source_height} " + f"output={geometry.out_width}x{geometry.out_height} " + ) + + runtime.warmup(color=False) + tactile = runtime.tactile + + print("Press 'q' to quit, 'r' to reset tracker.") + try: + while True: + ok, unrolled = runtime.grab_and_process(color=False) + if not ok: + break + + tactile.compute_deformation(check_motion=True, threshold=0) + tactile.compute_contact() + + fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = tactile.read_info( + tactile.info.FPS, + tactile.info.VRAW, + tactile.info.VNORMAL, + tactile.info.IMG, + tactile.info.CONTOUR, + tactile.info.CENTROID, + tactile.info.DEPTH, + tactile.info.FNORMAL, + tactile.info.FSHEARX, + tactile.info.FSHEARY, + ) + + status = ( + f"FPS={fps:.2f} normal={fnormal:.2f} shear=({fshearx:.2f}, {fsheary:.2f}) " + f"mask={tactile.mask_stats['coverage']:.2f}" + ) + + if args.show: + arrows = orisys.util.draw_arrows( + unrolled, + flow, + threshold=0.4, + grid_spacing=10, + arrow_scale=1.0, + ) + cv2.imshow("UV Unrolled", arrows) + + print(status, end="\r") + + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + print("\nExiting.") + break + if key == ord("r"): + tactile.reset() + print("\nTracker reset.") + finally: + runtime.close() + cv2.destroyAllWindows() + + +if __name__ == "__main__": + main() diff --git a/examples/products/ory_finger/finger_pipeline_3dview.py b/examples/products/ory_finger/finger_pipeline_3dview.py new file mode 100644 index 0000000..d9e1eb3 --- /dev/null +++ b/examples/products/ory_finger/finger_pipeline_3dview.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +""" +ORY-FINGER:UV-unroll finger tactile pipeline with unified Qt dashboard. + +Where to customize the GUI: + Layout (sizes, proportions) -> examples/gui/viewer3d/layout.py + Colors / QSS -> examples/gui/viewer3d/styles.py + Widget rendering logic -> examples/gui/viewer3d/widgets/ + Window behavior -> examples/gui/viewer3d/dashboard.py + +Recommended entry: + python examples/products/ory_finger/finger_pipeline_3dview.py --video 0 --config dd02 +""" + +import argparse +import os +import sys +from pathlib import Path + +os.environ.setdefault("OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS", "0") + +# Make shared packages importable when launched as a file path. +_EXAMPLES_DIR = Path(__file__).resolve().parents[2] +if str(_EXAMPLES_DIR) not in sys.path: + sys.path.insert(0, str(_EXAMPLES_DIR)) + +from gui.app_paths import configure_frozen_runtime, default_config_path, ensure_examples_import_path + +configure_frozen_runtime() +ensure_examples_import_path() + +from gui import run_dashboard +from pipeline import PipelineConfig +from viz import DEFAULT_FINGER_VIZ_POSE + + +def _parse_args(): + parser = argparse.ArgumentParser( + description="UV-unroll finger tactile pipeline with unified Qt dashboard" + ) + parser.add_argument("--video", "-v", default="0", help="Camera index or video file path") + parser.add_argument("--config", "-c", default=default_config_path(), help="Orisys SDK config") + parser.add_argument("--cal", default="", help="Optional calibration file path") + parser.add_argument("--verbose", action="store_true", help="SDK verbose logs") + parser.add_argument( + "--mesh-map", + choices=["flow", "depth"], + default="depth", + help="Color the 3D mesh from flow magnitude or contact depth.", + ) + parser.add_argument("--threshold", type=float, default=None, help="Mesh/arrow threshold") + parser.add_argument("--no-3d", action="store_true", help="Disable 3D mesh view") + parser.add_argument( + "--viz-pose", + default=DEFAULT_FINGER_VIZ_POSE, + help="world_to_cam.npy for the default view; pass 'none' to skip", + ) + parser.add_argument( + "--cutoff", + type=float, + default=5.0, + help="Low-pass cutoff for force curves (Hz)", + ) + parser.add_argument( + "--profile", + action="store_true", + help="Print per-stage timing to the log panel / console", + ) + parser.add_argument( + "--profile-every", + type=int, + default=30, + help="When --profile is set, print rolling averages every N frames", + ) + return parser.parse_args() + + +def main(): + args = _parse_args() + config = PipelineConfig.from_args(args) + sys.exit(run_dashboard(config)) + + +if __name__ == "__main__": + main() diff --git a/examples/products/ory_mini/basic_pipeline.py b/examples/products/ory_mini/basic_pipeline.py new file mode 100644 index 0000000..e33b5b3 --- /dev/null +++ b/examples/products/ory_mini/basic_pipeline.py @@ -0,0 +1,115 @@ +""" +ORY-MINI:单传感器基础流程 + +本示例演示 Orisys SDK 的标准处理流程: +1. 初始化传感器(摄像头或视频文件) +2. 获取图像并计算形变 +3. 按需计算接触区域 +4. 读取并可视化结果 + +SDK version: 0.4.2 +""" +import os +os.environ["OPENCV_VIDEOIO_MSMF_ENABLE_HW_TRANSFORMS"] = "0" +import cv2 +import orisys +import numpy as np +import argparse + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--video","-v", type=str, default="0", help="视频源:摄像头编号或视频文件路径") + parser.add_argument("--config","-c", type=str, default="mini", help="配置文件名称或路径") + parser.add_argument("--verbose","-verbose", type=bool, default=True, help="是否输出详细日志") + args = parser.parse_args() + + # 步骤 1:创建传感器对象(必需) + # config_name 可以是内置配置名称,也可以是自定义配置文件路径 + # cal_path 为标定文件路径;每台设备建议使用独立的标定文件 + + # 输入既可以是摄像头编号,也可以是视频文件路径 + try: + input = int(args.video) + is_camera = True + sensor = orisys.Sensor(input, config_name=args.config, verbose=args.verbose) + except: + is_camera = False + sensor = orisys.Sensor(args.video, config_name=args.config, verbose=args.verbose) + + print("\n按键说明:'q' 退出程序,'r' 重置追踪器。") + + while True: + # 步骤 2:数据处理 + # 步骤 2.1:获取并拼接图像(必需) + sensor.get_img() + # 步骤 2.2:计算形变场(必需);返回值表示当前帧是否检测到接触/运动 + is_contact_now = sensor.compute_deformation(check_motion=True, threshold=0) + # 步骤 2.3:仅在当前帧检测到接触/运动时计算接触区域 + # if is_contact_now: + sensor.compute_contact() + + + # 步骤 2.4:读取结果 + fps, flow, vnormal, img, contour, centroid, depth_map, fnormal, fshearx, fsheary = sensor.read_info( + sensor.info.FPS, + sensor.info.VRAW, + sensor.info.VNORMAL, + sensor.info.IMG, + sensor.info.CONTOUR, + sensor.info.CENTROID, + sensor.info.DEPTH, + sensor.info.FNORMAL, + sensor.info.FSHEARX, + sensor.info.FSHEARY + ) + + # 步骤 3:可视化显示 + # 步骤 3.1:显示形变矢量场 + arrows = orisys.util.draw_arrows( + img, + flow, + threshold=2, + grid_spacing=20, + arrow_scale=1.0 + ) + cv2.imshow("形变矢量场", arrows) + + # 步骤 3.2:显示接触区域和质心 + image_with_foe = orisys.util.draw_contact(img, contour, centroid) + cv2.imshow("接触区域与质心", image_with_foe) + + # 步骤 3.3:显示深度图 + # 将深度值归一化到 0-255 范围 + div_abs = depth_map + if div_abs.max() > div_abs.min(): + div_normalized = ((div_abs - div_abs.min()) / (div_abs.max() - div_abs.min()) * 255).astype(np.uint8) + else: + div_normalized = np.zeros_like(div_abs, dtype=np.uint8) + + # 应用伪彩色映射 + cv2.imshow("深度图", cv2.applyColorMap(div_normalized, cv2.COLORMAP_JET)) + + # 步骤 3.4:打印关键结果 + print( + f"FPS={fps:.2f}, 法向力={fnormal:.4f}, 切向力X={fshearx:.4f}, " + f"切向力Y={fsheary:.4f}, 深度图尺寸={depth_map.shape}, 原始分辨率={sensor.img_size_raw}" + ) + + # 步骤 3.5:键盘控制 + key = cv2.waitKey(1) & 0xFF + if key == ord("q"): + print("\n正在退出示例程序...") + break + if key == ord("r"): + # 当光流追踪出现漂移时,可手动重置追踪器 + sensor.reset() + print("追踪器已重置。") + + # ========================================================================= + # 4. 释放资源 + # ========================================================================= + sensor.disconnect() # 释放视频源及相关资源 + + +if __name__ == '__main__': + main() diff --git a/pyproject.toml b/pyproject.toml index 226ad5f..f0a26d1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,25 +4,44 @@ build-backend = "setuptools.build_meta" [project] name = "orisys" -description = "Orisys - Vision Based Tactile Sensor SDK" +description = "SDK for Orisys Technology Visuotactile Sensors" readme = "README.md" requires-python = ">=3.10" -authors = [{ name = "Orisys" }] +authors = [{ name = "Orisys-Tech" }] +maintainers = [ + {name = "Yijian Zhang", email = "zhangyijian@orisys-tech.cn"} +] dynamic = ["version"] dependencies = [ "numpy>=1.24.4", "opencv-python>=4.10.0.84", "scipy>=1.10.1", - "open3d>=0.17.0", + "numba>=0.59.0", +] +classifiers = [ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + "Development Status :: 4 - Beta", + + # Indicate who your project is intended for + "Intended Audience :: Developers", + "Topic :: Software Development :: SDL", + + # Specify the Python versions you support here. + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Private :: Do Not Upload", ] [project.optional-dependencies] cuda12 = ["cupy-cuda12x>=12.3.0"] cuda13 = ["cupy-cuda13x>=13.0.0"] +gui = ["open3d>=0.17.0","pyqt5>=5.15", "pyqtgraph>=0.13"] # Backward compatibility: 'cuda' defaults to CUDA 12.x cuda = ["cupy-cuda12x>=12.3.0"] -gui = ["pyqt5>=5.15", "pyqtgraph>=0.13"] -numba = ["numba>=0.59.0"] [tool.setuptools] package-dir = { "" = "src" } @@ -52,7 +71,7 @@ dev_install = true name = "examples" source = "examples" target = "examples" -patterns = ["*.py", "README.md", "viz/**/*.py", "pipeline/**/*.py", "gui/**/*.py"] +patterns = ["*.py", "README.md", "products/**/*.py", "viz/**/*.py", "pipeline/**/*.py", "gui/**/*.py"] preserve_tree = true [[tool.orisys.bundle.items]] diff --git a/scripts/qt_viewer_finger.py b/scripts/qt_viewer_finger.py index 7165a35..bbe8d55 100644 --- a/scripts/qt_viewer_finger.py +++ b/scripts/qt_viewer_finger.py @@ -22,7 +22,7 @@ def main(): parser = argparse.ArgumentParser(description="Orisys finger Qt dashboard") parser.add_argument("--video", "-v", type=str, default="0") - parser.add_argument("--config", "-c", type=str, default="./config/dd02-ov.json") + parser.add_argument("--config", "-c", type=str, default="dd02") parser.add_argument("--cal", type=str, default="") parser.add_argument("--verbose", action="store_true") parser.add_argument("--cutoff", type=float, default=10.0) diff --git a/src/orisys/configs.py b/src/orisys/configs.py index ebdfad8..c3249f7 100644 --- a/src/orisys/configs.py +++ b/src/orisys/configs.py @@ -1,6 +1,12 @@ import json from pathlib import Path +# Product-facing names that map to on-disk config stems. +# ORY-FINGER currently ships as dd02-ov.json; keep "dd02" as the stable alias. +CONFIG_ALIASES = { + "dd02": "dd02-ov", +} + def _candidate_config_dirs() -> list[Path]: """ @@ -62,9 +68,16 @@ def load_config(config_name_or_path): if config_path.is_absolute(): raise FileNotFoundError(f"Config file not found at absolute path: {config_path}") - direct = CONFIG_DIR / f"{config_name_or_path}.json" - if direct.exists(): - return _load_json(direct) + requested = str(config_name_or_path) + candidates = [requested] + alias = CONFIG_ALIASES.get(requested) + if alias and alias not in candidates: + candidates.append(alias) + + for name in candidates: + direct = CONFIG_DIR / f"{name}.json" + if direct.exists(): + return _load_json(direct) # Backward-compatible alias resolution: support old names via JSON "name" field. for candidate in _config_files(): @@ -72,7 +85,7 @@ def load_config(config_name_or_path): data = _load_json(candidate) except Exception: continue - if data.get("name") == config_name_or_path: + if data.get("name") in candidates: return data available = [f.stem for f in _config_files()] @@ -80,7 +93,7 @@ def load_config(config_name_or_path): raise FileNotFoundError( f"Input config file not found with name or path: {config_name_or_path}\n" f" Tried as file path: {config_path} (not found)\n" - f" Tried as config file: {direct} (not found)\n" + f" Tried as config files: {', '.join(candidates)} (not found)\n" f" Available config files: {available_str}" )