dashboard + dispatcher UX props, trim head+tail, cols, link direct
dashboard: - job_id, AUV GP1/GP2 (serial en tooltip), segment_label, duree reelle, nb frames, nb hors-eau trimes - lien viser plain <a href> (plus de POST ni popup). Affiche uniquement si job.done ET viser_url persistee (demo.py kept alive) - CSS minimal: flex row, separateurs, skipped en italic mute dispatcher: - trim head ET tail (AUV hors-eau en debut + fin de session) - migration DB: trimmed_head, trimmed_tail, video_duration_s - do_extract persiste total_duration_s + trimmed counts via set_status - run_one: RuntimeError(skipped_short) preserve le status=skipped - min_frames underwater pour skip les segments trop courts - ram_budget 0.45 -> 0.35 (OOM rc=137 avec 8237 frames sur 62GB RAM)
This commit is contained in:
17
app/main.py
17
app/main.py
@@ -146,12 +146,29 @@ def _build_acquisitions():
|
||||
"SELECT * FROM stitches ORDER BY level DESC, auv"
|
||||
).fetchall()
|
||||
|
||||
# Assign GP1/GP2 labels per AUV by enumerating distinct serials in fixed order.
|
||||
gp_by_serial: dict[tuple[int, str], str] = {}
|
||||
for j in jobs:
|
||||
key = (j["acquisition_id"], j["auv"])
|
||||
serials = gp_by_serial.setdefault(key, [])
|
||||
if j["gopro_serial"] not in serials:
|
||||
serials.append(j["gopro_serial"])
|
||||
gp_label: dict[tuple[int, str, str], str] = {}
|
||||
for (acq_id, auv), serials in gp_by_serial.items():
|
||||
for idx, ser in enumerate(sorted(serials)):
|
||||
gp_label[(acq_id, auv, ser)] = f"GP{idx + 1}"
|
||||
|
||||
by_acq: dict[int, list[dict]] = {}
|
||||
by_acq_total: dict[int, int] = {}
|
||||
for j in jobs:
|
||||
d = dict(j)
|
||||
dur_s = _job_duration_s(j)
|
||||
d["_duration"] = _fmt_dur(dur_s)
|
||||
d["gp_label"] = gp_label.get((j["acquisition_id"], j["auv"], j["gopro_serial"]), "?")
|
||||
d["video_duration_fmt"] = _fmt_dur(int(j["video_duration_s"] or 0)) if (j["video_duration_s"] or 0) > 0 else "—"
|
||||
d["trimmed_total"] = (j["trimmed_head"] or 0) + (j["trimmed_tail"] or 0)
|
||||
# Only expose a native viser link when port is listening. Probed on render via TCP check.
|
||||
d["native_viser_url"] = None # filled below
|
||||
by_acq.setdefault(j["acquisition_id"], []).append(d)
|
||||
by_acq_total[j["acquisition_id"]] = by_acq_total.get(j["acquisition_id"], 0) + dur_s
|
||||
|
||||
|
||||
@@ -115,3 +115,15 @@ button { background: transparent; color: var(--accent); border: 1px solid var(--
|
||||
button:hover { border-color: var(--accent); }
|
||||
a { color: var(--accent); }
|
||||
code { background: rgba(255,255,255,0.05); padding: 0 0.25rem; border-radius: 3px; }
|
||||
|
||||
/* Job row columns: id · AUV-GP · segment · meta · progress · viser */
|
||||
.job-item .label { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: 14px; }
|
||||
.job-item .job-id { font-family: monospace; color: var(--mut, #666); font-size: 12px; min-width: 32px; }
|
||||
.job-item .auv-gp { font-weight: 600; min-width: 100px; }
|
||||
.job-item .seg { color: var(--mut, #666); font-variant-numeric: tabular-nums; min-width: 90px; }
|
||||
.job-item .meta { color: var(--mut, #888); font-size: 12px; font-variant-numeric: tabular-nums; }
|
||||
.job-item .meta::before { content: '· '; opacity: 0.5; }
|
||||
.job-item .viser-link { text-decoration: none; padding: 2px 8px; border: 1px solid var(--accent, #4a9); border-radius: 3px; color: var(--accent, #4a9); font-size: 12px; }
|
||||
.job-item .viser-link:hover { background: var(--accent, #4a9); color: white; }
|
||||
.job-item.skipped { opacity: 0.55; }
|
||||
.job-item.skipped .label { font-style: italic; }
|
||||
|
||||
@@ -18,13 +18,15 @@
|
||||
{% else %}<span class="sq">■</span>{% endif %}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{ j.auv }}/{{ j.gopro_serial }}/{{ j.segment_label }}
|
||||
<span class="job-id">#{{ j.id }}</span>
|
||||
<span class="auv-gp" title="{{ j.gopro_serial }}">{{ j.auv }} {{ j.gp_label }}</span>
|
||||
<span class="seg">{{ j.segment_label }}</span>
|
||||
{% if j.video_duration_fmt != '—' %}<span class="meta">{{ j.video_duration_fmt }}{% if j.frame_count %} · {{ j.frame_count }} f{% if j.trimmed_total %} · −{{ j.trimmed_total }} hors-eau{% endif %}{% endif %}</span>{% endif %}
|
||||
{% if j.status in ('extracting','running') %}
|
||||
<span class="prog-wrap"><span class="prog-fill" style="width:{{ j.progress }}%"></span><span class="prog-text">{{ j.progress }}%</span></span>
|
||||
{% endif %}
|
||||
{% if j.status == 'done' and j.ply_path %}
|
||||
<button class="ext viewer-btn" data-view-url="jobs/{{ j.id }}/live" title="viser natif de demo.py (camera frustums, filtre confiance) — si encore alive">live</button>
|
||||
<button class="ext viewer-btn" data-view-url="jobs/{{ j.id }}/view" title="viser_ply.py standalone — toujours dispo">PLY</button>
|
||||
{% if j.status == 'done' and j.viser_url %}
|
||||
<a class="ext viser-link" href="{{ j.viser_url }}" target="_blank" rel="noopener" title="viser natif ({{ j.viser_url }})">viser</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="dur">{{ j._duration }}</span>
|
||||
|
||||
Reference in New Issue
Block a user