'use strict'; const $=id=>document.getElementById(id); const COLORS=['#2563eb','#db2777','#16a34a','#ea580c','#7c3aed','#0891b2','#ca8a04','#dc2626']; const LABELS={matched:'模型对应候选',missing_mask:'缺 mask · 待补',not_visible_in_reference:'参考帧未能定位',mask_quality_issue:'mask 质量待审',ambiguous:'身份对应含糊',requires_mask_union:'多 mask 分组待审'}; const REVIEW={unreviewed:'尚未审核',reviewed:'已审核并记录',needs_review:'需修改',reject:'不采用'}; const KEY='videoglamm-activitynet-object20-luna-review-v1'; const DATA_SHA='b3a7d7b4aeee343cc61a7a463abe83a5cab20efb590f6f3fda67e26f22714cfc'; const state={rows:[],filtered:[],cur:null,group:null,mask:null,frame:0,reviews:{},timer:null}; const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); const src=file=>`${state.cur.asset_dir}/${file}`; const currentGroup=()=>state.cur.groups.find(g=>g.group_id===state.group); const groupColor=g=>state.cur.source_masks.find(o=>o.mask_id===g.mask_id)?.color||'#a16207'; function stop(){if(state.timer)clearInterval(state.timer);state.timer=null;$('play').textContent='播放采样帧';} function reviewOf(id){return state.reviews[id]||{status:'unreviewed',note:''};} function filterRows(keep=true){ const q=$('search').value.trim().toLowerCase(),f=$('filter').value,id=keep?state.cur?.sample_id:null; state.filtered=state.rows.filter(r=>(f==='all'||reviewOf(r.sample_id).status===f)&&(!q||`${r.sample_id} ${r.caption} ${r.groups.map(g=>g.entity_label).join(' ')}`.toLowerCase().includes(q))); $('list-count').textContent=`${state.filtered.length} / ${state.rows.length} samples`; $('review-count').textContent=`本浏览器:${Object.values(state.reviews).filter(r=>r.status!=='unreviewed').length} / 20 已记录`; renderList(); if(!state.filtered.length){stop();state.cur=null;$('entry').hidden=true;$('loading').hidden=false;$('loading').textContent='没有符合筛选的样本。';$('prev').disabled=$('next').disabled=true;return;} if(!id||!state.filtered.some(r=>r.sample_id===id))load(state.filtered[0].sample_id);else renderList(); } function renderList(){ $('entry-list').innerHTML=state.filtered.map(r=>``).join(''); $('entry-list').querySelectorAll('button').forEach(b=>b.onclick=()=>load(b.dataset.id)); const i=state.filtered.indexOf(state.cur);$('prev').disabled=i<=0;$('next').disabled=i<0||i>=state.filtered.length-1; } function load(id){ stop();state.cur=state.rows.find(r=>r.sample_id===id);const r=state.cur;state.group=null;state.frame=r.reference_frame_id;state.mask=r.source_masks[0]?.mask_id||null; $('mode').value='all';$('group-filter').value='all';$('loading').hidden=true;$('entry').hidden=false; $('title').textContent=`${String(r.selection_index).padStart(2,'0')} · ${r.sample_id}`; $('meta').textContent=`10 张采样 RGB · ${r.source_masks.length} 个源 mask · ${r.groups.length} 个实体组 · 片段 ${r.source_timestamps.map(t=>t.toFixed(2)).join('–')} s`; $('badges').innerHTML=`${r.validation.structurally_valid?'字符 span 校验通过':'结构校验未通过 · 待修正'}${r.validation.complete_correspondence_candidate?'完整对应候选 · 待人工审':'部分对应 · 待人工审'}`; $('mask').innerHTML=r.source_masks.map(o=>``).join(''); $('objects').innerHTML=r.source_masks.map((o,i)=>``).join(''); $('objects').querySelectorAll('button').forEach(b=>b.onclick=()=>inspectMask(b.dataset.mask)); $('frames').innerHTML=Array.from({length:10},(_,i)=>``).join(''); $('frames').querySelectorAll('button').forEach(b=>b.onclick=()=>{stop();state.frame=Number(b.dataset.frame);renderMedia();}); const duration=r.source_timestamps[1]-r.source_timestamps[0];$('timing').textContent=`按源采样公式推算:间隔约 ${(duration/10).toFixed(3)} s(约 ${(10/duration).toFixed(3)} 张/秒);时间标签是估计值。`; $('issues').innerHTML=[...r.validation.errors.map(e=>`

结构校验错误 ${esc(e)}

`),...r.issues.map(i=>`

${esc(i.type)}${i.text?' · '+esc(i.text):''}
${esc(i.description)}

`),...r.model_review_reasons.map(t=>`

${esc(t)}

`),...r.validation.warnings.map(t=>`

本地检查:${esc(typeof t==='string'?t:JSON.stringify(t))}

`),...r.local_adjustments.map(t=>`

确定性整理(原模型输出保留):${esc(JSON.stringify(t))}

`)].join('')||'

没有额外问题记录;仍需人工复核实体完整性及对应。

'; const rev=reviewOf(id);$('review-status').value=rev.status;$('note').value=rev.note;$('saved').textContent=rev.updated_at?'本浏览器保存于 '+new Date(rev.updated_at).toLocaleString():''; $('original-refined').textContent=r.refined_caption_original;$('original-new').textContent=r.new_caption_original||'原字段不存在'; $('raw-json').textContent=JSON.stringify(r,null,2); $('links').innerHTML=`源完整 JSON未改动的 GPT 输出`; $('gpt-overlay').src=src('mask_overlay.jpg');$('gpt-identities').src=src('identity_sheet.jpg');$('gpt-binary').src=src('binary_mask_sheet.png'); renderList();renderCaption();renderGroups();renderDetail();renderMedia();window.scrollTo(0,0); } function renderCaption(){ const r=state.cur,chars=Array.from(r.caption),spans=[]; r.groups.forEach((g,gi)=>g.mentions.forEach(m=>{if(m.span)spans.push({s:m.span[0],e:m.span[1],g,gi});})); const pts=[...new Set([0,chars.length,...spans.flatMap(m=>[m.s,m.e])])].sort((a,b)=>a-b);$('caption').replaceChildren(); for(let i=0;im.s<=s&&e<=m.e),t=chars.slice(s,e).join(''); if(!active.length){$('caption').append(document.createTextNode(t));continue;} const selected=active.find(m=>m.g.group_id===state.group),a=selected||active[0],wrap=document.createElement('span'); wrap.className='mention'+(selected?' selected':'');wrap.style.setProperty('--c',groupColor(a.g));wrap.title=active.map(m=>`${m.g.group_id} · ${m.g.entity_label} · ${LABELS[m.g.grounding_status]}`).join('\n'); wrap.append(document.createTextNode(t));const sup=document.createElement('sup');sup.textContent=[...new Set(active.map(m=>m.g.group_id))].join(',');wrap.append(sup); wrap.onclick=()=>{const ids=[...new Set(active.map(m=>m.g.group_id))];selectGroup(ids[(ids.indexOf(state.group)+1)%ids.length]);};$('caption').append(wrap); } const g=currentGroup();$('selected-summary').textContent=g?`选中 ${g.group_id} · ${g.entity_label} · ${LABELS[g.grounding_status]};高亮包括该组全部提及。共享 / 重叠 span 可反复点击切换,或在下方直接选择组。`:'候选对应的文字与源 mask 同色;棕色为待补 / 待审。跨组 span 允许重叠,角标列出全部组。'; } function selectGroup(id){ stop();state.group=id;const g=currentGroup();state.frame=state.cur.reference_frame_id;state.mask=g.mask_id||null;$('mode').value=g.mask_id?'selected':'rgb'; renderCaption();renderGroups();renderDetail();renderMedia(); } function renderGroups(){ const filter=$('group-filter').value; $('groups').innerHTML=state.cur.groups.map((g,i)=>({g,i})).filter(({g})=>filter==='all'||(filter==='matched'?g.grounding_status==='matched':g.grounding_status!=='matched')).map(({g,i})=>``).join(''); $('groups').querySelectorAll('button').forEach(b=>b.onclick=()=>selectGroup(b.dataset.group)); } function renderDetail(){ const g=currentGroup();if(!g){$('group-detail').innerHTML='

点击 caption 或下方实体组查看对应。原始 mask 清单包含全部源 mask,可逐个检查。

';$('mask-preview').replaceChildren();return;} $('group-detail').innerHTML=`${esc(g.group_id+' · '+g.entity_label)}

${LABELS[g.grounding_status]} · ${esc(g.visibility)} · 模型信心 ${esc(g.confidence)}

${esc(g.reason)}

${g.mentions.map(m=>`
${esc(m.text)} · [${m.span?m.span.join(', '):'待确定'})
`).join('')}`; $('mask-preview').innerHTML=g.mask_id?`
该组的模型候选对应:${g.mask_id}(非人工认证)
实体组 ${g.group_id} 的候选源 mask ${g.mask_id}`:`
此实体没有已对应的源 mask。text 已保留,待补 / 待审。${g.candidate_mask_ids.length?' 候选 ID:'+g.candidate_mask_ids.join(', ')+';可在上方原始 mask 清单中独立检查。':''}
`; } function inspectMask(id){stop();state.mask=id;state.frame=state.cur.reference_frame_id;$('mode').value='selected';renderMedia();} function renderMedia(){ const r=state.cur,ref=state.frame===r.reference_frame_id,mode=$('mode').value,mid=state.mask; let file=String(state.frame).padStart(2,'0')+'.jpg',message='原始 RGB'; if(ref&&mode==='all'){file='all_overlay.jpg';message=`全部 ${r.source_masks.length} 个源 mask 叠加(颜色见清单)`;} if(ref&&mode==='selected'){if(mid){file=mid+'_overlay.jpg';message=`独立检查源 mask ${mid} · 叠加显示`;}else message='当前实体无对应 mask · 显示参考 RGB';} if(ref&&mode==='binary'){if(mid){file=mid+'_mask.png';message=`独立检查源 mask ${mid} · 二值图`;}else message='当前实体无对应 mask · 显示参考 RGB';} if(!ref)message='此采样帧无源 mask / bbox · 仅显示 RGB'; $('big-image').src=src(file);$('big-image').alt=`${r.sample_id} 帧 ${state.frame} · ${message}`; $('frame-state').textContent=`帧 ${String(state.frame).padStart(2,'0')} / 09 · 估计 ${r.estimated_frame_times_s[state.frame].toFixed(3)} s · ${ref?'MASK 参考帧 · ':''}${message}`; $('mask').value=mid||''; $('frames').querySelectorAll('button').forEach(b=>{b.classList.toggle('active',Number(b.dataset.frame)===state.frame);b.setAttribute('aria-pressed',String(Number(b.dataset.frame)===state.frame));}); $('objects').querySelectorAll('button').forEach(b=>b.classList.toggle('active',ref&&mode!=='all'&&mode!=='rgb'&&mid===b.dataset.mask)); } function persistReview(){ if(!state.cur)return;state.reviews[state.cur.sample_id]={status:$('review-status').value,note:$('note').value,updated_at:new Date().toISOString()}; try{localStorage.setItem(KEY,JSON.stringify({dataset_sha256:DATA_SHA,reviews:state.reviews}));$('saved').textContent='已保存在当前浏览器。请导出备份或汇总。';}catch(e){$('saved').textContent='浏览器保存失败;请立即导出审核备份。';} renderList();$('review-count').textContent=`本浏览器:${Object.values(state.reviews).filter(r=>r.status!=='unreviewed').length} / 20 已记录`; } $('search').oninput=()=>filterRows();$('filter').onchange=()=>filterRows(); $('prev').onclick=()=>load(state.filtered[Math.max(0,state.filtered.indexOf(state.cur)-1)].sample_id); $('next').onclick=()=>load(state.filtered[Math.min(state.filtered.length-1,state.filtered.indexOf(state.cur)+1)].sample_id); $('all-groups').onclick=()=>{state.group=null;renderCaption();renderGroups();renderDetail();}; $('group-filter').onchange=renderGroups; $('reference').onclick=()=>{stop();state.frame=state.cur.reference_frame_id;renderMedia();}; $('mode').onchange=()=>{stop();if($('mode').value!=='rgb')state.frame=state.cur.reference_frame_id;renderMedia();}; $('mask').onchange=()=>inspectMask($('mask').value); $('play').onclick=()=>{if(state.timer){stop();return;}state.frame=0;$('mode').value='rgb';renderMedia();$('play').textContent='暂停播放';state.timer=setInterval(()=>{if(state.frame>=9){stop();return;}state.frame++;renderMedia();},500);}; $('review-status').onchange=persistReview;$('note').oninput=persistReview; $('export').onclick=()=>{const x={schema:'videoglamm-activitynet-object20-luna-review-v1',dataset_sha256:DATA_SHA,exported_at:new Date().toISOString(),storage:'browser-local',source_annotations_unchanged:true,reviews:state.reviews};const u=URL.createObjectURL(new Blob([JSON.stringify(x,null,2)],{type:'application/json'})),a=document.createElement('a');a.href=u;a.download='videoglamm-activitynet-object20-luna-review.json';a.click();setTimeout(()=>URL.revokeObjectURL(u),2000);}; $('import').onclick=()=>$('import-file').click(); $('import-file').onchange=async()=>{try{const f=$('import-file').files[0];if(!f)return;const x=JSON.parse(await f.text());if(x.dataset_sha256!==DATA_SHA||!x.reviews||typeof x.reviews!=='object')throw new Error('数据版本或文件格式不匹配');const accepted={};for(const [id,r]of Object.entries(x.reviews)){if(!state.rows.some(a=>a.sample_id===id)||!r||!Object.hasOwn(REVIEW,r.status)||typeof r.note!=='string')throw new Error('存在无效样本或审核字段');accepted[id]={status:r.status,note:r.note,updated_at:r.updated_at||new Date().toISOString()};}for(const [id,r]of Object.entries(accepted)){const old=state.reviews[id];if(!old||Date.parse(r.updated_at)>=Date.parse(old.updated_at))state.reviews[id]=r;}localStorage.setItem(KEY,JSON.stringify({dataset_sha256:DATA_SHA,reviews:state.reviews}));filterRows();if(state.cur)load(state.cur.sample_id);alert('已合并审核记录;相同样本保留更新时间较新的记录。');}catch(e){alert('导入失败:'+e.message);}finally{$('import-file').value='';}}; async function init(){ const r=await fetch('data/annotations.json');if(!r.ok)throw new Error(`标注载入失败:${r.status}`);state.rows=await r.json(); try{const saved=JSON.parse(localStorage.getItem(KEY)||'null');if(saved?.dataset_sha256===DATA_SHA)state.reviews=Object.fromEntries(Object.entries(saved.reviews||{}).filter(([id,v])=>state.rows.some(r=>r.sample_id===id)&&v&&Object.hasOwn(REVIEW,v.status)&&typeof v.note==='string'));}catch(e){$('processing').textContent='浏览器存储不可用,请导出审核。';} filterRows(false); const receipt=await fetch('data/processing_receipt.json').then(r=>r.json());$('processing').textContent=`处理状态:${receipt.status} · ${receipt.request_counts.completed}/20 完成 · ${receipt.request_counts.failed} 失败。`; } init().catch(e=>{$('loading').hidden=false;$('loading').textContent=e.message;});