236 lines
18 KiB
HTML
236 lines
18 KiB
HTML
<!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="192채널 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">
|
||
<aside class="control-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>기본 예제를 쓰거나 파일을 순서대로 교체하세요.</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="현재 Scene과 Array를 참조하는 Experiment JSON을 선택하세요."><span class="file-sequence">3</span><span><strong>Experiment</strong><small>분석 설정</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><a class="builder-link" href="/apps/dataset-builder/">Builder에서 편집 →</a></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></div><strong id="microphone-count">— ch</strong></div>
|
||
<label class="field"><span>배열 선택</span><select id="array-preset"><option value="rectangular-16x12">16×12 rectangular · 기준</option><option value="fermat-spiral-192">Fermat spiral · 비교</option><option value="custom">불러온 Array JSON</option></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"><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>
|
||
</div>
|
||
<p class="workload-summary" id="workload-summary">연산량 계산 중…</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>
|
||
<label class="field field--wide"><span>후처리</span><select id="postprocess"><option value="none">없음 · raw DAS</option><option disabled>DAMAS · 후속 milestone</option><option disabled>CLEAN-SC · 후속 milestone</option></select></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>
|
||
|
||
<section class="scene-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">이동 예제 열기</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">실행 후 위치 오차, energy 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>DAS · beam spread · live</small></summary>
|
||
<div class="help-grid">
|
||
<p><strong>Heatmap</strong>DAS 공간 power이며 위치 확률분포가 아닙니다.</p>
|
||
<p><strong>빔 분포</strong>Half-power covariance 폭이며 신뢰구간이 아닙니다.</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>
|