P4s:模型审计 —— 移出零引用模型,修掉代码里三处悬空 best.pt
先回答"这些模型到底有没有人用",用全仓库引用扫描判定,不靠猜: 在用 yolo/detect_best_0.onnx + .engine ← paths.yolo_weights 在用 hole_insert/detect_best_1.onnx ← hole_detect.weights / workpiece_weights 零引用 detect_hc90(pt/onnx/engine) ← 只剩注释里的历史说明 零引用 detect_yolo26_gongjian.pt 零引用 detect_best(onnx/engine) ← 仅代码兜底候选,而那个候选是 .pt,不存在 所以 detect_hc90 不需要修复,是不用的东西:连同另两个一起移出仓库。 注意**二进制权重被 gitignore**,删掉就找不回来了,因此是移出到 ~/ros2-models-unused-20260923/ 而非 rm。搬回 models/ 即可被 list 重新发现。 顺手挖出并修掉三处悬空引用(和之前 site.yaml 里那个 best.pt 同一类问题): - hole_detector.weights_from_site:兜底路径写死 models/hole_insert/best.pt(不存在) - hole_detector._default_model_path:三个候选路径**全都不存在**,函数却 `return candidates[0]`,于是永远返回不存在的 best.pt;报错路径看着很合理, 极易被误判成"文件被误删"。现改为以 site.yaml 为准,再退到规范模型。 - config/hole_insert.yaml 顶部注释同样写的是 best.pt 新增 test_hole_detector.py(8 项)锁住这个回归。测试刻意**不断言文件存在** ——二进制不进 git,新克隆的仓库里 models/ 是空的——只断言解析结果指向规范模型名。 体检从 4 处问题降到 0:bash scripts/weights.sh doctor --check-engines → "没有发现问题" 测试 233 项通过;18 个包构建通过。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,7 +15,13 @@ import yaml
|
||||
|
||||
_PKG_SHARE_FALLBACK = Path(__file__).resolve().parents[1] / "config"
|
||||
DEFAULT_CONFIG = _PKG_SHARE_FALLBACK / "hole_insert.yaml"
|
||||
DEFAULT_MODEL_DIR = Path.home() / "ros2" / "models" / "hole_insert"
|
||||
|
||||
# 仓库里**实际存在**的孔/工件检测模型,相对仓库根。
|
||||
# 以前这个位置上写的是 ``models/hole_insert/best.pt`` —— 文件根本不存在,属于悬空引用:
|
||||
# 配置齐全时永远走不到这条分支,一旦 site.yaml 缺键就报"模型文件不存在",
|
||||
# 而打印出来的路径看着特别合理,很容易被误判成"文件被人误删了"。
|
||||
# 改动这个常量时,顺手跑 ``bash scripts/weights.sh doctor`` 确认没有悬空引用。
|
||||
_CANONICAL_HOLE_MODEL = "models/hole_insert/detect_best_1.onnx"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -91,20 +97,29 @@ def weights_from_site(data: dict[str, Any] | None = None) -> str:
|
||||
return str(
|
||||
hd.get("weights")
|
||||
or paths.get("hole_weights")
|
||||
or str(ros2_root() / "models" / "hole_insert" / "best.pt")
|
||||
or str(ros2_root() / _CANONICAL_HOLE_MODEL)
|
||||
)
|
||||
|
||||
|
||||
def _default_model_path() -> Path:
|
||||
candidates = [
|
||||
DEFAULT_MODEL_DIR / "best.pt",
|
||||
DEFAULT_MODEL_DIR / "hole_best.pt",
|
||||
Path.home() / "ros2" / "models" / "yolo" / "detect_best.pt",
|
||||
]
|
||||
for p in candidates:
|
||||
if p.exists():
|
||||
"""未显式给 ``model_path`` 时的兜底模型路径。
|
||||
|
||||
以前这里枚举了 ``hole_insert/best.pt`` / ``hole_best.pt`` / ``yolo/detect_best.pt``,
|
||||
三个文件在本仓库里**都不存在**,函数却会 ``return candidates[0]`` ——
|
||||
于是永远返回一个不存在的路径(同 ``weights_from_site`` 里那个悬空的 best.pt)。
|
||||
|
||||
现在以 site.yaml 为准(它才是真正的来源),它给的路径存在就用它;
|
||||
否则退到仓库里实际存在的规范模型。这样报错信息里的路径至少是对的。
|
||||
"""
|
||||
configured = weights_from_site()
|
||||
if configured:
|
||||
p = Path(configured).expanduser()
|
||||
if p.is_file():
|
||||
return p
|
||||
return candidates[0]
|
||||
|
||||
from arm_application.site_io import ros2_root
|
||||
|
||||
return ros2_root() / _CANONICAL_HOLE_MODEL
|
||||
|
||||
|
||||
class HoleDetector:
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
所以 engine 构建完就只剩张量,标签无从附着。实测:
|
||||
|
||||
detect_best_0.engine 二进制里搜不到 "workpiece" → 标签已丢
|
||||
detect_hc90.engine 能搜到 "workpiece" → 旧版 TRT 建的,且已无法加载
|
||||
detect_hc90.engine 能搜到 "workpiece" → 旧版 TRT 建的,但当前环境已无法加载
|
||||
|
||||
(`detect_hc90` 已作为零引用模型移出仓库,这里保留这行是因为它同时印证了两件事:
|
||||
旧版 TRT 建的 engine 里其实是有标签的,以及 engine 与 TRT 版本绑死。)
|
||||
|
||||
后果很隐蔽:`yolo_detector` 读不到名字时会退化成 `str(cid)`,也就是类名变成字符串
|
||||
`"0"`;而预览界面又不绘制类名,所以**没有任何地方看得出标签丢了**。
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 孔定位检测配置(改 conf / 内参 / 双孔间距即可,不必改代码)
|
||||
# 权重默认:~/ros2/models/hole_insert/best.pt
|
||||
# 权重来源:优先 site.yaml(hole_detect.weights / paths.hole_weights);
|
||||
# 两者都缺时兜底到 models/hole_insert/detect_best_1.onnx。
|
||||
# 原先这里注释写的是 best.pt —— 该文件不存在,已修正。
|
||||
# backend: yolo | hough | two_stage(two_stage 请用 hole_detect.yaml)
|
||||
|
||||
model_path: ""
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
"""`hole_detector` 权重路径解析的回归测试。
|
||||
|
||||
重点是挡住一类**悬空引用**:代码里写死的默认权重路径指向一个根本不存在的文件。
|
||||
这种 bug 特别难查,因为
|
||||
|
||||
* 配置齐全时永远走不到那条分支,日常运行完全正常;
|
||||
* 一旦 site.yaml 缺键才会触发,而报错信息里打印的路径看起来非常合理
|
||||
(`.../models/hole_insert/best.pt`),很容易被误判成"文件被人误删了"。
|
||||
|
||||
这里**不断言文件存在** —— 二进制权重不进 git,新克隆的仓库里 `models/` 是空的。
|
||||
只断言"解析结果指向规范模型名",这样在任何环境下都成立。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from arm_application.hole_detector import _default_model_path, weights_from_site
|
||||
|
||||
CANONICAL = "detect_best_1.onnx"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# weights_from_site:site.yaml 缺键时的兜底
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_weights_from_site_prefers_explicit_config():
|
||||
data = {
|
||||
"hole_detect": {"backend": "yolo", "weights": "/custom/my_holes.onnx"},
|
||||
"paths": {"hole_weights": "/custom/ignored.onnx"},
|
||||
}
|
||||
assert weights_from_site(data) == "/custom/my_holes.onnx"
|
||||
|
||||
|
||||
def test_weights_from_site_falls_back_to_paths():
|
||||
data = {
|
||||
"hole_detect": {"backend": "yolo"},
|
||||
"paths": {"hole_weights": "/custom/from_paths.onnx"},
|
||||
}
|
||||
assert weights_from_site(data) == "/custom/from_paths.onnx"
|
||||
|
||||
|
||||
def test_weights_from_site_yolo_fallback_is_not_dangling():
|
||||
"""回归:以前这里兜底到不存在 best.pt,现在必须指向规范模型。"""
|
||||
out = weights_from_site({"hole_detect": {"backend": "yolo"}})
|
||||
assert out.endswith(CANONICAL)
|
||||
assert "best.pt" not in out
|
||||
|
||||
|
||||
def test_weights_from_site_two_stage_uses_workpiece_weights():
|
||||
data = {
|
||||
"hole_detect": {"backend": "two_stage", "workpiece_weights": "/w/wp.onnx"},
|
||||
"paths": {"hole_weights": "/should/not/be/used.onnx"},
|
||||
}
|
||||
assert weights_from_site(data) == "/w/wp.onnx"
|
||||
|
||||
|
||||
def test_weights_from_site_two_stage_falls_back_to_yolo_weights():
|
||||
data = {
|
||||
"hole_detect": {"backend": "two_stage"},
|
||||
"paths": {"yolo_weights": "/w/track.onnx"},
|
||||
}
|
||||
assert weights_from_site(data) == "/w/track.onnx"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _default_model_path:没有任何配置时的兜底
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_default_model_path_is_not_the_dangling_best_pt():
|
||||
"""回归:以前枚举的三个候选全不存在,函数却 `return candidates[0]`。
|
||||
|
||||
于是它永远返回一个不存在的 `hole_insert/best.pt`,而调用方拿到之后
|
||||
只会在真正加载模型时才炸,报错路径还很有迷惑性。
|
||||
"""
|
||||
p = _default_model_path()
|
||||
assert p.name not in ("best.pt", "hole_best.pt"), f"又指回悬空文件了: {p}"
|
||||
|
||||
|
||||
def test_default_model_path_is_canonical_name():
|
||||
assert _default_model_path().name == CANONICAL
|
||||
|
||||
|
||||
def test_default_model_path_is_absolute():
|
||||
"""必须是绝对路径:相对路径会随 CWD 漂移,节点从不同目录启动就找不到。"""
|
||||
assert _default_model_path().is_absolute()
|
||||
Reference in New Issue
Block a user