Metadata-Version: 2.4
Name: tftree-manager
Version: 0.0.5
Summary: TF 树梳理与变换管理：YAML 外参加载/保存、图路径与环检测、直接关系重写、人工裁剪 UI、变换矩阵计算
License-Expression: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: numpy>=1.20.0
Requires-Dist: PyYAML>=5.4.0
Requires-Dist: Flask>=2.0.0
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Provides-Extra: ui
Requires-Dist: PySide6; extra == "ui"

# tftree-manager

**当前版本：0.0.5**

TF 树梳理与变换管理：从 YAML 目录加载/保存外参与顺序，构建无向图、路径查找、环检测、变换矩阵计算。

## 安装

```bash
pip install tftree-manager
```

## 功能特性

- 从 `yaml_dir/extrinsics/*.yaml` 与 `order_manifest.yaml` 加载/保存外参与顺序
- 无向图构建、BFS 路径查找、有向环检测
- 沿路径累积 4x4 变换矩阵、从根节点计算到所有可达节点
- 更新变换、求逆、删除有向边（`remove_transform`）、重排顺序并写回 YAML
- 直接关系重写（`rewrite_direct_relations`）：删除被更新相机旧链路，按外部 YAML 声明方向写入新直接边，并在 `order_manifest.yaml` 记录原始计算链路 `link_nodes`
- 精确人工裁剪（`remove_transforms_by_keys` / `launch_prune_ui`）：调用方主动打开 PySide6 图形 UI，选择并覆盖删除旧链路 YAML
- 可选 Flask API 服务与命令行入口

## 快速开始

```python
from tftree_manager import TFTreeManager

manager = TFTreeManager("/path/to/yaml_dir")
manager.load_from_yaml()

# 所有坐标系
frames = manager.get_all_frames()

# 两节点间变换矩阵
T = manager.get_transform("lidar", "camera")

# 从根节点计算到所有节点
transforms = manager.compute_all_transforms_from_root("world")

# 检测环
cycles = manager.detect_cycles()

# 保存
manager.save_to_yaml()
```

## 直接关系重写

V0.0.4 新增 `TFTreeManager.rewrite_direct_relations(update_yaml_path, prune_frames=None, output_dir=None, save=True)`，用于处理 PB 解析后的 YAML 工作目录。C04 负责产出 IPM 自动计算后的直接关系 YAML，`pb_calibration` 继续负责 PB parse/build。

本库已提供的模板能力：

- 读取 `direct_relations.yaml`，校验 `direct_relations` 格式、重复边、反向重复边与无向闭环。
- 删除 `replace_frames` 或 `prune_frames` 命中的旧边，按 update YAML 顺序追加新的直接边。
- 写出重写后的 `extrinsics/*.yaml` 与 `order_manifest.yaml`，并在 `direct_relations_meta` 保留追溯元数据。
- 提供 Python API 与 CLI 两种触发方式。

调用方需要单独开发的接口/适配层：

- 直接关系 YAML 生成接口：业务侧根据 IPM、标定结果或其它计算结果生成 `direct_relations`，本库不负责计算外参。
- PB 工作目录准备接口：业务侧负责调用 `pb_calibration parse`，准备包含 `extrinsics/` 与 `order_manifest.yaml` 的 YAML 工作目录。
- 重写触发接口：业务侧可封装 Python API 或 CLI，把 YAML 工作目录、update YAML、输出目录与错误处理接入 C04 或其它流程。
- PB 回写接口：业务侧继续调用 `pb_calibration build`，把重写后的 YAML 转回 PB；本库不直接读写 PB。

输入 YAML 格式：

```yaml
direct_relations:
  - source_frame: at128p_front
    target_frame: front_120
    link_nodes: [at128p_front, vrf, front_120]
    translation: {x: 0.0, y: 0.0, z: 0.0}
    rotation: {qx: 0.0, qy: 0.0, qz: 0.0, qw: 1.0}
    replace_frames: [front_120]
```

规则：

