fix(ceus): 降低长视频内存峰值,快照开跑即写,build 核对 CEUS 运行与交付包
- 内存:prepare_segmentation 改为流式解码,只留分屏和配准的抽样帧,绿框逐帧在两半上找; 分割那一半由 SegmentationInput.target_frames() 到用时再解码,segment_video 边读边写 jpg; MedSAM2 init_state 的读帧换成直接在存放设备上建张量(不改 MedSAM2 源码,结果逐位相同); 传播结果写进预分配数组。帧数最多的一段视频常驻内存峰值 11.2 GiB → 2.3 GiB,分辨率最大的 一段原代码被 OOM 杀掉、现为 4.2 GiB;两段的掩码和全部产物与改前逐字节相同,33 段视频的 第 1–2 步结果与改前一致,29 例配准与 review.csv 一致。 - 快照:ceus_quant 开跑时写 snapshot/(git、ceus.yaml、review.csv、uv.lock、segment.json), 配置和核对表从快照副本读;收尾由新函数 runs.finish_snapshot 只新建 finished.json。 build 与 ceus_align_review 同样改用 finish_snapshot 记结束时间。 - build:summary.json 加 video_sha256;装 CEUS 结果时核对快照里的 delivery_dir、逐例核对 视频 SHA-256 和推注时间,不同就整次停下;新字段做类型检查,不进 tic.json。 来源:主会话任务(问题 1–3 及补充的 delivery_dir 比对)。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,7 @@ import numpy.typing as npt
|
||||
from cadx import ceus
|
||||
from cadx.ceus import CeusConfig, HalfRegistration, ViewRect, crop_frame
|
||||
from cadx.delivery import read_delivery
|
||||
from cadx.runs import make_build_id, read_git_state, write_snapshot
|
||||
from cadx.runs import finish_snapshot, make_build_id, read_git_state, write_snapshot
|
||||
from ceus_quant import REPO_ROOT, REVIEW_HEADER, file_sha256, load_config, resolve_paths
|
||||
|
||||
KEY_HEADER = ("id", "video_id", "is_control", "dx", "dy", "video_sha256")
|
||||
@@ -35,13 +35,6 @@ type _Gray = npt.NDArray[np.float32]
|
||||
type _Mask = npt.NDArray[np.bool_]
|
||||
|
||||
|
||||
def _indices(count: int, wanted: int) -> set[int]:
|
||||
"""与 `sample_frames_for_split` 相同的均匀抽帧下标。"""
|
||||
if count <= wanted:
|
||||
return set(range(count))
|
||||
return {int(i) for i in np.linspace(0, count - 1, num=wanted, dtype=np.int32).tolist()}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Material:
|
||||
"""一例出图要的东西:两个视图;整帧的时间平均灰度、抽样中间那帧的灰度、绿框紫圈像素;两半扇形。"""
|
||||
@@ -59,8 +52,8 @@ def step1(video_path: Path, cfg: CeusConfig) -> tuple[Material, HalfRegistration
|
||||
"""第 1 步:逐帧解码只留分屏和配准要的抽样帧,找分屏线,配准两半。"""
|
||||
video = ceus.VideoFile(video_path)
|
||||
_, frame_count, _, _ = video.probe()
|
||||
split_at = _indices(frame_count, cfg.views.split_sample_frames)
|
||||
register_at = _indices(frame_count, cfg.registration.sample_frames)
|
||||
split_at = set(ceus.sample_indices(frame_count, cfg.views.split_sample_frames))
|
||||
register_at = set(ceus.sample_indices(frame_count, cfg.registration.sample_frames))
|
||||
split_frames, frames = list[_U8](), list[_U8]()
|
||||
decoded = 0
|
||||
for index, frame in video.iter_frames():
|
||||
@@ -169,9 +162,9 @@ def render(config_path: Path) -> None:
|
||||
csv.writer(handle).writerows([KEY_HEADER, *sorted(rows)])
|
||||
with (partial / "failed.csv").open("w", encoding="utf-8", newline="") as handle:
|
||||
csv.writer(handle).writerows([("video_id", "step", "reason"), *failures])
|
||||
finished_at, lock = datetime.now().astimezone(), REPO_ROOT / "uv.lock"
|
||||
configs = {"ceus.yaml": config_path} # align_check 段的随机数种子随 ceus.yaml 进快照
|
||||
write_snapshot(partial / "snapshot", run_id, git, started_at, finished_at, lock, configs)
|
||||
write_snapshot(partial / "snapshot", run_id, git, started_at, REPO_ROOT / "uv.lock", configs)
|
||||
finish_snapshot(partial / "snapshot", datetime.now().astimezone(), {})
|
||||
if run_dir.exists():
|
||||
raise FileExistsError(f"运行目录已存在,不覆盖:{run_dir}")
|
||||
partial.rename(run_dir)
|
||||
|
||||
Reference in New Issue
Block a user