Reconstruction code style cleanup plan (2026-04-27)¶
Goal¶
把 pipeline/reconstruction、pipeline/hoi_reconstructor.py、以及相关 yaml/json config 统一改成 pipeline/reconstruction/preprocess/human/sam3d_body.py 当前这种代码习惯:
- 入口逻辑平铺,读者能直接看到输入、主要处理、输出。
- 函数少而实,只保留明确领域动作或容易写错的逻辑。
- config 严格传入,不做复杂 parse、fallback、兼容搬运。
- 注释充分解释流程、坐标系、shape、loss、hard constraint,不写语法翻译。
- debug 文件只是检查产物,不作为阶段 handoff。
这份文档用于替换旧计划里“代码习惯要求”那部分过于抽象的表述。以后 reconstruction 相关代码 review 以这里为准。
Reference¶
当前目标风格参考:
pipeline/reconstruction/preprocess/human/sam3d_body.py
这个文件已经具备下面几个特征:
run_sam3d_body(video_dir, motion_cfg)是唯一上层入口。- 入口用
# 1. ...、# 2. ...标出主流程。 - config 字段直接读取,缺字段直接报错。
- 固定输入/输出路径和固定 export dict 直接 inline。
- MHR joint weights 用有语义的常量和 index group 表达,不裸写 magic numbers。
- 领域函数有简短 docstring,复杂处有必要注释。
- debug 文件集中写到 debug 目录,不作为下一阶段输入。
Non-goals¶
- 不把 reconstruction 改成框架。
- 不新增 provider、registry、runner class、plugin、hook、callback。
- 不为了未来实验预留扩展点。
- 不为了兼容旧 config 写复杂迁移层。
- 不把 debug/cache 文件重新变成 stage handoff。
Mandatory Code Rules¶
1. Entrypoint style¶
每个被上层 import 的 preprocess 模块只保留一个清晰入口函数:
入口函数内部按主流程编号:
# 1. Prepare folders and validate inputs.
# 2. Load models and upstream files.
# 3. Run the model.
# 4. Convert into the downstream format.
# 5. Write outputs and optional debug files.
要求:
- 编号注释描述 pipeline 阶段,不描述 Python 语法。
- 固定路径直接写在使用处,例如
video_dir / "motion" / "result.pt"。 - 不创建
_resolve_paths(...)这类只包一层 path dict 的函数。 - import-only 模块不需要
argparse、main()、if __name__ == "__main__"。 - 真正需要 CLI 的脚本可以保留
main(),但 CLI 只负责 argparse 和调用核心函数。
2. Config style¶
config 是输入契约,不是让代码猜测的对象。
要求:
- 直接读字段,例如
sam3d_cfg = motion_cfg["sam3d_body"]。 - 不写
str(...).strip().lower()这类防御式转换。 - 不写
if "x" in cfg else default,除非这个字段在 plan 里明确允许可选。 - 缺字段就让
KeyError或具体RuntimeError暴露。 - model-specific 配置放在 model 自己的 block 里,例如
motion.sam3d_body.*、motion.gvhmr.*。 - yaml/json 必须同步补齐必需字段,不能依赖代码默认值。
推荐结构:
motion:
enabled: true
model: "sam3d_body"
debug_mode: false
sam3d_body:
config: "submodules/sam-body4d/configs/body4d.yaml"
max_frames: 0
batch_size: 32
smplx_batch_size: 256
不推荐:
batch_size = cfg["batch_size"] if "batch_size" in cfg else 32
model = str(cfg["model"]).strip().lower()
3. Function style¶
函数只在下面情况保留:
- 对应一个明确动作,例如
fit_ground_plane(...)、transform_hmr_to_world(...)。 - 封装容易写错的领域逻辑,例如 SO(3)、camera/world transform、MHR forward materialize。
- 复用至少 2 次,且重复写容易出错。
函数不应该存在于下面情况:
- 只拼固定路径。
- 只返回一个固定 dict。
- 只包装
Path.mkdir、json.dump、np.savez_compressed、torch.save。 - 只被调用一次,且函数体比调用点更难理解。
参数要求:
- 避免长参数函数。
- 优先传自然上下文,例如
params、world、motion_cfg、recon_cfg。 - 不要把
params["body_pose"]、world["T_c2w"]打平后到处传。 - 只有优化变量需要变成
nn.Parameter时,才在局部拆出来。
3.1 Naming and container style¶
名称必须直接说明数据是什么。禁止使用 artifact、artifacts 这类无法表达领域语义的
泛化名称,包括变量名、dict key、dataclass field、配置块和目录名。
推荐:
result_path = video_dir / "motion" / "result.pt"
common_rollout_path = run_dir / "common_rollout.npz"
video_path = run_dir / "render.mp4"
不推荐:
要求:
- 输入和输出按真实角色命名,例如
case_json、result_path、common_rollout_path、evaluation_path、video_path。 - 单次使用的简单路径拼接写成一行,不为排版增加多行括号,也不提取 helper:
不要写:
如果表达式确实过长,先提取已有的自然分组,例如 physics = case["physics"],然后仍将
路径拼接写成一行;不要新增 _resolve_*_path()。
- 上述规则适用于 reconstruction、Physics 和所有 source converter。JSON 中保存的相对
路径就在第一次使用处相对 JSON 所在目录解析;禁止新增 _resolve_case_path(...)、
_load_input_files(...) 或只做一次 Path.resolve() 的 helper。
- Runtime loader 只读取和严格检查当前固定字段。旧输出的补字段、改路径和格式迁移必须
放在一次性脚本中,不能作为 loader 的分支或 fallback。
- 不新增只为重新包装已有路径或 dict 的 ArtifactBundle、PhysicsCase、
OutputArtifacts 等容器。
- 已有 case JSON、result.pt、run_record.json 或自然分组对象能够表达数据时,直接复用;
不再定义一份重复 schema。
- run_record.json 使用 inputs 和 outputs 区分来源与结果;不要增加
artifacts 总括层。
- 只有两个以上模块共享同一组有领域含义的数据,并且类型能阻止真实错误时,才增加
dataclass;仅减少参数数量不是新增 dataclass 的理由。
- NPZ/JSON runtime 格式只保存数据和必要路径,不保存
*_SEMANTICS、protocol/version 名称或自然语言 contract。
- 不重复可由同一输入推导出的字段。静态 object/human geometry 已由 URDF/MJCF 表达时,
rollout 不再重复 scale、gender 或 betas。
- 不增加任意 metadata dict 作为扩展口;当前消费者需要什么字段,就用准确名称显式
表达什么字段。
- 只被调用一次、只返回固定 dict 的 helper 直接 inline;只在一个文件内搬运几个值且
不能阻止真实错误的薄 dataclass 直接删除。
4. Constant and magic number style¶
有领域含义的数字必须命名。
推荐:
MHR_JOINT_W_IGNORE_ROOT = 0.0
MHR_JOINT_W_STRONG_ANCHOR = 0.8
MHR_JOINT_WEIGHT_GROUPS = (
(MHR_JOINT_W_IGNORE_ROOT, (0, 1, 2, 3, 4)),
(MHR_JOINT_W_STRONG_ANCHOR, (5, 6, 69)),
)
不推荐:
允许 inline 的数字:
- 一眼能看懂的 array shape index,例如
points.shape[-1] != 3。 - 标准图像阈值,例如二值 mask
> 127。 - 简短固定矩阵维度,例如
reshape(4, 4)。
5. Comment style¶
注释面向读者解释“为什么”和“这一步在 pipeline 中做什么”。
必须注释的内容:
- camera frame vs world frame。
T_c2w/T_w2c的方向。- MHR axis flip。
- MHR
body_pose[:130]。 - Euler/SO(3) 转换。
- contact heel index。
- loss 的物理或视觉意义。
- debug 文件用于检查什么问题。
函数 docstring 要说功能,不要说 schema 细节。
推荐:
不推荐:
block-level 注释前留空行:
if inlier_pts.shape[0] < 30:
raise RuntimeError(...)
# Refit the final plane on all inliers for a stable normal and offset.
centroid = inlier_pts.mean(axis=0)
不要写逐行语法翻译:
6. Data flow style¶
阶段间数据优先在内存中传递。
要求:
- reconstruction 内部禁止
stage_dir + candidate.json + smoothed_world_mhr.npz这类 stage handoff。 - debug 落盘是事后检查,不是下一阶段输入。
result.pt、tracks.npz、depth/*.npz这类最终结果或检查文件可以保留。- 同一进程内禁止 subprocess 链。
- 如果必须调用外部工具,只允许上层明确一跳,子流程内部不能再 fork 其他脚本。
7. Subprocess and environment boundary¶
这条适用于所有 pipeline adapter,不限 reconstruction。
- 公开 YAML/JSON config 不得写 Conda 环境名、解释器绝对路径、机器目录、包目录、cache 目录或
PATH/PYTHONPATH/LD_LIBRARY_PATH等环境变量。 - 使用者先在 shell 或任务调度器中激活所需 Conda 环境,再用该环境启动
cli.pipeline.physics。 - pipeline 必须启动外部后端时,命令第一个参数使用
sys.executable,并且不传env=;子进程自然继承启动 CLI 的同一解释器和环境。 - Python 代码不得反推 Conda prefix,不得改写
CONDA_PREFIX、PATH、PYTHONPATH、LD_LIBRARY_PATH、CUDA 可见卡或临时 cache 路径。 - 只有 shell 启动脚本可以包含本机环境激活与环境变量导出;这些是部署入口,不是 pipeline 逻辑。
8. Common code style¶
只有真实共享逻辑才进 common.py。
可以共享:
- 坐标系转换。
- MHR forward materialize。
- MoGe cache 读写。
- 多个脚本都需要且容易写错的 shape validation。
不应该共享:
- 简单 path 拼接。
- 单处使用的小判断。
- 单处使用的 json dump。
- 单处使用的 summary dict。
Refactor Scope¶
1. pipeline/reconstruction/*¶
目标:
- 所有 preprocess 模块改成
run_xxx(video_dir, cfg)风格。 - 每个模块职责单一,入口平铺,领域函数少而实。
- 不通过 subprocess 调同仓库另一个 preprocess 脚本。
- 不使用 stage handoff 文件串联同一模块内部步骤。
- debug 输出集中在 stage debug 目录,正常路径只写下游需要的文件。
优先检查文件:
pipeline/reconstruction/preprocess/human/gvhmr.pypipeline/reconstruction/preprocess/depth/moge.pypipeline/reconstruction/preprocess/depth/video_depth_anything.pypipeline/reconstruction/preprocess/tracks/tapip3d_tracks.pypipeline/reconstruction/preprocess/tracks/project_tracks_2d.pypipeline/reconstruction/optimization/*
具体改法:
- 把长 argparse/main 逻辑拆成可 import 的
run_xxx(...)。 - 如果模块只被上层 import,删除 CLI 入口。
- 删除只用于调用脚本的
subprocess.run,改为 import 函数后同进程调用。 - 删除
_resolve_paths、build_context、runner、provider、registry类抽象。 - 固定输出路径 inline。
- 必需 config 字段直接读取。
- 对 loss、坐标系、shape、hard constraint 补注释。
2. pipeline/hoi_reconstructor.py¶
目标职责收敛:
- 准备
preprocess/目录。 - 按 config 跑 segmentation、motion、depth、tracks。
- 调用 solver。
- 渲染结果。
必须修改:
assert config["reconstruction"]["method_type"] == "ours"。- 删除 D3D-HOI baseline runner/import/branch,D3D-HOI 只作为 asset 数据来源。
- 删除
build_context、大statedict、provider/registry/runner class。 - 删除 root-level/flat config 读取。
- 按 config 直接分发:
if motion_cfg["model"] == "gvhmr":
run_gvhmr(preprocess_dir, recon_cfg)
elif motion_cfg["model"] == "sam3d_body":
run_sam3d_body(preprocess_dir, motion_cfg)
else:
raise RuntimeError(f"Unsupported motion model: {motion_cfg['model']}")
入口结构建议:
def main():
config = load_config(...)
assert config["reconstruction"]["method_type"] == "ours"
# 1. Prepare preprocess directory and copied inputs.
# 2. Run segmentation.
# 3. Run motion.
# 4. Run depth.
# 5. Run tracks.
# 6. Run object solver.
# 7. Render outputs.
不应该保留:
resolve_d3dhoi_input(...)放在这一层。- legacy method branch 的深层兼容逻辑。
str(...).strip().lower()。- 为固定 path 和固定 summary 写小 helper。
subprocess.run调 reconstruction 子脚本。
3. Config yaml/json¶
目标:
- config 表达 pipeline 选择,不让代码猜默认值。
- yaml/json 与代码字段一一对应。
- model-specific 字段放在对应 model block。
必须修改:
- 所有
motion.model: "sam3d_body"的 config 必须有motion.sam3d_body。 - 所有
motion.model: "gvhmr"的 config 必须有motion.gvhmr或明确的 GVHMR block。 - depth/tracks/segmentation 同理,model-specific 字段不能散在 root。
- 删除 legacy flat alias,或者在 plan 中明确某份 legacy config 暂不维护。
- json config 与 yaml config 保持同一结构。
推荐 motion config:
motion:
enabled: true
model: "sam3d_body"
debug_mode: false
sam3d_body:
config: "submodules/sam-body4d/configs/body4d.yaml"
max_frames: 0
batch_size: 32
smplx_batch_size: 256
推荐 GVHMR config:
motion:
enabled: true
model: "gvhmr"
debug_mode: false
gvhmr:
static_cam: true
use_dpvo: false
f_mm: null
hand_refine:
enabled: false
refiner: "hamer"
batch_size: 16
dynhamr:
batch_size: 48
yolo_conf: 0.25
yolo_model: ""
Migration Order¶
Step 1. Freeze style baseline¶
- Keep
sam3d_body.pyas the local style reference. - Do not add new abstraction to
sam3d_body.pyunless another file really reuses the logic. - Keep
configs/ours_global_opt.yamlaligned with the strictsam3d_bodyfields.
Step 2. Clean hoi_reconstructor.py¶
- Remove legacy/baseline branches from the main reconstruction path.
- Replace build-context logic with direct config reads.
- Make stage calls explicit and ordered.
- Keep only
oursas maintained method in this entry.
Step 3. Clean preprocess modules¶
- Refactor GVHMR to put hand-refine and Dyn-HaMR config under the GVHMR motion block.
- Refactor depth modules to expose direct function entries and strict config reads.
- Refactor tracks modules to remove subprocess chains and stage handoff files where possible.
- Keep CLI only for scripts that are truly standalone.
Step 4. Clean configs¶
- Update
configs/ours_*.yaml. - Update
configs/exp/**/*.json. - Decide whether
configs/baseline/d3dhoi/*.yamlare maintained or frozen. - If a config is frozen legacy, mark it clearly instead of silently supporting it in code.
Step 5. Verify¶
Minimum checks:
python -m py_compile pipeline/hoi_reconstructor.py
python -m py_compile pipeline/reconstruction/preprocess/human/sam3d_body.py
python -m py_compile pipeline/reconstruction/preprocess/human/gvhmr.py
python -m py_compile pipeline/reconstruction/preprocess/depth/moge.py
python -m py_compile pipeline/reconstruction/preprocess/depth/video_depth_anything.py
git diff --check -- pipeline/hoi_reconstructor.py pipeline/reconstruction configs
Behavior checks:
method_type != "ours"在hoi_reconstructor.py直接 assert fail。motion.model == "sam3d_body"只走run_sam3d_body(...)。motion.model == "gvhmr"只走run_gvhmr(...)。- 缺必需 config 字段时直接报错。
- debug off 时不写额外 debug/cache handoff。
- debug on 时只写检查文件,不作为下一阶段输入。
Review Checklist¶
代码 review 时逐项检查:
- 是否有 provider/registry/runner class/plugin/hook/callback。
- 是否有
_resolve_paths、build_context、state大 dict。 - 是否有
str(...).strip().lower()防御式 config parse。 - 是否有
if "x" in cfg else default隐式默认值。 - 是否有固定 dict/path/summary 被薄函数包装。
- 是否有 subprocess 调 reconstruction 内部脚本。
- 是否有 stage handoff 文件承担同模块内部数据传递。
- 是否有裸 magic number,且这个数字其实有领域含义。
- 是否缺少 camera/world、
T_c2w、MHR、SO(3)、contact、loss 的注释。 - 是否 block-level 注释前没有空行。
- 是否 config yaml/json 与代码必需字段一致。