- `source_frame -> target_frame` 是最终写入 `extrinsics/*.yaml` 的方向，不自动反转。
- `link_nodes` 仅作为原始计算链路元数据保存到 `order_manifest.yaml` 的 `direct_relations_meta`，不写入单条 extrinsic YAML。
- `link_nodes` 长度必须 >= 2，首节点等于 `source_frame`，尾节点等于 `target_frame`。
- 未提供 `replace_frames` 时默认删除 `target_frame` 相关旧边；也可通过 API/CLI 额外传入 `prune_frames`。
- 重写后会校验不存在 `A->B` 与 `B->A` 反向重复边，且无无向闭环；允许森林结构。

推荐接入策略：

- 有输出隔离需求时使用 `output_dir`，避免覆盖 `pb_calibration parse` 生成的原始 YAML。
- 单相机更新时 `replace_frames` 默认填目标相机 frame 即可；显式写出更利于排查。
- 多相机更新时每条 direct relation 都显式写 `replace_frames`，避免误删无关外参。
- 捕获到 `TFTreeValidationError` 时不要继续执行 PB build，应保留原 YAML 并把错误详情返回给上层流程。

Python 示例：

```python
from tftree_manager import TFTreeManager

manager = TFTreeManager("/path/to/yaml_dir")
manager.load_from_yaml()
result = manager.rewrite_direct_relations(
    "/path/to/direct_relations.yaml",
    output_dir="/path/to/rewritten_yaml",
)
print(result.added_relations[0].link_nodes)
```

## 命令行

```bash
# 以 YAML 目录为参数，加载并打印树与环信息
tftree-manager /path/to/yaml_dir

# 按 direct_relations YAML 重写直接边；默认写回 yaml_dir
tftree-manager rewrite-direct /path/to/yaml_dir /path/to/direct_relations.yaml

# 输出到新目录，不修改原 YAML 目录
tftree-manager rewrite-direct /path/to/yaml_dir /path/to/direct_relations.yaml --output-dir /path/to/rewritten_yaml

# 额外删除指定 frame 相关旧边
tftree-manager rewrite-direct /path/to/yaml_dir /path/to/direct_relations.yaml --prune-frame front_120

# 启动 Flask API 服务（默认 0.0.0.0:5000）
tftree-manager-server
```

环境变量 `TFTREE_DATA_DIR` 可指定 API 默认数据目录（默认 `./data`）。

## 人工链路裁剪 UI

V0.0.5 新增独立 PySide6 UI，用于“重新标定后需要人工裁剪旧链路”的场景。UI 只读取/保存 PB parse 后的 YAML 工作目录，不直接生成 PB。

Python API：

```python
from tftree_manager import launch_prune_ui

result = launch_prune_ui(
    yaml_dir="/path/to/yaml_dir",
    candidate_edges=["vrf->front_120", "at128p_front->front_120"],
    title="重新标定链路裁剪",
)
print(result.removed_edges)
```

非 GUI 精确删除 API：

```python
from tftree_manager import TFTreeManager

manager = TFTreeManager("/path/to/yaml_dir")
manager.load_from_yaml()
result = manager.remove_transforms_by_keys(["vrf->front_120"])
```

CLI：

```bash
tftree-manager prune-ui /path/to/yaml_dir \
  --candidate-edge 'vrf->front_120' \
  --candidate-edge 'at128p_front->front_120'

# 自动化/测试入口：不打开 UI，直接删除并覆盖 YAML
tftree-manager prune-ui /path/to/yaml_dir --remove-edge 'vrf->front_120'

# 只校验候选边是否存在，不写盘；存在缺失候选边时退出码为 2
tftree-manager prune-ui /path/to/yaml_dir --candidate-edge 'missing->edge' --dry-run
```

保存策略：

- 默认直接覆盖 `yaml_dir/extrinsics/*.yaml` 与 `order_manifest.yaml`，不自动备份。
- 保存前校验剩余拓扑不存在反向重复边和无向闭环。
- 不存在的候选边会在 UI 中标红提示，不会作为删除项保存。

## 依赖

- Python >= 3.10
- numpy >= 1.20.0
- PyYAML >= 5.4.0
- Flask >= 2.0.0（API 与 server 入口需要）
- PySide6（仅 `prune-ui` 图形界面需要，可通过 `pip install tftree-manager[ui]` 安装）

## License

MIT
