CoT_box_upload / model /pipeline.py
BingoG's picture
Upload model files
aa975a2 verified
Raw
History Blame Contribute Delete
10.4 kB
import os, json, pandas as pd, csv
from typing import Dict, Any, List, Set
from config import VLLMConfig
from utils import norm_key, ensure_dir
from sampling import read_video_meta, sample_indices_uniform, sample_indices_fps, grab_frames
from client import to_jpeg_base64, call_vllm
from prompt import build_messages
from parser import parse_step5_line, validate_bbox_sequences
from video_utils import compose_videos, build_out_paths
from tqdm import tqdm
def load_dataframe(csv_path: str) -> pd.DataFrame:
# 同前版本:兼容你提供的表头
with open(csv_path, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
try:
header = next(reader)
except StopIteration:
return pd.DataFrame(columns=['videoid','source_video_path','instruction','chinese_instruction'])
norm_header = [h.strip() for h in header]
if len(norm_header) >= 4 and \
norm_header[0].lower() == 'videoid' and \
norm_header[1].lower() == 'source_video_path' and \
norm_header[2].lower() == 'instruction' and \
norm_header[3].lower() == 'chinese_instruction':
rows = []
for row in reader:
if not row or all((x is None or str(x).strip() == '') for x in row): continue
vid = (row[0].strip() if len(row) > 0 else '')
vpath= (row[1].strip() if len(row) > 1 else '')
rest = row[2:] if len(row) > 2 else []
joined = ','.join([x if x is not None else '' for x in rest])
instr, zh = ('','')
if ',' in joined: instr, zh = joined.rsplit(',', 1)
else: instr, zh = joined, ''
rows.append({
'videoid': vid,
'source_video_path': vpath,
'instruction': instr.strip(),
'chinese_instruction': zh.strip(),
})
return pd.DataFrame(rows, columns=['videoid','source_video_path','instruction','chinese_instruction'])
else:
return pd.read_csv(csv_path, engine='python')
def _stable_key(row: Dict[str,Any]) -> str:
cols = {norm_key(k): k for k in row.keys()}
def pick(*cands, default=None):
for c in cands:
if c in cols: return row[cols[c]]
return default
uid = pick('videoid','uid','id','task_id','clip_id','name','key', default=None)
video_path = pick('source_video_path','video_path','video','filepath','file_path')
sampling_mode = pick('sampling_mode','sampling','mode', default='uniform_5')
stem = str(uid) if uid not in (None, "") else os.path.splitext(os.path.basename(video_path or ""))[0]
return f"{stem}::{sampling_mode}"
def _append_results_csv(results_csv: str, row: Dict[str,Any], header_written: bool):
df = pd.DataFrame([row])
write_header = not os.path.exists(results_csv) or (os.path.getsize(results_csv)==0 and not header_written)
df.to_csv(results_csv, mode='a', header=write_header, index=False, encoding='utf-8-sig')
def process_one(row: Dict[str,Any], vllm: VLLMConfig, outroot: str, dry_run: bool=False) -> Dict[str,Any]:
cols = {norm_key(k): k for k in row.keys()}
def pick(*cands, default=None):
for c in cands:
if c in cols: return row[cols[c]]
return default
video_path = pick('source_video_path','video_path','video','filepath','file_path')
cn = pick('chinese_instruction'); en = pick('instruction','edit_instruction','edit','prompt','text')
edit_instruction = (cn if cn and str(cn).strip() else en)
sampling_mode = pick('sampling_mode','sampling','mode', default='uniform_5')
fps_val = pick('fps', default=None); n_frames_val = pick('n_frames','n','nframes','num_frames', default=None)
uid = pick('videoid','uid','id','task_id','clip_id','name','key', default=None)
fps = 1.0 if fps_val in (None,"") else float(fps_val)
n_frames = 5 if n_frames_val in (None,"") else int(n_frames_val)
if not video_path or not os.path.exists(video_path):
raise AssertionError(f'视频不存在或未提供:{video_path}')
if not (isinstance(edit_instruction,str) and len(str(edit_instruction).strip())>0):
raise AssertionError('缺少编辑指令(chinese_instruction / instruction)')
meta = read_video_meta(video_path)
if sampling_mode == 'fps_1':
indices = sample_indices_fps(meta['total'], meta['fps'], fps or 1.0)
mode_tag = f'fps_{int(fps)}'
else:
indices = sample_indices_uniform(meta['total'], n_frames or 5)
mode_tag = f'uniform_{len(indices)}'
frames_rgb, mapping = grab_frames(video_path, indices)
frames_b64 = [to_jpeg_base64(f) for f in frames_rgb]
messages = build_messages(frames_b64, edit_instruction)
step5_line = ""
raw_resp = None
latency = None
if not dry_run:
step5_line, latency, raw_resp = call_vllm(vllm.api_base, vllm.model, messages)
# 记录“原始文本输出”(完整保留,尚未做正则抽取)
raw_dir = os.path.join(outroot, 'raw_text')
os.makedirs(raw_dir, exist_ok=True)
stem = str(uid) if uid not in (None, "") else os.path.splitext(os.path.basename(video_path))[0]
raw_txt_path = os.path.join(raw_dir, f'{stem}__{mode_tag}.txt')
with open(raw_txt_path, 'w', encoding='utf-8') as fw:
fw.write(step5_line if isinstance(step5_line, str) else str(step5_line))
# 仅取最后一行作为 Step5 行(若模型多段输出)
lines = [l.strip() for l in step5_line.strip().splitlines() if l.strip()]
step5_line = lines[-1] if lines else step5_line
else:
raw_txt_path = ""
parsed = {}
enhanced_instruction = ""
tasks = {}
if step5_line:
parsed = parse_step5_line(step5_line) # 按你固定格式解析
validate_bbox_sequences(parsed) # 坐标规范校验
enhanced_instruction = parsed.get('enhanced_instruction','')
tasks = parsed.get('tasks',{})
# 输出路径(含原视频名 + 后缀)
stem = str(uid) if uid not in (None, "") else os.path.splitext(os.path.basename(video_path))[0]
out_inspect, out_mask = build_out_paths(outroot, video_path, stem, mode_tag)
out_json = os.path.join(outroot, 'parsed_json', f'{stem}__{mode_tag}.json')
for d in [os.path.dirname(out_inspect), os.path.dirname(out_mask), os.path.dirname(out_json)]:
os.makedirs(d, exist_ok=True)
# 无论是否解析成功,都保存 JSON(含 raw_response)
with open(out_json, 'w', encoding='utf-8') as f:
json.dump({
'video_path': video_path,
'sampling_mode': sampling_mode,
'indices': indices,
'mapping_local_t_to_global_index': mapping,
'model_api_base': vllm.api_base,
'model_name': vllm.model,
'enhanced_instruction': enhanced_instruction,
'tasks': tasks,
'raw_response': raw_resp,
'raw_text_path': raw_txt_path,
'parsed_ok': bool(parsed),
}, f, ensure_ascii=False, indent=2)
# 两路视频:采样帧原视频(红框) + 黑底白框;无坐标也生成,便于排查
compose_videos(video_path, mapping, parsed or {"tasks": {}}, out_inspect, out_mask, allow_empty=True)
return {
'key': f"{stem}::{sampling_mode}",
'uid': uid,
'video_path': video_path,
'edit_instruction_src': edit_instruction,
'enhanced_instruction': enhanced_instruction,
'sampling_mode': sampling_mode,
'n_sampled': len(indices),
'indices_json': json.dumps(indices, ensure_ascii=False),
'mapping_json': json.dumps(mapping, ensure_ascii=False),
'tasks_json': json.dumps(tasks, ensure_ascii=False),
'latency_s': latency,
'out_inspect': out_inspect,
'out_mask': out_mask,
'out_json': out_json,
'raw_text_path': raw_txt_path,
'parsed_ok': bool(parsed),
'error': '' if parsed else 'parsed_empty_or_invalid'
}
def run_csv(csv_path: str, outroot: str, vllm: VLLMConfig, dry_run: bool=False,
results_csv: str=None, resume: bool=False, show_progress: bool=True) -> List[Dict[str,Any]]:
df = load_dataframe(csv_path)
done_keys: Set[str] = set()
header_written = False
if results_csv and os.path.exists(results_csv) and resume:
try:
existing = pd.read_csv(results_csv)
if 'key' in existing.columns:
done_keys = set(existing['key'].astype(str).tolist())
except Exception:
pass
iterator = df.iterrows()
if show_progress:
iterator = tqdm(df.iterrows(), total=len(df), desc="Processing", ncols=100)
results = []
for _, row in iterator:
rdict = {}
try:
key = _stable_key(row.to_dict())
if resume and key in done_keys:
continue
rdict = process_one(row.to_dict(), vllm, outroot, dry_run=dry_run)
except Exception as e:
rdict = {
'key': _stable_key(row.to_dict()),
'uid': row.to_dict().get('videoid', None),
'video_path': row.to_dict().get('source_video_path', None),
'edit_instruction_src': row.to_dict().get('chinese_instruction', '') or row.to_dict().get('instruction', ''),
'enhanced_instruction': '',
'sampling_mode': row.to_dict().get('sampling_mode',''),
'n_sampled': None,
'indices_json': '',
'mapping_json': '',
'tasks_json': '',
'latency_s': None,
'out_inspect': '',
'out_mask': '',
'out_json': '',
'raw_text_path': '',
'parsed_ok': False,
'error': str(e)
}
results.append(rdict)
if results_csv:
_append_results_csv(results_csv, rdict, header_written=header_written)
header_written = True
# 断点续做日志
ckpt_path = os.path.join(outroot, 'logs', 'pipeline_checkpoint.jsonl')
with open(ckpt_path, 'a', encoding='utf-8') as fp:
fp.write(json.dumps(rdict, ensure_ascii=False) + '\n')
return results