SolusOps/fatima / notebook_backend /presentation.py
SolusOps's picture
download
raw
7.33 kB
"""Presentation-only HTML helpers. Original observations and scores are untouched."""
import difflib
import html
from pathlib import Path
LABELS = {'english': 'English', 'hindi': 'Hindi', 'hinglish': 'Roman Hinglish',
'french': 'French', 'chinese': 'Chinese'}
TASKS = {0: 'Middle drawer', 1: 'Bowl on stove', 2: 'Bottle on cabinet',
3: 'Top drawer and bowl', 4: 'Bowl on cabinet', 5: 'Push plate',
6: 'Cream cheese in bowl', 7: 'Turn on stove', 8: 'Bowl on plate',
9: 'Bottle on rack'}
CSS = '''<style>
.research {font-family:system-ui,sans-serif;color:#252b32;line-height:1.5}
.research p {margin:.4rem 0 .7rem}.research h4{font-size:1rem;margin:.35rem 0}
.research details {margin:.4rem 0}.research summary {cursor:pointer;font-size:.9rem;color:#385d79}
.research pre {white-space:pre-wrap;overflow-wrap:anywhere;font-size:.85rem;max-height:360px;overflow:auto}
.research img {max-width:100%;height:auto}.research figure{margin:0}
.research figcaption{font-size:.85rem;color:#505b66;margin-top:.35rem}
.research .comparison {display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:20px;margin:1rem 0}
.research .media-comparison {grid-template-columns:repeat(auto-fit,minmax(260px,420px))}
.research .image-answer {display:grid;grid-template-columns:minmax(230px,32%) minmax(0,1fr);gap:22px;margin:1rem 0 1.6rem}
.research .image-answer>figure {align-self:start}.research .image-answer img {width:100%;max-height:330px;object-fit:contain}
.research .views {display:grid;grid-template-columns:1fr 1fr;gap:16px;margin:.7rem 0}
.research .views img {width:100%;image-rendering:auto;max-height:360px;object-fit:contain}
.research .scroll {overflow-x:auto;width:100%;margin:.8rem 0}
.research table {border-collapse:collapse;width:100%;text-align:left;font-size:.9rem}
.research table th,.research table td {padding:9px 12px;border-bottom:1px solid #dbe0e5;vertical-align:top;text-align:left!important}
.research .prediction-table {table-layout:fixed}.research .prediction-table th:nth-child(1){width:14%}.research .prediction-table th:nth-child(2){width:48%}.research .prediction-table th:nth-child(3){width:12%}.research .prediction-table th:nth-child(4){width:26%}
.research th {background:#f5f7f9;font-weight:600}.research .scroll table{min-width:660px}
.research .matrix-scroll {overflow-x:auto;padding-bottom:16px}
.research .rollout-matrix {display:grid;grid-template-columns:repeat(5,minmax(230px,1fr));gap:14px;min-width:1220px}
.research .matrix-heading {border-bottom:2px solid #bcc8d1;padding:8px 6px;font-size:.95rem;min-height:86px}
.research .matrix-heading p{font-size:.85rem;margin:.3rem 0}
.research .video-card{min-width:0;border-bottom:1px solid #dbe0e5;padding:6px 0 12px}
.research video{width:100%;height:auto;aspect-ratio:1;object-fit:contain;background:#f1f3f5;display:block;margin:8px 0}
.research .video-meta{display:flex;justify-content:space-between;gap:8px;font-size:.85rem}
.research .outcome{font-weight:600}.research .failure{color:#795242}.research .success{color:#275c73}
.research .video-card>a{font-size:.85rem}.research .instruction{font-size:.9rem;min-height:2.8em}
.research mark{background:#e8edf4;color:#204b72;font-weight:600;padding:0 2px}
.research .frames {display:grid;grid-template-columns:repeat(4,minmax(170px,1fr));gap:10px;min-width:740px}
.research .frames img{width:100%}.research .note{font-size:.9rem;color:#505b66}
@media(max-width:760px){.research .image-answer{grid-template-columns:1fr}.research .comparison{grid-template-columns:1fr}.research .views{grid-template-columns:1fr 1fr;gap:8px}}
@media print{.research video{display:none}.research .rollout-matrix{min-width:0;grid-template-columns:repeat(5,1fr)}.research .matrix-scroll{overflow:visible}}
</style>'''
def esc(value):
# Display punctuation only. Exact source strings remain in linked raw records.
return html.escape(str(value).replace('\u2014', '; '))
def wrap(body): return '<div class="research">' + body + '</div>'
def exact_block(text, label='Saved prompt / response'):
return '<details><summary>' + esc(label) + '</summary><pre>' + esc(text) + '</pre></details>'
def table(frame): return wrap('<div class="scroll">' + frame.to_html(index=False,escape=True).replace('\u2014','; ') + '</div>')
def highlight_change(original, modified):
left, right = original.split(), modified.split()
chunks=[]
for tag,a,b,c,d in difflib.SequenceMatcher(None,left,right,autojunk=False).get_opcodes():
if c==d: continue
words=esc(' '.join(right[c:d]))
chunks.append(words if tag=='equal' else '<mark>'+words+'</mark>')
return ' '.join(chunks)
def select_matrix(records, tasks, states=range(20,25)):
index={}
for r in records:
if r.get('condition',r.get('language')) != 'hinglish': continue
key=(r['task_id'],r['initial_state_index'])
if key in index: raise ValueError('Duplicate Hinglish task/state')
index[key]=r
try: return [index[t,s] for s in states for t in tasks]
except KeyError as e: raise ValueError('Missing Hinglish matrix episode') from e
def card(r, title=None, instruction=True):
outcome = '' if r.get('success') is None else ('Success' if r['success'] else 'Failure')
cls = 'success' if r.get('success') else 'failure'
heading='<h4>'+esc(title)+'</h4>' if title else ''
text='<p class="instruction">'+esc(r['instruction'])+'</p>' if instruction else ''
poster=r.get('poster',r.get('initial_frame',''))
full=r.get('full_recording',r['clip'])
return '<article class="video-card">'+heading+text+'<div class="video-meta"><span>Task '+str(r['task_id'])+' · State '+str(r['initial_state_index'])+'</span><span class="outcome '+cls+'">'+outcome+'</span></div><video controls preload="none" playsinline poster="'+esc(poster)+'" src="'+esc(r['clip'])+'">Video playback unavailable.</video><a href="'+esc(full)+'">Full recording</a> · <a href="'+esc(poster)+'">Still image</a></article>'
def video_row(records, titles=None):
titles=titles or [LABELS.get(r.get('language',r.get('condition')), 'French') for r in records]
return wrap('<div class="comparison media-comparison">'+''.join(card(r,t) for r,t in zip(records,titles))+'</div>')
def matrix_html(records, tasks):
records=select_matrix(records,tasks)
heads=[]
for t in tasks:
r=next(r for r in records if r['task_id']==t)
heads.append('<header class="matrix-heading"><strong>Task '+str(t)+': '+TASKS[t]+'</strong><p>'+esc(r['instruction'])+'</p></header>')
return wrap('<div class="matrix-scroll"><div class="rollout-matrix">'+''.join(heads)+''.join(card(r,instruction=False) for r in records)+'</div></div>')
def views(external,wrist,caption='Simultaneous camera observations'):
return wrap('<div class="views"><figure><img src="'+esc(external)+'"><figcaption>External camera</figcaption></figure><figure><img src="'+esc(wrist)+'"><figcaption>Wrist camera</figcaption></figure></div><p class="note">'+esc(caption)+'</p>')
def frame_row(record):
return wrap('<div class="scroll"><div class="frames">'+''.join('<figure><img src="'+esc(p)+'"><figcaption>'+esc(label)+' · '+f'{t:.1f}s'+'</figcaption></figure>' for p,t,label in zip(record['key_frames'],record['key_frame_seconds'],record['key_frame_labels']))+'</div></div>')

Xet Storage Details

Size:
7.33 kB
·
Xet hash:
c37182e49b5528c0e26c1f29ac49feb7046f3d25ae364914849c3a9b2b0a9cf9

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.