Files
micarrsim/apps/acoustic-camera/index.html
T

277 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="다중 채널 CPU·WebGPU acoustic camera simulator" />
<title>Acoustic Camera · micarrsim</title>
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
<link rel="stylesheet" href="/apps/acoustic-camera/styles.css" />
</head>
<body>
<div class="app-shell camera-shell">
<header class="app-header camera-header">
<a class="app-brand" href="/"><strong>micarrsim</strong><span>Acoustic Camera</span></a>
<div class="header-status">
<span class="run-status" id="run-status">기본 입력 준비 중…</span>
<span class="status-chip status-chip--neutral" data-runtime-status>런타임 확인 중…</span>
</div>
<div class="header-actions">
<button class="button button--ghost is-hidden" id="cancel-button" type="button">계산 취소</button>
<button class="button button--ghost" id="export-result-button" type="button" disabled>Result JSON</button>
</div>
</header>
<nav class="workflow-strip" id="camera-workflow" aria-label="Acoustic Camera 작업 순서">
<a href="#input-step" data-workflow-step="1"><span>1</span><strong>입력</strong><small>Scene · Array</small></a>
<a href="#configure-step" data-workflow-step="2"><span>2</span><strong>설정</strong><small>Grid · Band</small></a>
<a href="#run-step" data-workflow-step="3"><span>3</span><strong>실행</strong><small>정지 · 이동</small></a>
<a href="#inspect-step" data-workflow-step="4"><span>4</span><strong>확인</strong><small>Map · 지표</small></a>
</nav>
<main class="camera-layout" id="camera-layout" data-split-layout>
<aside class="control-panel" id="camera-controls" data-split-panel aria-label="입력 및 시뮬레이션 설정">
<section class="workflow-card input-card" id="input-step" data-workflow-section="1">
<div class="step-heading">
<span class="step-number">1</span>
<div><h2>입력 준비</h2><p>기본 예제를 쓰거나 파일을 교체하세요. Experiment부터 열어도 대역은 유지됩니다.</p></div>
</div>
<div class="workspace-tools input-file-grid" aria-label="입력 파일">
<label class="file-button file-choice" title="Scene JSON을 선택하세요."><span class="file-sequence">1</span><span><strong>Scene</strong><small>장면·음원</small></span><input id="scene-input" type="file" accept="application/json,.json" /></label>
<label class="file-button file-choice" title="사용자 배열이 있을 때 Array JSON을 선택하세요."><span class="file-sequence">2</span><span><strong>Array</strong><small>마이크 배열</small></span><input id="array-input" type="file" accept="application/json,.json" /></label>
<label class="file-button file-choice" title="Experiment를 먼저 열 수 있습니다. sceneId와 arrayId provenance label은 보존되고 현재 열린 Scene·Array가 실행 입력이 됩니다."><span class="file-sequence">3</span><span><strong>Experiment</strong><small>분석 설정 · ID 보존</small></span><input id="experiment-input" type="file" accept="application/json,.json" /></label>
<label class="file-button file-choice" title="Scene에 사용한 원본 파일을 다시 선택하세요."><span class="file-sequence">4</span><span><strong>Assets</strong><small>오디오·이미지</small></span><input id="asset-input" type="file" accept="audio/*,image/*,.wav,.flac,.mp3,.ogg,.m4a,.png,.jpg,.jpeg,.webp,.svg" multiple /></label>
</div>
<div class="input-summary">
<div class="compact-section-heading"><h3>현재 장면</h3><div class="example-actions"><button class="mini-button" id="load-music-example-button" type="button" title="12채널·단일 FFT-grid carrier·Fixed K=1인 빠른 표준 MUSIC 예제를 엽니다.">MUSIC 빠른 예제</button><a class="builder-link" href="/apps/dataset-builder/" target="_blank" rel="noopener" title="현재 Camera의 Experiment를 유지한 채 새 탭에서 Scene을 편집합니다. 저장 후 이 Camera 탭으로 돌아와 Scene JSON을 여세요.">Builder 새 탭 ↗</a></div></div>
<div class="source-summary" id="source-summary"></div>
<div class="asset-list" id="asset-list"></div>
</div>
<div class="array-card">
<div class="array-heading"><div><span class="compact-label">MIC ARRAY</span><h3 id="array-title">배열</h3><a class="builder-link" href="/apps/array-builder/" target="_blank" rel="noopener" title="현재 Camera의 Experiment를 유지한 채 새 탭에서 배열을 편집합니다. 저장 후 이 Camera 탭으로 돌아와 Array JSON을 여세요.">Array Builder 새 탭 ↗</a></div><strong id="microphone-count">— ch</strong></div>
<label class="field">
<span>배열 선택</span>
<select id="array-preset">
<optgroup label="기준 배열">
<option value="rectangular-16x12">16×12 rectangular · 192 ch / 0.45 m</option>
<option value="fermat-spiral-192">Fermat spiral · 192 ch / Ø 0.45 m</option>
</optgroup>
<optgroup label="채널 수 비교 · 0.45 m">
<option value="rectangular-2x2-450mm">2×2 sparse · 4 ch</option>
<option value="rectangular-4x3-450mm">4×3 sparse · 12 ch</option>
<option value="rectangular-8x6-450mm">8×6 · 48 ch</option>
<option value="rectangular-24x16-450mm">24×16 dense · 384 ch</option>
</optgroup>
<optgroup label="물리 크기 비교 · 192 ch">
<option value="rectangular-16x12-compact">16×12 compact · 0.12 m</option>
<option value="rectangular-16x12-large">16×12 large · 1.20 m</option>
</optgroup>
<optgroup label="사용자 배열">
<option value="custom">불러온 Array JSON</option>
</optgroup>
</select>
</label>
<details class="inline-details" id="array-details">
<summary>배열 미리보기와 좌표 정보</summary>
<canvas id="array-canvas" aria-label="마이크 배열 평면도"></canvas>
<p id="array-meta">배열을 불러오고 있습니다.</p>
</details>
</div>
</section>
<section class="workflow-card configure-card" id="configure-step" data-workflow-section="2">
<div class="step-heading">
<span class="step-number">2</span>
<div><h2>분석 설정</h2><p>자주 쓰는 항목만 먼저 조정합니다.</p></div>
</div>
<div class="quick-field-grid">
<label class="field field--wide"><span>위치 추정 알고리즘</span><select id="algorithm"><option value="das">DAS · 기준 power</option><option value="das-phat">DAS-PHAT · 위상 기반</option><option value="gcc-phat">GCC-PHAT · TDOA 기반 / CPU</option><option value="music">MUSIC · noise subspace / CPU</option></select></label>
<label class="field"><span>Backend</span><select id="backend"><option value="auto">Auto (GPU→CPU)</option><option value="webgpu">WebGPU 강제</option><option value="cpu">CPU 강제</option></select></label>
<label class="field"><span>Grid preset</span><select id="grid-preset"><option value="50">50×50 · 기준</option><option value="100">100×100 · 4× points</option><option value="200">200×200 · 16× points</option><option value="custom">직접 입력</option></select></label>
<label class="field"><span>Scene 거리 z (m)</span><input id="scene-distance" type="number" min="0.01" step="0.1" /></label>
<label class="field" title="최강 peak 대비 검출 임계값입니다. 낮추면 약한 음원을 더 찾지만 sidelobe 오검출도 늘 수 있습니다."><span>Peak 임계값 (%)</span><input id="detection-threshold" type="number" min="1" max="100" step="1" /></label>
<label class="field" title="원시 map은 바꾸지 않고, 임계값 위 영역을 peak로 묶는 표준 방식을 선택합니다."><span>Peak 묶기</span><select id="detection-method"><option value="local-max">Local maxima</option><option value="connected-components">8-connected components</option><option value="watershed">Marker watershed</option></select></label>
</div>
<div class="algorithm-options" id="music-options" hidden>
<label class="field"><span>Source count 방식</span><select id="music-source-count-method"><option value="fixed">Fixed K</option><option value="mdl">Wax–Kailath MDL</option><option value="aic">Wax–Kailath AIC</option></select></label>
<label class="field"><span>Source count K</span><input id="music-source-count" type="number" min="1" max="1023" step="1" /></label>
<p id="music-options-note">각 band는 ADC FFT bin 하나만 선택합니다. Fixed K는 마이크 수보다 작아야 합니다.</p>
</div>
<p class="workload-summary" id="workload-summary">연산량 계산 중…</p>
<p class="sampling-warning" id="sampling-warning" hidden></p>
<div class="band-config">
<div class="compact-section-heading"><h3>주파수 대역</h3><button class="mini-button" id="add-band-button" type="button">+ 대역</button></div>
<div class="band-editor-list" id="band-list"></div>
</div>
<details class="advanced-settings" id="advanced-settings">
<summary><span>고급 설정</span><small>음향 · 범위 · STFT · 식별자</small></summary>
<div class="advanced-settings__body">
<div class="advanced-group">
<h3>실험과 환경</h3>
<div class="advanced-field-grid">
<label class="field field--wide"><span>이름</span><input id="experiment-name" type="text" maxlength="120" /></label>
<label class="field field--wide"><span>Experiment ID</span><input id="experiment-id" type="text" maxlength="120" /></label>
<label class="field"><span>음속 (m/s)</span><input id="speed-of-sound" type="number" min="250" max="400" step="0.1" /></label>
<label class="field"><span>Seed</span><input id="scene-seed" type="number" min="0" max="4294967295" step="1" /></label>
<label class="field field--wide"><span>음향 환경</span><select id="acoustic-profile"><option value="free-field">자유음장 · 직접음만</option><option value="treated-room">흡음된 실내 · 짧은 잔향</option><option value="typical-room">일반 실내 · 균형</option><option value="reflective-room">반사 강한 실내</option><option value="custom" disabled>Scene 사용자 설정</option></select></label>
<label class="field field--wide"><span>마이크 주파수 응답 처리</span><select id="frequency-response-mode"><option value="compensate">측정 curve 적용 + 위치추정에서 역보정</option><option value="raw">측정 curve 적용 + 보정하지 않음</option></select></label>
</div>
<p class="acoustic-environment-summary" id="acoustic-environment-summary">음향 모델을 확인하고 있습니다.</p>
</div>
<div class="advanced-group">
<h3>Scan 범위</h3>
<div class="advanced-field-grid">
<label class="field"><span>Columns</span><input id="scan-columns" type="number" min="8" max="512" step="1" /></label>
<label class="field"><span>Rows</span><input id="scan-rows" type="number" min="8" max="512" step="1" /></label>
<label class="field"><span>x min (m)</span><input id="scan-x-min" type="number" step="0.05" /></label>
<label class="field"><span>x max (m)</span><input id="scan-x-max" type="number" step="0.05" /></label>
<label class="field"><span>y min (m)</span><input id="scan-y-min" type="number" step="0.05" /></label>
<label class="field"><span>y max (m)</span><input id="scan-y-max" type="number" step="0.05" /></label>
</div>
</div>
<div class="advanced-group">
<h3>STFT와 표시</h3>
<div class="advanced-field-grid">
<label class="field"><span>FFT size</span><select id="fft-size"><option>128</option><option>256</option><option selected>512</option><option>1024</option><option>2048</option><option>4096</option></select></label>
<label class="field"><span>Hop size</span><input id="hop-size" type="number" min="1" max="4096" step="1" /></label>
<label class="field"><span>Map 정규화</span><select id="normalization"><option value="peak">Peak</option><option value="none">없음</option></select></label>
<label class="field"><span>Dynamic range (dB)</span><input id="dynamic-range" type="number" min="6" max="120" step="1" /></label>
</div>
</div>
</div>
</details>
<details class="output-card utility-details" id="document-tools">
<summary>Scene · Experiment 문서 저장</summary>
<div class="output-actions">
<button class="button button--ghost" id="export-scene-button" type="button">Scene JSON</button>
<button class="button button--ghost" id="export-experiment-button" type="button">Experiment JSON</button>
</div>
<p class="output-save-state" id="output-save-state">저장하지 않은 변경 없음</p>
</details>
</section>
<section class="validation-card" aria-live="polite">
<div class="validation-dot" id="validation-dot"></div>
<div><strong id="validation-title">검증 대기</strong><p id="validation-message">입력을 준비하고 있습니다.</p></div>
</section>
</aside>
<div
class="split-handle"
id="camera-split-handle"
role="separator"
tabindex="0"
aria-label="분석 설정 패널 너비 조절"
aria-controls="camera-controls"
aria-orientation="vertical"
data-split-resizer
data-storage-key="micarrsim.camera.control-panel-width"
title="드래그하거나 방향키로 너비 조절 · Enter/더블클릭으로 초기화"
></div>
<section class="scene-workspace" id="camera-workspace" aria-label="Acoustic image workspace">
<div class="workspace-heading">
<div><span class="compact-label">CURRENT SCENE</span><h1 id="workspace-title">음향 장면</h1></div>
<p class="workspace-meta" id="scene-meta">장면을 불러오고 있습니다.</p>
</div>
<section class="run-stage" id="run-step" data-workflow-section="3" aria-labelledby="run-step-title">
<div class="step-heading step-heading--horizontal">
<span class="step-number">3</span>
<div><h2 id="run-step-title">실행 방식 선택</h2><p>한 장을 계산할지, 이동 경로를 재생할지 선택하세요.</p></div>
</div>
<div class="mode-grid">
<article class="mode-card mode-card--snapshot" id="snapshot-mode">
<div class="mode-heading">
<div><span class="mode-kicker">SNAPSHOT</span><h3>정지 장면</h3></div>
<span class="mode-format">1 map</span>
</div>
<p>현재 위치와 설정으로 전체 장면을 한 번 분석합니다.</p>
<div class="mode-actions">
<button class="button button--primary button--full" id="run-button" type="button" disabled>▶ Snapshot 실행</button>
</div>
</article>
<article class="mode-card mode-card--moving live-panel" id="moving-mode" aria-labelledby="live-title">
<div class="mode-heading">
<div><span class="mode-kicker">MOVING TIMELINE</span><h3 id="live-title">이동 장면</h3></div>
<span class="mode-format">여러 map</span>
</div>
<p id="live-status" aria-live="polite">trajectory가 있는 Scene을 불러오거나 이동 예제를 여세요.</p>
<div class="live-actions">
<button class="button button--ghost" id="load-moving-example-button" type="button" title="Scene·Array·Experiment와 분석 대역을 이동 예제 전체로 교체합니다.">이동 예제 전체 열기</button>
<button class="button button--primary" id="prepare-live-button" type="button" disabled>Map 준비</button>
<button class="button button--ghost" id="live-play-button" type="button" disabled>▶ 재생</button>
</div>
<div class="live-controls">
<label class="live-scrubber"><span id="live-time-label">0.00 / 0.00 s</span><input id="live-time" type="range" min="0" max="0" step="0.001" value="0" disabled /></label>
<label class="compact-select"><span>Map rate</span><select id="live-map-rate"><option value="2" selected>2 Hz</option><option value="5">5 Hz</option><option value="8">8 Hz</option></select></label>
<label class="compact-select"><span>속도</span><select id="live-speed"><option value="0.5">0.5×</option><option value="1" selected>1×</option><option value="2">2×</option></select></label>
</div>
<details class="mode-help"><summary>동작 방식과 한계</summary><p class="live-note">짧은 분석창 중앙의 위치를 쓰는 준정적 시뮬레이션입니다. Heatmap과 위치를 frame 사이에서 보간하며 실제 마이크 스트림 입력은 아닙니다.</p></details>
</article>
</div>
</section>
<section class="inspect-stage" id="inspect-step" data-workflow-section="4" aria-labelledby="inspect-title">
<div class="inspect-heading">
<div class="step-heading step-heading--horizontal">
<span class="step-number">4</span>
<div><h2 id="inspect-title">Map과 결과 확인</h2><p>Canvas가 위치추정 응답과 추정 위치를 표시합니다.</p></div>
</div>
<span class="result-chip" id="result-badge">아직 실행하지 않음</span>
</div>
<div class="scene-canvas-card">
<canvas id="scene-canvas" aria-label="Ground truth와 주파수별 acoustic image"></canvas>
<div class="canvas-message" id="canvas-message"><strong>실행할 준비가 되면 위에서 모드를 선택하세요.</strong><span>결과 heatmap은 이 canvas에 표시됩니다.</span></div>
<div class="canvas-badge" id="canvas-badge">결과 없음</div>
</div>
<div class="overlay-toolbar">
<div class="band-legend" id="band-legend" aria-label="표시 주파수 대역"></div>
<label class="range-field"><span>Overlay</span><input id="overlay-opacity" type="range" min="0" max="1" step="0.05" value="0.72" /></label>
<label class="check-field" title="점선은 1× RMS, 실선 영역은 2× RMS 빔 응답 분포입니다. 확률 또는 신뢰구간은 아닙니다."><input id="show-beam-spread" type="checkbox" checked /><span>빔 분포</span></label>
<label class="check-field"><input id="show-truth" type="checkbox" checked /><span>Ground truth</span></label>
</div>
<section class="result-section" aria-live="polite">
<div class="compact-section-heading"><h3>측정 요약</h3><span>peak · 오차 · beam</span></div>
<div class="empty-result" id="empty-result">실행 후 위치 오차, truth concentration, sidelobe와 timing을 표시합니다.</div>
<div class="metric-grid is-hidden" id="metric-grid"></div>
</section>
<details class="history-section utility-details" id="comparison-history">
<summary><span>비교 기록</span><small>배열 · 알고리즘 재실행 결과</small></summary>
<div class="history-body">
<div class="history-actions"><button class="text-button" id="export-history-button" type="button" disabled>전체 기록 JSON</button><button class="text-button" id="clear-history-button" type="button">기록 비우기</button></div>
<p class="section-copy">같은 scene·scan·STFT에서 배열이나 알고리즘을 바꾼 결과를 비교합니다.</p>
<div class="history-list" id="history-list"></div>
</div>
</details>
<details class="help-panel utility-details" id="camera-help">
<summary><span>표시 해석과 한계</span><small>localization · beam spread · live</small></summary>
<div class="help-grid">
<p><strong>Heatmap</strong>DAS는 power, PHAT 계열은 무차원 score이며 위치 확률분포가 아닙니다.</p>
<p><strong>빔 분포</strong>Half-power 또는 half-maximum 응답 폭이며 신뢰구간이 아닙니다.</p>
<p><strong>이동 재생</strong>준정적 frame 시뮬레이션이며 실제 장치 스트림이 아닙니다.</p>
</div>
</details>
</section>
</section>
</main>
</div>
<script type="module" src="/packages/ui/runtime-status.js"></script>
<script type="module" src="/apps/acoustic-camera/main.js"></script>
</body>
</html>