跳转至

Reconstruction code style cleanup plan (2026-04-27)

Goal

pipeline/reconstructionpipeline/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 模块只保留一个清晰入口函数:

def run_xxx(video_dir: Path, cfg) -> Path:
    """Do one pipeline stage in plain language."""

入口函数内部按主流程编号:

# 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 模块不需要 argparsemain()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.mkdirjson.dumpnp.savez_compressedtorch.save
  • 只被调用一次,且函数体比调用点更难理解。

参数要求:

  • 避免长参数函数。
  • 优先传自然上下文,例如 paramsworldmotion_cfgrecon_cfg
  • 不要把 params["body_pose"]world["T_c2w"] 打平后到处传。
  • 只有优化变量需要变成 nn.Parameter 时,才在局部拆出来。

3.1 Naming and container style

名称必须直接说明数据是什么。禁止使用 artifactartifacts 这类无法表达领域语义的 泛化名称,包括变量名、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"

不推荐:

artifacts = {"result": ..., "rollout": ..., "video": ...}
artifact_path = ...

要求:

  • 输入和输出按真实角色命名,例如 case_jsonresult_pathcommon_rollout_pathevaluation_pathvideo_path
  • 单次使用的简单路径拼接写成一行,不为排版增加多行括号,也不提取 helper:
contact_path = (case_json_path.parent / case["physics"]["contact_reference_path"]).resolve()

不要写:

contact_path = (
    case_json_path.parent
    / case["physics"]["contact_reference_path"]
).resolve()

如果表达式确实过长,先提取已有的自然分组,例如 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 的 ArtifactBundlePhysicsCaseOutputArtifacts 等容器。 - 已有 case JSON、result.ptrun_record.json 或自然分组对象能够表达数据时,直接复用; 不再定义一份重复 schema。 - run_record.json 使用 inputsoutputs 区分来源与结果;不要增加 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)),
)

不推荐:

joint_w[:5] = 0.0
joint_w[[5, 6, 69]] = 0.8

允许 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 细节。

推荐:

"""Transform SAM3D/MHR motion from camera frame to the floor-aligned world frame."""

不推荐:

"""Write motion/result.pt in the canonical smpl_params_global + K_fullimg 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)

不要写逐行语法翻译:

# Convert tensor to numpy.
arr = tensor.cpu().numpy()

6. Data flow style

阶段间数据优先在内存中传递。

要求:

  • reconstruction 内部禁止 stage_dir + candidate.json + smoothed_world_mhr.npz 这类 stage handoff。
  • debug 落盘是事后检查,不是下一阶段输入。
  • result.pttracks.npzdepth/*.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_PREFIXPATHPYTHONPATHLD_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.py
  • pipeline/reconstruction/preprocess/depth/moge.py
  • pipeline/reconstruction/preprocess/depth/video_depth_anything.py
  • pipeline/reconstruction/preprocess/tracks/tapip3d_tracks.py
  • pipeline/reconstruction/preprocess/tracks/project_tracks_2d.py
  • pipeline/reconstruction/optimization/*

具体改法:

  • 把长 argparse/main 逻辑拆成可 import 的 run_xxx(...)
  • 如果模块只被上层 import,删除 CLI 入口。
  • 删除只用于调用脚本的 subprocess.run,改为 import 函数后同进程调用。
  • 删除 _resolve_pathsbuild_contextrunnerproviderregistry 类抽象。
  • 固定输出路径 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、大 state dict、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.py as the local style reference.
  • Do not add new abstraction to sam3d_body.py unless another file really reuses the logic.
  • Keep configs/ours_global_opt.yaml aligned with the strict sam3d_body fields.

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 ours as 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/*.yaml are 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_pathsbuild_contextstate 大 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 与代码必需字段一致。