docs: add quick start and user guides

This commit is contained in:
Codex
2026-08-28 12:47:57 +09:00
parent 3317a03d1a
commit 513de2b51f
16 changed files with 1148 additions and 65 deletions
+168 -20
View File
@@ -1,27 +1,175 @@
# micarrsim # micarrsim
192채널 MEMS 마이크 배열의 배치와 음향 영상 성능을 평가하기 위한 WebGPU 기반 시뮬레이터입니다. 192채널 MEMS 마이크 배열의 배치와 자유음장 DAS 음향 영상을 비교하는 로컬 브라우저
시뮬레이터입니다. Dataset Builder에서 재현 가능한 음향 장면을 만들고 Acoustic Camera에서
CPU 또는 WebGPU로 배열 응답, acoustic map과 위치 검출 지표를 계산합니다.
현재는 설계 단계입니다. 프로젝트 목표는 [`goal.md`](goal.md), 구현 로드맵과 기술 결정은 별도 프로그램이나 패키지를 설치하지 않습니다. 모든 앱과 계산은 이 저장소와 브라우저 안에서
[`docs/execution-plan.md`](docs/execution-plan.md)를 참고하세요. 실행됩니다.
## 디렉터리 ## 실행 환경
```text - Windows 10/11
apps/ - 최신 Microsoft Edge 또는 Google Chrome
dataset-builder/ 음원 합성 및 ground truth 생성 UI - 저장소에 포함된 Windows PowerShell과 브라우저 외 추가 런타임 불필요
acoustic-camera/ 전파, DAC 빔포밍, 평가 및 시각화 UI - WebGPU가 없거나 장치를 열 수 없어도 CPU backend로 실행 가능
packages/
acoustics-core/ 공통 좌표계, 음향 모델, 평가 로직 ## 가장 간단하게 시작하기
webgpu-kernels/ WGSL 기반 steering/DAC 연산
schemas/ 배열·장면·실험 JSON Schema 1. 저장소 최상위의 `start.cmd`를 더블클릭합니다.
assets/ 2. 기본 브라우저에서 micarrsim 시작 화면이 열립니다.
array-geometries/ 마이크 배열 예제 3. 사용을 마치면 함께 열린 콘솔에서 `Ctrl+C`를 눌러 로컬 서버를 종료합니다.
sample-scenes/ 재배포 가능한 소규모 예제 장면
tests/ 브라우저가 자동으로 열리지 않으면 <http://127.0.0.1:4173/>을 직접 여세요. HTML module과
unit/ 수치 모델 단위 테스트 Worker를 사용하므로 `index.html` 파일을 탐색기에서 직접 열어서는 안 됩니다.
integration/ 앱 간 데이터 계약과 기준 시나리오 테스트
docs/ 설계 및 실행 계획 다른 포트를 쓰거나 브라우저를 자동으로 열지 않으려면 명령 프롬프트에서 실행합니다.
```bat
start.cmd -Port 4174 -NoBrowser
``` ```
원본 DCASE/MIMII 데이터와 생성된 대용량 결과는 저장소에 커밋하지 않습니다. PowerShell에서 서버를 직접 실행할 수도 있습니다.
```powershell
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\serve.ps1 -Port 4174 -NoBrowser
```
이 명령의 `ExecutionPolicy Bypass`는 실행 중인 PowerShell 프로세스에만 적용되며 시스템 정책을
영구 변경하지 않습니다. 해당 명령을 사용했다면 <http://127.0.0.1:4174/>을 여세요.
실행 후 브라우저용 빠른 시작과 문제 해결은 <http://127.0.0.1:4173/docs/>에서도 볼 수 있습니다.
### 30초 기본 예제
1. 시작 화면에서 **Acoustic Camera**를 엽니다.
2. 기본 192채널 rectangular 배열, single-tone scene과 50×50 experiment가 준비될 때까지
기다립니다.
3. **기본 예제 실행**을 누릅니다.
4. 계산이 끝나면 overlay와 평가 지표를 확인하고 **Result JSON**을 누릅니다.
실제 완료 시간은 브라우저와 장치에 따라 다릅니다. CPU fallback에서는 WebGPU보다 오래 걸릴 수
있습니다.
## 내 장면으로 전체 흐름 실행하기
### 1. Dataset Builder에서 Scene 만들기
1. 시작 화면에서 **Dataset Builder**를 엽니다.
2. 선택적으로 배경 이미지와 오디오를 추가합니다.
3. 음원을 추가해 canvas 또는 x/y/z 입력으로 위치를 정하고, tone·noise·audio asset과 ambient를
설정합니다.
4. 상태가 **저장 가능한 장면**이면 **Scene JSON 저장**을 누릅니다.
**Preview WAV 저장**은 source와 ambient를 공간 정보 없이 하나의 mono channel로 합친 청취
확인용 파일입니다. 192채널 마이크 신호나 beamforming 결과가 아닙니다.
### 2. Acoustic Camera에서 실행하기
1. 홈으로 돌아가 **Acoustic Camera**를 엽니다.
2. 다음 순서로 입력합니다: **Scene → Array → Experiment → Assets**.
3. **Scene**에서 Builder가 저장한 `.scene.json`을 선택합니다.
4. 기본 배열을 사용하지 않을 때만 **Array**에서 `.array.json`을 선택합니다.
5. 별도 실험 문서를 사용할 때만 **Experiment**에서 `.experiment.json`을 선택합니다.
6. Scene에 이미지나 audio asset이 있다면 **Assets**에서 Builder에 사용했던 원본 파일을 다시
선택합니다. 여러 파일을 한 번에 선택할 수 있습니다.
7. 입력 검증이 통과하면 backend와 scan grid를 정하고 실행합니다.
8. 완료 후 **Result JSON**을 저장합니다.
Array와 Scene을 먼저 불러와야 Experiment의 `arrayId`와 `sceneId` 관계를 안전하게 검증할 수
있습니다. Scene을 다시 불러오면 이전 asset 연결은 해제됩니다.
## Scene JSON과 로컬 assets
Scene JSON에는 원본 이미지나 오디오 byte가 들어가지 않습니다. 대신 asset id, 종류, 파일명과
SHA-256 manifest를 저장합니다. 브라우저는 새 페이지나 세션에서 로컬 파일 접근 권한을 유지할 수
없으므로 원본 파일을 다시 선택해야 합니다.
- Camera는 SHA-256으로 선택한 파일을 manifest에 다시 연결합니다.
- 같은 이름이라도 내용이 달라 hash가 일치하지 않으면 거절합니다.
- 로컬 asset 한 파일은 최대 128 MiB, 연결된 파일 합계는 최대 512 MiB입니다.
- 실제 decode되어 메모리에 유지되는 오디오 합계는 최대 192 MiB입니다.
- Scene JSON에는 원본 파일명이 기록될 수 있으므로 파일을 외부에 공유하기 전에 확인하세요.
## Backend와 scan grid
- **Auto (GPU→CPU)**: 권장값입니다. WebGPU adapter나 device를 사용할 수 없거나 지원되는 장치
한계를 넘으면 CPU로 계속합니다.
- **WebGPU 강제**: GPU 경로만 사용합니다. 장치를 열 수 없거나 한계를 넘으면 fallback하지 않고
오류를 표시합니다.
- **CPU 강제**: 비교와 호환성 확인을 위해 CPU DAS만 사용합니다.
WebGPU 경로도 신호 합성과 STFT 일부는 CPU에서 수행합니다. grid의 point 수는 50×50을 1로 보면
100×100은 4배, 200×200은 16배입니다. 처음에는 50×50을 사용하고 해상도가 필요할 때만 높이세요.
실행 시간은 microphone 수, scene 길이, FFT, 대역 폭과 장치에 함께 좌우됩니다.
## Result JSON
Result JSON은 계약 버전 `1.0.0`의 `micarrsim.result` 문서입니다. 주요 내용은 다음과 같습니다.
- 실행한 `experimentId`, `arrayId`, `sceneId`, 실제 `backend`와 생성 시각
- Array·Scene·Experiment의 canonical SHA-256인 `inputHash`
- 정규화 전 peak, 적용 scale과 clipping 수를 담은 `signalStats`
- 전체·신호 생성·beamforming 시간인 `timingMs`
- 주파수 대역별 선형 power `rawMap`, 표시용 `displayMap`, 검출 `peaks`
- 위치 오차, precision/recall, truth 반경 내 energy, peak-to-sidelobe, main-lobe 폭을 계산하기
위한 평가 파라미터와 `metrics`
평가에서 tone source는 tone 주파수가 포함되는 band의 ground truth로만 사용합니다. Audio asset과
noise source는 스펙트럼을 미리 알 수 없으므로 모든 band의 ground truth로 취급합니다. 이 가정은
결과를 실제 데이터와 해석할 때 고려해야 합니다.
## 개인정보와 모델의 한계
서버는 `127.0.0.1` loopback에만 열리고 외부 서비스로 파일을 업로드하거나 telemetry를 보내지
않습니다. 선택한 파일은 브라우저 메모리에서 읽으며 생성한 JSON/WAV는 브라우저 다운로드로만
저장됩니다.
현재 음향 모델은 직접 경로와 거리 감쇠를 사용하는 자유음장 모델입니다. 반사, 잔향, 회절,
차폐, 바람, 실제 MEMS 비선형과 장착 구조는 모델링하지 않습니다. Beamformer는 conventional
DAS이며 UI의 DAMAS와 CLEAN-SC는 아직 사용할 수 없습니다. 따라서 결과는 배열·grid·대역의 상대
비교와 소프트웨어 검증용이며, 현장 성능 인증이나 안전 판단을 대신하지 않습니다.
## 배열 예제
- `assets/array-geometries/rectangular-16x12.array.json`: 0.45 m 정사각 aperture의 16×12 기준 배열
- `assets/array-geometries/fermat-spiral-192.array.json`: 0.45 m 원형 aperture의 192채널 Fermat spiral
Camera의 **비교 배열** 선택으로 두 fixture를 같은 scene과 experiment에서 연속 실행할 수 있습니다.
상세한 생성법은 [`assets/array-geometries/README.md`](assets/array-geometries/README.md)를 참고하세요.
## 테스트와 benchmark
서버를 실행한 뒤 다음 주소를 브라우저에서 엽니다.
- 전체 browser test runner: <http://127.0.0.1:4173/tests/>
- WebGPU scaling benchmark: <http://127.0.0.1:4173/benchmarks/webgpu-scaling.html>
포트를 바꿨다면 URL의 포트도 같은 값으로 바꾸세요. 테스트 범위는
[`tests/unit/README.md`](tests/unit/README.md)와
[`tests/integration/README.md`](tests/integration/README.md), 기준 실측은
[`docs/benchmarks/README.md`](docs/benchmarks/README.md)에 설명되어 있습니다.
## 저장 형식과 개발 문서
- [`docs/data-contracts.md`](docs/data-contracts.md): Array·Scene·Experiment·Result 계약
- [`schemas/README.md`](schemas/README.md): JSON Schema 구성
- [`docs/execution-plan.md`](docs/execution-plan.md): 구현 단계와 기술 결정
- [`goal.md`](goal.md): 프로젝트 목표
원본 DCASE/MIMII 데이터, 사용자가 선택한 assets와 생성된 대용량 결과는 저장소에 커밋하지
않습니다.
## 디렉터리 구조
```text
apps/ Dataset Builder와 Acoustic Camera UI
assets/ 192채널 배열과 synthetic Scene/Experiment fixture
benchmarks/ 현재 브라우저에서 실행하는 WebGPU scaling 측정
docs/ 사용자 안내, 데이터 계약, 실행 계획과 기준 실측
packages/ 음향 수치 모델, 계약 검증, UI, WebGPU kernel
schemas/ Array·Scene·Experiment·Result JSON Schema
scripts/ 무설치 로컬 서버와 fixture 생성기
tests/ 브라우저 unit·integration·end-to-end test
start.cmd Windows 원클릭 실행 진입점
```
+90 -1
View File
@@ -1,3 +1,92 @@
# Acoustic Camera # Acoustic Camera
scene과 마이크 배열을 불러와 자유음장 전파, WGSL DAS 빔포밍, overlay 및 평가를 수행하는 앱입니다. Scene과 마이크 배열로 자유음장 microphone signal을 합성하고, STFT와 conventional DAS를 통해
주파수 대역별 acoustic map과 ground-truth 평가 지표를 만드는 브라우저 앱입니다.
## 열기와 기본 예제
저장소의 `start.cmd`를 실행한 뒤 시작 화면에서 **Acoustic Camera**를 선택하거나
<http://127.0.0.1:4173/apps/acoustic-camera/>을 엽니다. `index.html`을 직접 열지 마세요.
가장 빠른 확인 방법은 다음과 같습니다.
1. 기본 fixture가 준비될 때까지 기다립니다.
2. 192채널 rectangular 배열, single-tone scene과 50×50 grid를 그대로 둡니다.
3. **기본 예제 실행**을 누릅니다.
4. 완료 후 overlay, 위치 오차와 timing을 확인하고 **Result JSON**을 저장합니다.
## 사용자 입력 불러오기
문서는 다음 순서로 불러오는 것이 안전합니다.
1. **Scene**: `.scene.json`
2. **Array**: 선택 사항인 `.array.json`; 또는 **비교 배열** preset 사용
3. **Experiment**: 선택 사항인 `.experiment.json`
4. **Assets**: Scene이 참조하는 원본 이미지와 오디오
Scene을 불러오면 현재 Experiment의 `sceneId`와 scan plane을 Scene에 맞추고 기존 asset 연결을
해제합니다. Array를 불러오면 현재 Experiment의 `arrayId`를 맞춥니다. 별도 Experiment는 현재
Scene과 Array를 참조해야 하므로 마지막에 불러오세요.
Scene JSON에는 원본 파일 byte가 없습니다. **Assets**에서 Builder에 사용한 파일을 다시 선택하면
Camera가 SHA-256을 계산해 manifest에 연결합니다. 같은 이름이라도 내용이 바뀌면 거절됩니다.
브라우저를 다시 열거나 Scene을 바꿀 때도 재선택해야 합니다.
- asset 한 파일: 최대 128 MiB
- 연결된 asset 합계: 최대 512 MiB
- decode되어 메모리에 유지되는 오디오 합계: 최대 192 MiB
파일은 외부로 업로드되지 않고 브라우저 메모리에서만 처리됩니다.
## Backend 선택
- **Auto (GPU→CPU)**: 권장. WebGPU를 먼저 시도하고 adapter/device 부재나 지원되는 장치 한계가
있으면 CPU로 계속합니다.
- **WebGPU 강제**: GPU만 사용하며 운용 오류가 나도 CPU로 fallback하지 않습니다.
- **CPU 강제**: CPU 수치 기준이나 WebGPU가 없는 환경에서 사용합니다.
WebGPU가 감지돼도 실제 장치는 실행 시 확인합니다. 계산 중 **계산 취소**를 누르면 Worker를
종료하며, 이미 완료된 이전 결과가 있으면 그대로 남습니다.
## Grid, STFT와 대역
50×50은 2,500 points입니다. 이를 1로 보면 100×100은 4배, 200×200은 16배 points를 계산합니다.
처음에는 50×50을 권장합니다. 실행량은 microphone 수, scene 길이, FFT/hop과 선택 band의 bin
수에도 비례하므로 실제 시간 배율은 장치마다 다릅니다.
각 band는 id, 최소/최대 Hz와 overlay 색을 가집니다. Nyquist를 넘거나 겹치는 id 등 계약 오류는
실행 전에 표시됩니다. 현재 후처리는 raw DAS만 지원하며 DAMAS와 CLEAN-SC 항목은 비활성화되어
있습니다.
## 화면과 배열 비교
- acoustic overlay의 투명도와 표시할 band를 선택할 수 있습니다.
- **Ground truth**를 켜면 source 위치와 검출 peak를 비교할 수 있습니다.
- rectangular와 Fermat spiral preset을 같은 Scene/Experiment에서 연속 실행하면 **배열 비교 기록**에
backend, timing과 평가 요약이 남습니다.
- 입력을 바꾼 뒤 화면에 남은 결과는 이전 실행 snapshot이며 UI가 이전 설정 결과임을 표시합니다.
## Result JSON
**Result JSON**은 검증을 통과한 `micarrsim.result` 문서만 저장합니다.
- `experimentId`, `arrayId`, `sceneId`, `createdAt`, 실제 `backend`
- 실행 입력 전체의 canonical SHA-256인 `inputHash`
- `signalStats`: 정규화 전 peak, 적용 scale, clipped sample 수
- `timingMs`: 전체, signal generation과 beamforming 시간
- band별 `rawMap` 선형 power, `displayMap`, 검출 `peaks`
- 평가 설정과 `peakToSidelobeDb`, `energyInsideTruthRatio`, `precision`, `recall`,
`mainLobeWidthM`
Tone source는 그 주파수가 band 범위 안에 있을 때만 해당 band의 ground truth입니다. Audio asset과
noise source는 스펙트럼을 사전에 판별하지 않으므로 모든 band의 ground truth로 평가합니다. Source
z가 scan z와 다르면 평면에 투영한 x/y를 사용하므로 UI 경고와 위치 오차를 함께 해석하세요.
## 해석 한계
신호 모델은 직접 경로와 거리 감쇠를 사용하는 자유음장입니다. 반사, 잔향, 회절, 차폐, 바람,
센서 비선형과 장착 구조는 포함하지 않습니다. 배열 JSON의 sensitivity/phase calibration은 적용하지만
결과는 상대 배열 비교와 알고리즘 검증용이며 실측 인증을 대신하지 않습니다.
WebGPU 성능을 별도로 측정하려면 서버 실행 후
<http://127.0.0.1:4173/benchmarks/webgpu-scaling.html>을 여세요.
+8 -5
View File
@@ -31,11 +31,14 @@
<h1 id="workspace-title">음향 장면</h1> <h1 id="workspace-title">음향 장면</h1>
<p class="workspace-meta" id="scene-meta">장면을 불러오고 있습니다.</p> <p class="workspace-meta" id="scene-meta">장면을 불러오고 있습니다.</p>
</div> </div>
<div class="workspace-tools" aria-label="입력 파일"> <div class="workspace-import">
<label class="button button--ghost file-button">Scene<input id="scene-input" type="file" accept="application/json,.json" /></label> <div class="workspace-tools" aria-label="입력 파일">
<label class="button button--ghost file-button">Array<input id="array-input" type="file" accept="application/json,.json" /></label> <label class="button button--ghost file-button" title="1. Scene JSON을 먼저 선택하세요.">Scene<input id="scene-input" type="file" accept="application/json,.json" /></label>
<label class="button button--ghost file-button">Experiment<input id="experiment-input" type="file" accept="application/json,.json" /></label> <label class="button button--ghost file-button" title="2. 사용자 배열이 있을 때 Array JSON을 선택하세요.">Array<input id="array-input" type="file" accept="application/json,.json" /></label>
<label class="button button--ghost file-button">Assets<input id="asset-input" type="file" accept="audio/*,image/*,.wav,.flac,.mp3,.ogg,.m4a,.png,.jpg,.jpeg,.webp,.svg" multiple /></label> <label class="button button--ghost file-button" title="3. 현재 Scene과 Array를 참조하는 Experiment JSON을 선택하세요.">Experiment<input id="experiment-input" type="file" accept="application/json,.json" /></label>
<label class="button button--ghost file-button" title="4. Scene에 사용한 원본 파일을 다시 선택하세요.">Assets<input id="asset-input" type="file" accept="audio/*,image/*,.wav,.flac,.mp3,.ogg,.m4a,.png,.jpg,.jpeg,.webp,.svg" multiple /></label>
</div>
<p class="import-order">권장 순서 · Scene → Array → Experiment → Assets</p>
</div> </div>
</div> </div>
+3
View File
@@ -17,7 +17,9 @@
.workspace-heading .eyebrow, .section-heading .eyebrow, .array-heading .eyebrow { margin-bottom: 7px; } .workspace-heading .eyebrow, .section-heading .eyebrow, .array-heading .eyebrow { margin-bottom: 7px; }
.workspace-heading h1 { margin-bottom: 4px; font-size: clamp(1.9rem, 4vw, 3.2rem); letter-spacing: -.055em; } .workspace-heading h1 { margin-bottom: 4px; font-size: clamp(1.9rem, 4vw, 3.2rem); letter-spacing: -.055em; }
.workspace-meta, .section-copy { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.55; } .workspace-meta, .section-copy { margin: 0; color: var(--muted); font-size: .76rem; line-height: 1.55; }
.workspace-import { display: grid; gap: 7px; justify-items: end; }
.workspace-tools { justify-content: flex-end; } .workspace-tools { justify-content: flex-end; }
.import-order { margin: 0; color: var(--muted); font-size: .64rem; }
.scene-canvas-card { position: relative; overflow: hidden; min-height: 520px; border: 1px solid var(--line); border-radius: 22px; background: #07110f; box-shadow: var(--shadow); } .scene-canvas-card { position: relative; overflow: hidden; min-height: 520px; border: 1px solid var(--line); border-radius: 22px; background: #07110f; box-shadow: var(--shadow); }
#scene-canvas { display: block; width: 100%; height: clamp(520px, 64vh, 760px); } #scene-canvas { display: block; width: 100%; height: clamp(520px, 64vh, 760px); }
.canvas-message { position: absolute; top: 32px; left: 50%; display: grid; width: min(440px, calc(100% - 48px)); gap: 6px; color: var(--muted); text-align: center; pointer-events: none; transform: translateX(-50%); } .canvas-message { position: absolute; top: 32px; left: 50%; display: grid; width: min(440px, calc(100% - 48px)); gap: 6px; color: var(--muted); text-align: center; pointer-events: none; transform: translateX(-50%); }
@@ -109,6 +111,7 @@
.camera-header, .workspace-heading { align-items: stretch; } .camera-header, .workspace-heading { align-items: stretch; }
.camera-header { position: static; display: flex; flex-direction: column; } .camera-header { position: static; display: flex; flex-direction: column; }
.header-status, .header-actions, .workspace-tools { justify-content: flex-start; width: 100%; } .header-status, .header-actions, .workspace-tools { justify-content: flex-start; width: 100%; }
.workspace-import { justify-items: start; }
.header-actions .button, .workspace-tools .button { flex: 1 1 calc(50% - 5px); } .header-actions .button, .workspace-tools .button { flex: 1 1 calc(50% - 5px); }
.scene-workspace { padding: 18px; } .scene-workspace { padding: 18px; }
.workspace-heading { flex-direction: column; } .workspace-heading { flex-direction: column; }
+63 -1
View File
@@ -1,3 +1,65 @@
# Dataset Builder # Dataset Builder
음원, ambient audio, 이미지상의 위치를 조합해 scene audio와 ground truth manifest를 만드는 앱입니다. 이미지 평면에 source ground truth를 배치하고 tone, noise, 로컬 audio asset과 ambient를 조합해
재현 가능한 `micarrsim.scene` JSON을 만드는 브라우저 앱입니다.
## 열기
저장소의 `start.cmd`를 실행한 뒤 시작 화면에서 **Dataset Builder**를 선택하거나
<http://127.0.0.1:4173/apps/dataset-builder/>을 엽니다. `index.html`을 직접 열면 module과 Worker가
동작하지 않습니다.
## 기본 사용법
1. **배경 이미지**를 선택하거나 이미지 없이 grid를 사용합니다.
2. **오디오 추가**로 source 또는 ambient에 사용할 파일을 연결합니다.
3. **+ 음원 추가**를 누르고 카드를 선택한 뒤 canvas를 클릭하거나 x/y/z를 입력합니다.
4. source 종류를 설정합니다.
- `Tone`: 주파수와 위상을 갖는 합성 정현파
- `Noise`: seed로 재현되는 white 또는 pink noise
- `Audio asset`: 선택한 파일의 channel을 사용하며 짧은 파일은 반복 가능
5. 필요하면 ambient 종류와 gain을 설정합니다.
6. 검증 카드가 **저장 가능한 장면**이면 **Scene JSON 저장**을 누릅니다.
길이, sample rate, 음속, seed와 mix 정규화도 Scene JSON에 저장됩니다. 이미지가 있으면 표시
평면의 x/y 범위, z와 image pixel 크기가 ground truth 좌표 변환에 사용됩니다.
## 미리 듣기와 Preview WAV
**미리 듣기**는 현재 mix를 브라우저에서 재생합니다. **Preview WAV 저장**은 모든 source와
ambient를 공간 정보 없이 합친 mono PCM16 확인용 파일입니다. 마이크별 전파 지연이 적용된
192채널 녹음이나 acoustic-camera 결과가 아닙니다.
클리핑이 예상되면 저장 전에 경고합니다. 출력이 너무 크면 scene 길이 또는 sample rate를 줄이거나
source gain과 mix 정규화를 조절하세요.
## Scene JSON과 assets
Scene JSON은 오디오·이미지 원본을 포함하지 않고 asset manifest만 기록합니다. 각 descriptor에는
id, 종류, 파일명과 SHA-256이 들어갑니다.
- 파일 한 개는 최대 128 MiB, 연결 파일 합계는 최대 512 MiB입니다.
- decode된 오디오의 합계는 최대 192 MiB입니다.
- Scene을 다시 불러오면 브라우저 권한이 없으므로 원본 이미지와 오디오를 다시 선택해야 합니다.
- 같은 이름의 파일도 SHA-256이 manifest와 다르면 기존 asset으로 연결되지 않습니다.
- 사용 중인 asset은 먼저 source, ambient 또는 image 참조를 바꾼 뒤 제거할 수 있습니다.
원본 파일은 외부로 업로드되지 않고 브라우저 메모리에서만 처리됩니다. 단, JSON에는 파일명이
남을 수 있으므로 공유 전에 확인하세요.
## Acoustic Camera로 넘기기
1. Scene JSON과 사용한 원본 assets를 함께 보관합니다.
2. Acoustic Camera에서 **Scene**으로 JSON을 선택합니다.
3. 사용자 문서가 있다면 **Array**, **Experiment** 순서로 선택합니다.
4. **Assets**에서 원본 이미지와 오디오를 다시 선택해 SHA-256 연결 상태를 확인합니다.
5. 검증이 통과하면 시뮬레이션을 실행합니다.
안전한 전체 import 순서는 **Scene → Array → Experiment → Assets**입니다. 자세한 계산과 Result
설명은 [`../acoustic-camera/README.md`](../acoustic-camera/README.md)를 참고하세요.
## 모델 범위
Builder의 mix는 재현 가능한 source recipe와 위치 ground truth를 만드는 단계입니다. 자유음장 전파,
마이크 calibration과 DAS는 Acoustic Camera에서 수행합니다. 실제 방의 반사·잔향·차폐를 Builder가
추정하지 않습니다.
+38 -1
View File
@@ -1,3 +1,40 @@
# Array Geometries # Array Geometries
재현 가능한 마이크 배열 JSON 예제를 둡니다. Camera에서 즉시 사용하고 테스트에서 재현할 수 있는 계약 버전 `1.0.0`의 192채널 Array JSON
fixture입니다. 좌표 단위는 m이고 모든 microphone은 배열 평면 `z = 0`에 있습니다. `+x`는 오른쪽,
`+y`는 위, `+z`는 scene 방향입니다.
## 포함된 배열
### `rectangular-16x12.array.json`
- 16 columns × 12 rows = 192 microphones
- 폭과 높이가 각각 0.45 m인 정사각 aperture
- x/y 양 끝을 포함해 균일한 간격으로 배치
- 기본 single-tone experiment와 CPU/WebGPU 기준 fixture가 참조하는 baseline
### `fermat-spiral-192.array.json`
- 192 microphones
- 반지름 0.225 m, 지름 0.45 m인 원형 aperture
- golden angle과 면적 균등 반지름으로 만든 Fermat spiral
- rectangular baseline과 sidelobe·집중도·위치 오차를 비교하기 위한 preset
Acoustic Camera의 **비교 배열**에서 두 배열을 선택해 같은 Scene/Experiment 결과를 세션 기록에
나란히 추가할 수 있습니다.
## fixture 다시 생성하기
저장소 최상위에서 다음 명령을 실행하면 두 JSON을 결정론적으로 다시 생성합니다.
```powershell
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\generate-array-fixture.ps1
```
이 스크립트는 기존 `rectangular-16x12.array.json`과 `fermat-spiral-192.array.json`을 덮어씁니다.
`ExecutionPolicy Bypass`는 이 PowerShell 프로세스에만 적용됩니다. 생성 뒤
<http://127.0.0.1:4173/tests/>에서 schema와 192채널 배치 검증을 실행하세요.
사용자 배열은 [`../../schemas/array.schema.json`](../../schemas/array.schema.json) 구조를 따르고
고유한 `id`, aperture와 microphone별 `[x, y, z]`를 가져야 합니다. 선택적인 `sensitivityDb`와
`phaseOffsetDeg`는 합성 channel 편차와 beamforming 역 calibration에 사용됩니다.
+10 -1
View File
@@ -1,3 +1,12 @@
# Sample Scenes # Sample Scenes
재배포 가능한 최소 synthetic scene과 fixture를 둡니다. 재배포 가능한 최소 synthetic fixture입니다. 외부 asset이 없어 서버를 시작한 직후 Camera에서
바로 실행할 수 있습니다.
- `single-tone.scene.json`: 48 kHz, 0.25 s, 1 kHz tone 하나를 z=2 m 평면에 배치한 Scene
- `single-tone.experiment.json`: rectangular 192채널 배열, 900–1100 Hz band, 50×50 scan,
FFT 512 / hop 256, Auto backend를 쓰는 Experiment
두 문서는 계약 버전 `1.0.0`이며 기본 Camera 실행, CPU golden test, CPU/WebGPU 비교와 benchmark가
같은 조건을 공유하도록 고정되어 있습니다. fixture를 바꿀 때는 <http://127.0.0.1:4173/tests/>의
전체 테스트와 benchmark 입력 의미를 함께 확인하세요.
+4 -3
View File
@@ -18,6 +18,7 @@ distance 생성, CPU STFT가 포함된다. 200×200은 64 MiB steering buffer
쓰지 않는다. 쓰지 않는다.
같은 환경의 자동 검증에서 192채널·50×50 GPU raw map은 CPU raw map 대비 peak-normalized RMS 같은 환경의 자동 검증에서 192채널·50×50 GPU raw map은 CPU raw map 대비 peak-normalized RMS
오차 `3.008e-5`를 기록했고, 모든 37개 테스트를 통과했다. 상세 원자료는 오차 `3.008e-5`를 기록했고, 측정 당시 제공되던 37개 테스트를 모두 통과했다. 이후 end-to-end와
`webgpu-intel-gen9-2026-08-28.json`에 있다. 사용자는 `/benchmarks/webgpu-scaling.html`에서 자신의 서버 보안 테스트가 추가되었으므로 현재 전체 suite 상태는 `/tests/`에서 별도로 확인한다. 상세
장치 결과를 다시 측정해 JSON으로 저장할 수 있다. 원자료는 `webgpu-intel-gen9-2026-08-28.json`에 있다. 사용자는
`/benchmarks/webgpu-scaling.html`에서 자신의 장치 결과를 다시 측정해 JSON으로 저장할 수 있다.
+36 -3
View File
@@ -11,14 +11,27 @@ micarrsim의 저장 형식은 JSON Schema Draft 2020-12와 추가 관계 검증
- `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 DAS 설정 - `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 DAS 설정
- `micarrsim.result`: raw/display acoustic map, 복수 peak와 source 매칭, 평가·성능 정보 - `micarrsim.result`: raw/display acoustic map, 복수 peak와 source 매칭, 평가·성능 정보
scene JSON에는 192채널 원시 sample을 넣지 않는다. 합성 tone/noise는 seed가 포함된 recipe로 scene JSON에는 192채널 원시 sample이나 원본 asset byte를 넣지 않는다. 합성 tone/noise는 seed가
재생성하고, 녹음 파일과 이미지는 manifest 기준의 안전한 상대 경로로 참조한다. 사용자 컴퓨터의 포함된 recipe로 재생성하고, 녹음 파일과 이미지는 `id`, `kind`, `fileName`, `mimeType`,
절대 경로, URL, `..` 경로는 저장하지 않는다. `sha256` manifest로 참조한다. 사용자 컴퓨터의 절대 경로, URL, `..` 경로는 저장하지 않는다.
array의 `sensitivityDb`와 `phaseOffsetDeg`는 각 마이크 수신 channel에 적용되는 편차다. 합성기는 array의 `sensitivityDb`와 `phaseOffsetDeg`는 각 마이크 수신 channel에 적용되는 편차다. 합성기는
이를 신호에 주입하고, beamformer는 알려진 calibration 값의 역 gain/phase를 적용한다. 따라서 이를 신호에 주입하고, beamformer는 알려진 calibration 값의 역 gain/phase를 적용한다. 따라서
두 값이 0이 아닌 배열도 오류 없이 조용히 무시되지 않는다. 두 값이 0이 아닌 배열도 오류 없이 조용히 무시되지 않는다.
## 로컬 asset 재연결
브라우저 보안상 Scene JSON을 새 페이지에서 열었을 때 이전 로컬 파일 권한은 복원되지 않는다.
Camera는 사용자가 다시 선택한 파일을 SHA-256으로 계산해 같은 descriptor에만 연결한다. 이름이
같아도 byte가 다르면 거절하고, 원본은 브라우저 메모리에서만 decode하며 서버에 업로드하지 않는다.
- 파일 하나: 최대 128 MiB
- 한 Scene에 연결된 원본 파일 합계: 최대 512 MiB
- 동시에 유지하는 decode 오디오 channel 합계: 최대 192 MiB
Builder가 내보낸 Scene JSON과 사용한 원본 이미지·오디오는 함께 보관해야 한다. Preview WAV는
scene recipe를 mono로 합친 청취 확인용 파일이며 마이크 배열의 192채널 수신 신호가 아니다.
## 오디오와 map 정규화 ## 오디오와 map 정규화
`scene.mix.normalization`은 마이크 신호 합성 전에 적용할 오디오 mix 정책이다. `none`은 원래 `scene.mix.normalization`은 마이크 신호 합성 전에 적용할 오디오 mix 정책이다. `none`은 원래
@@ -32,6 +45,25 @@ raw DAS map을 덮어쓰지 않는다.
map은 row-major 배열이다. 첫 행은 `yMax`, 마지막 행은 `yMin`, 각 행은 `xMin`에서 `xMax` map은 row-major 배열이다. 첫 행은 `yMax`, 마지막 행은 `yMin`, 각 행은 `xMin`에서 `xMax`
방향이다. `values.length`는 반드시 `rows × columns`와 같아야 한다. 방향이다. `values.length`는 반드시 `rows × columns`와 같아야 한다.
## Result 평가 정보
각 band의 `peaks`는 위치와 power 값, 선택적 `sourceId`, `localizationErrorM`,
`localizationErrorPixels`를 저장한다. `metrics`에는 다음 유한값만 기록한다.
- `peakToSidelobeDb`
- `energyInsideTruthRatio`
- `precision`, `recall`
- `mainLobeWidthM`
`evaluation`은 지표를 다시 해석할 수 있도록 truth 반경, source 매칭 tolerance, peak 검출
threshold와 최소 거리, sidelobe 제외 반경, half-power 비율, dynamic range, 최대 검출 수를
버전과 함께 저장한다. `environment`에는 실제 backend, microphone/grid 수, scan bounds와 z,
가능한 경우 GPU adapter 및 fallback 정보를 기록한다.
대역별 ground truth는 tone의 주파수가 band 안에 있을 때 그 tone source를 포함한다. noise와
asset source는 현재 모든 band의 truth로 간주한다. 즉 녹음 asset의 실제 spectrum energy를
대역별로 판정하는 계약은 아직 없으며, 이 가정은 실제 데이터 결과를 해석할 때 고려해야 한다.
## 재현성 hash ## 재현성 hash
`result.inputHash`는 실행에 사용한 `{ array, scene, experiment }`를 객체 key 사전순으로 재귀 `result.inputHash`는 실행에 사용한 `{ array, scene, experiment }`를 객체 key 사전순으로 재귀
@@ -48,6 +80,7 @@ SHA-256을 계산해 scene의 해당 asset `sha256` 필드를 채운 상태로 c
- asset 종류와 안전한 상대 경로 - asset 종류와 안전한 상대 경로
- Nyquist 및 시간·scan 범위 - Nyquist 및 시간·scan 범위
- map shape와 source peak 매칭 - map shape와 source peak 매칭
- band별 evaluation 파라미터와 허용 metric
- 현재 비스트리밍 CPU backend의 512 MiB channel-buffer 예산 - 현재 비스트리밍 CPU backend의 512 MiB channel-buffer 예산
지원하지 않는 schemaVersion은 묵시적으로 변환하지 않고 오류로 알린다. 지원하지 않는 schemaVersion은 묵시적으로 변환하지 않고 오류로 알린다.
+32 -22
View File
@@ -1,5 +1,10 @@
# micarrsim 실행 계획 # micarrsim 실행 계획
> 구현 상태 · 2026-08-28
>
> 단계 0–4는 완료되어 설치 없는 브라우저 앱, CPU/WebGPU DAS, Builder→Camera asset 재연결,
> 배열 비교, 평가·Result 내보내기와 53개 자동 테스트로 검증했다. 단계 5는 향후 고도화 범위다.
## 1. 목표와 성공 기준 ## 1. 목표와 성공 기준
이 프로젝트는 정방형 평면에 배치할 192개 MEMS 마이크의 배열을 비교하기 위한 재현 가능한 이 프로젝트는 정방형 평면에 배치할 192개 MEMS 마이크의 배열을 비교하기 위한 재현 가능한
@@ -8,7 +13,7 @@
1. 기계음 데이터와 ambient audio를 조합해 장면 및 ground truth를 생성한다. 1. 기계음 데이터와 ambient audio를 조합해 장면 및 ground truth를 생성한다.
2. 배열 JSON과 장면 정보를 사용해 각 마이크에 도달하는 신호를 합성한다. 2. 배열 JSON과 장면 정보를 사용해 각 마이크에 도달하는 신호를 합성한다.
3. WGSL로 conventional delay-and-sum(DAS/DAC) 빔포밍을 수행한다. 3. WGSL로 conventional delay-and-sum(DAS) 빔포밍을 수행한다.
4. 주파수 구간별 acoustic image를 ground truth 위에 표시하고 정량 평가한다. 4. 주파수 구간별 acoustic image를 ground truth 위에 표시하고 정량 평가한다.
5. 배열, 거리, 음원, 주파수, grid 해상도를 바꾼 실험을 저장하고 비교한다. 5. 배열, 거리, 음원, 주파수, grid 해상도를 바꾼 실험을 저장하고 비교한다.
@@ -18,10 +23,12 @@ CPU 기준 구현과 GPU 결과의 수치 오차가 합의된 허용 범위 안
## 2. 기술 접근 결정 ## 2. 기술 접근 결정
TypeScript 기반의 브라우저 앱 2개와 WebGPU/WGSL을 권장한다. 브라우저는 배열·장면 편집, 최종 구현은 빌드 단계와 패키지 설치가 없는 브라우저 네이티브 ES module 2개와 WebGPU/WGSL을
오디오 미리 듣기, 이미지 overlay에 적합하고 WebGPU는 192 × scan point × frequency bin 형태의 선택했다. 초기 계획의 TypeScript 권고보다 Windows 사용자가 `start.cmd` 하나로 바로 실행하는
병렬 연산에 잘 맞는다. WGSL 커널을 독립 패키지로 두면 이후 GLSL 또는 네이티브 GPU 구현으로 목표를 우선한 결정이다. JSON Schema와 브라우저 자동 테스트가 런타임 계약을 보완한다.
옮길 때 데이터 배치와 수식을 재사용하기 쉽다. 브라우저는 배열·장면 편집, 오디오 미리 듣기, 이미지 overlay에 적합하고 WebGPU는
192 × scan point × frequency bin 형태의 병렬 연산에 잘 맞는다. WGSL 커널은 독립 package로
두어 데이터 배치와 수식을 CPU 기준 구현과 대조할 수 있게 했다.
데이터셋 전체를 브라우저에 적재하지 않는다. 사용자가 로컬 파일을 선택해 장면을 만들고, 데이터셋 전체를 브라우저에 적재하지 않는다. 사용자가 로컬 파일을 선택해 장면을 만들고,
결과를 manifest와 오디오 파일로 내보내는 방식을 우선 구현한다. 대규모 batch 전처리가 필요해질 결과를 manifest와 오디오 파일로 내보내는 방식을 우선 구현한다. 대규모 batch 전처리가 필요해질
@@ -105,7 +112,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
## 8. 단계별 구현 ## 8. 단계별 구현
### 단계 0 — 기반과 계약 ### 단계 0 — 기반과 계약 · 완료
- workspace, formatter, linter, unit test, WebGPU feature detection 구성 - workspace, formatter, linter, unit test, WebGPU feature detection 구성
- 네 가지 JSON Schema와 최소 예제 작성 - 네 가지 JSON Schema와 최소 예제 작성
@@ -114,7 +121,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 예제 JSON이 schema validation을 통과하고 두 앱이 동일 manifest를 읽는다. 완료 조건: 예제 JSON이 schema validation을 통과하고 두 앱이 동일 manifest를 읽는다.
### 단계 1 — CPU 기준 모델 ### 단계 1 — CPU 기준 모델 · 완료
- 자유음장 구면 전파, 분수 지연, STFT, steering, DAS의 CPU reference 구현 - 자유음장 구면 전파, 분수 지연, STFT, steering, DAS의 CPU reference 구현
- 단일 음원의 지연·감쇠·peak 위치를 analytic result와 비교 - 단일 음원의 지연·감쇠·peak 위치를 analytic result와 비교
@@ -122,15 +129,15 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 자동화된 golden test가 통과하고 sign/좌표 convention이 문서화된다. 완료 조건: 자동화된 golden test가 통과하고 sign/좌표 convention이 문서화된다.
### 단계 2 — Dataset Builder ### 단계 2 — Dataset Builder · 완료
- 이미지와 음원 파일 불러오기, 위치·시간·gain 편집, ambient mix와 미리 듣기 - 이미지와 음원 파일 불러오기, 위치·시간·gain 편집, ambient mix와 미리 듣기
- deterministic seed, clipping 경고, manifest/audio 내보내기 - deterministic seed, clipping 경고, Scene JSON과 mono Preview WAV 내보내기
- 데이터셋 adapter 인터페이스와 synthetic adapter 구현 - 데이터셋 adapter 인터페이스와 synthetic adapter 구현
완료 조건: 만든 scene을 다시 열었을 때 음원, 위치, 파형, 정답이 동일하다. 완료 조건: 만든 scene을 다시 열었을 때 음원, 위치, 파형, 정답이 동일하다.
### 단계 3 — WebGPU DAS ### 단계 3 — WebGPU DAS · 완료
- CPU와 동일한 buffer layout 및 WGSL kernel 구현 - CPU와 동일한 buffer layout 및 WGSL kernel 구현
- frequency band별 map 생성, device limit 대응, CPU fallback - frequency band별 map 생성, device limit 대응, CPU fallback
@@ -138,7 +145,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: CPU/GPU map 오차 기준을 통과하고 192채널 50×50 실행 timing이 기록된다. 완료 조건: CPU/GPU map 오차 기준을 통과하고 192채널 50×50 실행 timing이 기록된다.
### 단계 4 — Acoustic Camera UI와 평가 ### 단계 4 — Acoustic Camera UI와 평가 · 완료
- scene/ground truth/acoustic overlay, 배열 geometry, parameter panel 구현 - scene/ground truth/acoustic overlay, 배열 geometry, parameter panel 구현
- 결과 metric, 실행 환경, 입력 hash와 map 내보내기 - 결과 metric, 실행 환경, 입력 hash와 map 내보내기
@@ -146,7 +153,7 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
완료 조건: 사용자가 코드 수정 없이 배열 JSON 두 개를 같은 장면에서 비교할 수 있다. 완료 조건: 사용자가 코드 수정 없이 배열 JSON 두 개를 같은 장면에서 비교할 수 있다.
### 단계 5 — 실제 데이터와 고도화 ### 단계 5 — 실제 데이터와 고도화 · 향후
- 라이선스 검토를 통과한 DCASE/MIMII adapter와 batch workflow 추가 - 라이선스 검토를 통과한 DCASE/MIMII adapter와 batch workflow 추가
- microphone calibration·noise 모델 검토 - microphone calibration·noise 모델 검토
@@ -164,15 +171,18 @@ flattening이 모델 타당성을 훼손하는지 청취 및 spectrum 통계로
- 실제 AC와 simulation gap: 감도, 위상, 위치 오차를 이후 calibration/noise model로 주입 가능하게 한다. - 실제 AC와 simulation gap: 감도, 위상, 위치 오차를 이후 calibration/noise model로 주입 가능하게 한다.
- post-processing이 기본 결과를 가림: raw DAS map과 후처리 map을 항상 별도로 보존한다. - post-processing이 기본 결과를 가림: raw DAS map과 후처리 map을 항상 별도로 보존한다.
## 10. 첫 구현 백로그 ## 10. 구현 결과와 다음 백로그
1. TypeScript workspace와 두 앱 shell을 생성한다. - [x] 설치 없는 ES module workspace와 두 앱 shell
2. array/scene/experiment/result schema와 fixture를 만든다. - [x] array/scene/experiment/result schema와 synthetic fixture
3. 좌표 변환, 거리, 구면 감쇠, 주파수 phase delay 단위 테스트를 작성한다. - [x] 좌표·거리·구면 감쇠·분수 지연·STFT 단위 테스트
4. CPU DAS golden implementation과 synthetic scene을 만든다. - [x] CPU DAS golden implementation
5. WGSL kernel 및 CPU/GPU 비교 harness를 만든다. - [x] WGSL kernel, CPU/GPU 비교와 50²/100²/200² benchmark
6. scene canvas, array view, parameter controls를 연결한다. - [x] scene canvas, array view, parameter controls와 Result export
7. benchmark와 평가 지표를 결과 manifest에 기록한다. - [x] Builder→Camera SHA-256 asset 재연결 왕복 테스트
- [ ] DCASE/MIMII 라이선스 검토와 adapter
- [ ] 잔향·calibration·noise model
- [ ] DAMAS/CLEAN-SC 후처리
각 단계는 정확성 test를 먼저 통과시킨 뒤 UI 또는 최적화를 진행한다. 초기 milestone에서는 잔향, 정확성 test를 먼저 통과시킨 뒤 UI 또는 최적화를 진행한다는 원칙은 유지한다. 단계 5 기능은
DAMAS/CLEAN-SC, 대규모 dataset batch 변환을 완료 조건으로 삼지 않는다. 자유음장 DAS 결과를 바꾸지 않는 선택형 확장으로 추가한다.
+192
View File
@@ -0,0 +1,192 @@
<!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="micarrsim 빠른 시작, 전체 데이터 흐름, 결과 해석과 문제 해결"
/>
<title>micarrsim 사용 설명서</title>
<link rel="stylesheet" href="/packages/ui/app-shell.css" />
<link rel="stylesheet" href="/docs/styles.css" />
</head>
<body>
<header class="guide-header">
<a class="guide-brand" href="/">← micarrsim 홈</a>
<nav aria-label="문서 목차">
<a href="#quick-start">빠른 시작</a>
<a href="#full-workflow">내 데이터</a>
<a href="#results">결과 해석</a>
<a href="#troubleshooting">문제 해결</a>
</nav>
</header>
<main class="guide-shell">
<section class="guide-hero">
<p class="eyebrow">Local user guide</p>
<h1>설치 없이 시작하는<br />음향 카메라 실험</h1>
<p>
Windows 10/11과 최신 Chrome 또는 Edge만 있으면 됩니다. 모든 입력과 계산은 이
컴퓨터의 브라우저 안에서 처리되며 외부 서버로 업로드되지 않습니다.
</p>
</section>
<section class="guide-section" id="quick-start">
<div class="section-number">01</div>
<div>
<p class="eyebrow">30초 확인</p>
<h2>기본 결과 하나 저장하기</h2>
<ol class="guide-steps">
<li><code>start.cmd</code>를 더블클릭합니다.</li>
<li>홈에서 <a href="/apps/acoustic-camera/">Acoustic Camera</a>를 엽니다.</li>
<li><strong>▶ 기본 예제 실행</strong>을 누릅니다.</li>
<li>완료되면 <strong>Result JSON</strong>을 눌러 다운로드 폴더에 저장합니다.</li>
</ol>
<aside class="callout">
브라우저가 자동으로 열리지 않으면
<a href="http://127.0.0.1:4173/">http://127.0.0.1:4173/</a>을 직접 여세요.
종료할 때는 서버 콘솔에서 <kbd>Ctrl</kbd>+<kbd>C</kbd>를 누릅니다.
</aside>
</div>
</section>
<section class="guide-section" id="full-workflow">
<div class="section-number">02</div>
<div>
<p class="eyebrow">5분 전체 흐름</p>
<h2>내 장면을 만들고 Camera에서 실행하기</h2>
<ol class="guide-steps">
<li>
<a href="/apps/dataset-builder/">Dataset Builder</a>에서 이미지와 오디오를
선택하고 음원 위치, gain, 시간, ambient를 설정합니다.
</li>
<li><strong>Scene JSON 저장</strong>을 누르고 사용한 원본 파일과 함께 보관합니다.</li>
<li><strong>Scene 저장 후 Camera 열기</strong>를 누릅니다.</li>
<li>
Camera에서 파일을 <strong>Scene → Array → Experiment → Assets</strong> 순서로
가져옵니다. 내 Array/Experiment가 없다면 내장 기본값을 그대로 둡니다.
</li>
<li>
Assets에서 Builder에 사용한 원본 이미지와 오디오를 한 번에 다시 선택합니다.
준비 상태가 모두 초록색인지 확인합니다.
</li>
<li><strong>Auto (GPU→CPU)</strong>로 실행한 뒤 Result JSON을 저장합니다.</li>
</ol>
<aside class="callout callout--important" id="asset-reconnect">
<strong>Scene JSON에는 원본 파일이 들어 있지 않습니다.</strong>
파일명·종류·SHA-256 manifest만 저장됩니다. 브라우저는 새 페이지에서 로컬 파일
권한을 유지할 수 없으므로 같은 원본을 다시 선택해야 합니다. 이름이 같아도 내용의
SHA-256이 다르면 연결되지 않습니다.
</aside>
<div class="limit-grid" aria-label="asset 한도">
<div><strong>128 MiB</strong><span>원본 파일 하나</span></div>
<div><strong>512 MiB</strong><span>연결 파일 합계</span></div>
<div><strong>192 MiB</strong><span>decode된 오디오 합계</span></div>
</div>
</div>
</section>
<section class="guide-section">
<div class="section-number">03</div>
<div>
<p class="eyebrow">Compute backend</p>
<h2>속도와 격자 선택</h2>
<div class="guide-table-wrap">
<table>
<thead><tr><th>선택</th><th>동작</th><th>권장 상황</th></tr></thead>
<tbody>
<tr><td>Auto (GPU→CPU)</td><td>WebGPU를 먼저 시도하고 불가능하면 CPU 사용</td><td>기본 권장</td></tr>
<tr><td>WebGPU 강제</td><td>GPU 장치가 없거나 실패하면 fallback 없이 오류</td><td>GPU 비교·측정</td></tr>
<tr><td>CPU 강제</td><td>동일한 자유음장 DAS를 CPU에서 계산</td><td>호환성 확인·기준 비교</td></tr>
</tbody>
</table>
</div>
<p class="guide-copy">
먼저 50×50을 권장합니다. 100×100은 4배, 200×200은 16배의 scan point를 계산합니다.
실행 중에는 취소할 수 있으며, 취소하거나 실패해도 이전 완료 결과는 유지됩니다.
</p>
</div>
</section>
<section class="guide-section" id="results">
<div class="section-number">04</div>
<div>
<p class="eyebrow">Result JSON</p>
<h2>무엇이 저장되나요?</h2>
<dl class="result-list">
<div><dt>rawMap / displayMap</dt><dd>선형 power 원본과 화면 표시용 acoustic map</dd></div>
<div><dt>peaks</dt><dd>검출 위치, source 매칭, m·pixel 위치 오차</dd></div>
<div><dt>metrics</dt><dd>energy 비율, precision/recall, sidelobe, main-lobe 폭</dd></div>
<div><dt>evaluation</dt><dd>검출 threshold, 반경, tolerance 등 지표 계산 조건</dd></div>
<div><dt>timing / environment</dt><dd>backend, grid·scan 범위, 장치 정보와 구간별 시간</dd></div>
<div><dt>inputHash</dt><dd>Array·Scene·Experiment 입력을 묶은 SHA-256 재현성 식별자</dd></div>
</dl>
<aside class="callout">
평가는 tone 음원을 해당 주파수 대역이 tone 주파수를 포함할 때만 정답으로 사용합니다.
noise와 asset 음원은 현재 모든 대역의 정답으로 간주합니다. 녹음 파일의 실제 대역별
에너지를 자동 판정하는 기능은 아직 없습니다.
</aside>
</div>
</section>
<section class="guide-section" id="troubleshooting">
<div class="section-number">05</div>
<div>
<p class="eyebrow">Troubleshooting</p>
<h2>막혔을 때 확인할 것</h2>
<details open>
<summary>화면이 비거나 module/Worker 오류가 납니다.</summary>
<p><code>index.html</code>을 직접 열지 말고 반드시 <code>start.cmd</code>로 실행하세요.</p>
</details>
<details>
<summary>4173 포트를 사용할 수 없다고 나옵니다.</summary>
<p><code>start.cmd -Port 4174</code>처럼 다른 포트를 지정하고 <code>http://127.0.0.1:4174/</code>을 여세요.</p>
</details>
<details>
<summary>Experiment를 가져올 수 없습니다.</summary>
<p>Scene과 Array를 먼저 가져오세요. Experiment의 <code>sceneId</code>와 <code>arrayId</code>가 현재 문서와 같아야 합니다.</p>
</details>
<details>
<summary>Assets가 준비되지 않습니다.</summary>
<p>Builder에서 사용한 것과 바이트가 같은 원본 파일을 다시 선택하세요. 파일명뿐 아니라 SHA-256도 일치해야 합니다.</p>
</details>
<details>
<summary>WebGPU가 없거나 실행이 너무 느립니다.</summary>
<p>Backend를 Auto 또는 CPU로 두고 50×50 grid부터 실행하세요. 최신 Chrome/Edge와 그래픽 드라이버도 확인하세요.</p>
</details>
</div>
</section>
<section class="guide-section">
<div class="section-number">06</div>
<div>
<p class="eyebrow">Scope & privacy</p>
<h2>현재 모델의 범위</h2>
<p class="guide-copy">
micarrsim 1.0은 점 음원, 구면 감쇠·분수 지연을 쓰는 자유음장 모델과 conventional
DAS를 제공합니다. 실내 반사·잔향, 실제 마이크 위치 오차와 비선형성, DAMAS,
CLEAN-SC, DCASE/MIMII 자동 adapter는 현재 범위에 포함되지 않습니다. 결과는 실제
장비의 절대 성능 보증이 아니라 같은 조건에서 배열을 비교하는 재현 가능한 기준입니다.
</p>
<p class="guide-copy">
원본 asset은 브라우저 메모리에서만 읽고 서버나 외부 네트워크로 보내지 않습니다.
다만 Scene JSON에는 원본 파일명이 남을 수 있으므로 외부 공유 전 확인하세요.
</p>
</div>
</section>
<section class="reference-grid" aria-label="상세 문서">
<a href="/README.md"><strong>전체 README</strong><span>설치·실행·구조·제약</span></a>
<a href="/apps/dataset-builder/README.md"><strong>Builder 설명서</strong><span>source·ambient·Preview WAV</span></a>
<a href="/apps/acoustic-camera/README.md"><strong>Camera 설명서</strong><span>backend·결과·비교</span></a>
<a href="/docs/data-contracts.md"><strong>데이터 계약</strong><span>JSON Schema와 재현성</span></a>
<a href="/docs/execution-plan.md"><strong>실행 계획</strong><span>구현 상태와 향후 단계</span></a>
<a href="/tests/"><strong>브라우저 테스트</strong><span>단위·통합 테스트 실행</span></a>
<a href="/benchmarks/webgpu-scaling.html"><strong>WebGPU 측정</strong><span>현재 장치 benchmark</span></a>
</section>
</main>
</body>
</html>
+291
View File
@@ -0,0 +1,291 @@
.guide-header {
position: sticky;
z-index: 10;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
min-height: 64px;
padding: 12px clamp(18px, 4vw, 48px);
border-bottom: 1px solid var(--line);
background: rgb(8 17 15 / 92%);
backdrop-filter: blur(18px);
}
.guide-brand,
.guide-header nav a {
color: inherit;
font-size: 0.82rem;
font-weight: 700;
text-decoration: none;
}
.guide-header nav {
display: flex;
flex-wrap: wrap;
gap: 18px;
color: var(--muted);
}
.guide-shell {
width: min(980px, calc(100% - 40px));
margin: 0 auto;
padding: clamp(48px, 8vw, 96px) 0 72px;
}
.guide-hero {
max-width: 820px;
margin-bottom: clamp(64px, 10vw, 112px);
}
.guide-hero h1 {
margin-bottom: 24px;
font-size: clamp(2.8rem, 8vw, 6.4rem);
letter-spacing: -0.07em;
line-height: 0.98;
}
.guide-hero > p:last-child,
.guide-copy {
color: #c8d9d3;
line-height: 1.8;
}
.guide-hero > p:last-child {
max-width: 720px;
font-size: clamp(1rem, 2vw, 1.2rem);
}
.guide-section {
display: grid;
grid-template-columns: 70px minmax(0, 1fr);
gap: clamp(18px, 4vw, 42px);
padding: clamp(36px, 7vw, 68px) 0;
border-top: 1px solid var(--line);
scroll-margin-top: 72px;
}
.section-number {
color: var(--mint);
font-size: 0.75rem;
font-weight: 760;
letter-spacing: 0.12em;
}
.guide-section h2 {
margin-bottom: 28px;
font-size: clamp(1.8rem, 4vw, 3rem);
letter-spacing: -0.05em;
}
.guide-steps {
display: grid;
gap: 14px;
margin: 0;
padding-left: 1.35rem;
color: #c8d9d3;
line-height: 1.75;
}
.guide-steps li {
padding-left: 8px;
}
.guide-steps a,
.callout a {
color: var(--mint);
}
code,
kbd {
border: 1px solid var(--line);
border-radius: 6px;
color: var(--text);
background: rgb(255 255 255 / 5%);
font-family: "Cascadia Code", Consolas, monospace;
}
code {
padding: 0.12em 0.4em;
}
kbd {
padding: 2px 6px;
box-shadow: 0 2px 0 var(--line);
font-size: 0.78rem;
}
.callout {
margin-top: 26px;
padding: 18px 20px;
border-left: 3px solid var(--cyan);
border-radius: 0 12px 12px 0;
color: var(--muted);
background: rgb(88 199 232 / 7%);
line-height: 1.7;
}
.callout--important {
border-color: var(--amber);
background: rgb(255 197 110 / 7%);
}
.callout strong {
color: var(--text);
}
.limit-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 14px;
}
.limit-grid div {
padding: 18px;
border: 1px solid var(--line);
border-radius: 12px;
background: rgb(255 255 255 / 2%);
}
.limit-grid strong,
.limit-grid span {
display: block;
}
.limit-grid strong {
color: var(--mint);
font-size: 1.2rem;
}
.limit-grid span {
margin-top: 5px;
color: var(--muted);
font-size: 0.78rem;
}
.guide-table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: 14px;
}
.guide-table-wrap table {
width: 100%;
border-collapse: collapse;
color: #c8d9d3;
font-size: 0.88rem;
}
.guide-table-wrap th,
.guide-table-wrap td {
padding: 14px 16px;
border-bottom: 1px solid var(--line);
text-align: left;
vertical-align: top;
}
.guide-table-wrap th {
color: var(--text);
background: rgb(255 255 255 / 4%);
}
.guide-table-wrap tr:last-child td {
border-bottom: 0;
}
.result-list {
display: grid;
gap: 10px;
margin: 0;
}
.result-list div {
display: grid;
grid-template-columns: minmax(150px, 0.4fr) minmax(0, 1fr);
gap: 18px;
padding: 14px 0;
border-bottom: 1px solid var(--line);
}
.result-list dt {
color: var(--mint);
font-family: "Cascadia Code", Consolas, monospace;
font-size: 0.86rem;
}
.result-list dd {
margin: 0;
color: var(--muted);
line-height: 1.6;
}
details {
padding: 16px 0;
border-bottom: 1px solid var(--line);
}
summary {
cursor: pointer;
font-weight: 700;
}
details p {
margin: 12px 0 0;
color: var(--muted);
line-height: 1.7;
}
.reference-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
padding-top: 48px;
border-top: 1px solid var(--line);
}
.reference-grid a {
padding: 20px;
border: 1px solid var(--line);
border-radius: 14px;
background: var(--surface);
text-decoration: none;
}
.reference-grid a:hover,
.reference-grid a:focus-visible {
border-color: var(--mint);
outline: none;
}
.reference-grid strong,
.reference-grid span {
display: block;
}
.reference-grid span {
margin-top: 8px;
color: var(--muted);
font-size: 0.78rem;
line-height: 1.5;
}
@media (max-width: 720px) {
.guide-header nav {
display: none;
}
.guide-section {
grid-template-columns: 1fr;
}
.limit-grid,
.reference-grid {
grid-template-columns: 1fr;
}
.result-list div {
grid-template-columns: 1fr;
gap: 6px;
}
}
+43 -4
View File
@@ -25,6 +25,26 @@
</div> </div>
</section> </section>
<section class="workflow-card" aria-labelledby="workflow-title">
<div class="workflow-card__heading">
<div>
<p class="eyebrow">Local workflow</p>
<h2 id="workflow-title">파일이 흐르는 방식</h2>
</div>
<a href="/docs/#asset-reconnect">왜 원본을 다시 선택하나요?</a>
</div>
<ol class="workflow-list">
<li><strong>Dataset Builder</strong><span>음원과 위치 만들기</span></li>
<li><strong>Scene JSON + 원본 assets</strong><span>같이 보관하기</span></li>
<li><strong>Acoustic Camera</strong><span>배열·DAS 실행하기</span></li>
<li><strong>Result JSON</strong><span>map·지표 저장하기</span></li>
</ol>
<p class="workflow-note">
원본 파일은 JSON에 포함되거나 외부로 업로드되지 않습니다. Camera에서 같은 파일을 다시
선택하면 SHA-256으로 확인한 뒤 연결합니다.
</p>
</section>
<section class="launcher-grid" aria-label="도구 선택"> <section class="launcher-grid" aria-label="도구 선택">
<a class="launcher-card" href="/apps/dataset-builder/"> <a class="launcher-card" href="/apps/dataset-builder/">
<span class="launcher-card__number">01</span> <span class="launcher-card__number">01</span>
@@ -32,7 +52,7 @@
<h2>Dataset Builder</h2> <h2>Dataset Builder</h2>
<p>기기 이미지에 음원을 배치하고 ambient를 혼합해 재현 가능한 장면을 만듭니다.</p> <p>기기 이미지에 음원을 배치하고 ambient를 혼합해 재현 가능한 장면을 만듭니다.</p>
</div> </div>
<span class="launcher-card__action">장면 만들기 →</span> <span class="launcher-card__action">새 Scene 만들기 →</span>
</a> </a>
<a class="launcher-card launcher-card--accent" href="/apps/acoustic-camera/"> <a class="launcher-card launcher-card--accent" href="/apps/acoustic-camera/">
@@ -41,14 +61,33 @@
<h2>Acoustic Camera</h2> <h2>Acoustic Camera</h2>
<p>배열 응답을 계산하고 주파수 대역별 DAS 결과를 ground truth와 비교합니다.</p> <p>배열 응답을 계산하고 주파수 대역별 DAS 결과를 ground truth와 비교합니다.</p>
</div> </div>
<span class="launcher-card__action">시뮬레이션 열기 →</span> <span class="launcher-card__action">기본 예제로 바로 실행 →</span>
</a> </a>
</section> </section>
<section class="quick-start-grid" aria-label="빠른 시작">
<article class="quick-start-card">
<p class="eyebrow">30초 확인</p>
<h2>설정 없이 첫 결과 만들기</h2>
<p>Acoustic Camera를 열어 <strong>▶ 기본 예제 실행</strong>을 누르고, 완료되면 <strong>Result JSON</strong>을 저장하세요.</p>
<a href="/apps/acoustic-camera/">기본 예제 열기 →</a>
</article>
<article class="quick-start-card">
<p class="eyebrow">내 데이터</p>
<h2>Builder부터 끝까지</h2>
<p>Scene을 저장한 뒤 Camera에서 <strong>Scene → Array → Experiment → Assets</strong> 순서로 가져오면 참조 오류를 줄일 수 있습니다.</p>
<a href="/docs/#full-workflow">5분 안내 보기 →</a>
</article>
</section>
<footer class="landing-footer"> <footer class="landing-footer">
<a href="/docs/execution-plan.md">실행 계획</a> <a href="/docs/">사용 설명서</a>
<span aria-hidden="true">·</span> <span aria-hidden="true">·</span>
<a href="https://github.com/" rel="noreferrer">프로젝트는 로컬에서만 실행됩니다</a> <a href="/tests/">테스트</a>
<span aria-hidden="true">·</span>
<a href="/benchmarks/webgpu-scaling.html">성능 측정</a>
<span aria-hidden="true">·</span>
<a href="/docs/execution-plan.md">실행 계획</a>
</footer> </footer>
</main> </main>
<script type="module" src="/packages/ui/landing.js"></script> <script type="module" src="/packages/ui/landing.js"></script>
+118 -2
View File
@@ -114,11 +114,92 @@ p {
background: rgb(255 197 110 / 8%); background: rgb(255 197 110 / 8%);
} }
.workflow-card {
margin-top: clamp(42px, 7vw, 72px);
padding: clamp(22px, 4vw, 34px);
border: 1px solid var(--line);
border-radius: 22px;
background: rgb(255 255 255 / 2%);
}
.workflow-card__heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: 24px;
}
.workflow-card__heading h2,
.quick-start-card h2 {
margin-bottom: 0;
letter-spacing: -0.035em;
}
.workflow-card__heading a,
.quick-start-card a {
color: var(--mint);
font-size: 0.84rem;
font-weight: 700;
text-decoration: none;
}
.workflow-list {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 10px;
margin: 24px 0 16px;
padding: 0;
list-style: none;
counter-reset: workflow;
}
.workflow-list li {
position: relative;
min-height: 104px;
padding: 18px;
border: 1px solid rgb(101 242 191 / 18%);
border-radius: 14px;
background: rgb(101 242 191 / 5%);
counter-increment: workflow;
}
.workflow-list li::before {
display: block;
margin-bottom: 18px;
color: var(--mint);
content: "0" counter(workflow);
font-size: 0.7rem;
font-weight: 760;
letter-spacing: 0.1em;
}
.workflow-list strong,
.workflow-list span {
display: block;
}
.workflow-list strong {
font-size: 0.9rem;
}
.workflow-list span {
margin-top: 6px;
color: var(--muted);
font-size: 0.78rem;
}
.workflow-note {
margin-bottom: 0;
color: var(--muted);
font-size: 0.82rem;
line-height: 1.65;
}
.launcher-grid { .launcher-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px; gap: 18px;
margin-top: clamp(56px, 9vw, 104px); margin-top: 18px;
} }
.launcher-card { .launcher-card {
@@ -181,6 +262,30 @@ p {
color: var(--cyan); color: var(--cyan);
} }
.quick-start-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
margin-top: 18px;
}
.quick-start-card {
padding: clamp(22px, 4vw, 32px);
border: 1px solid var(--line);
border-radius: 20px;
background: var(--surface);
}
.quick-start-card p:not(.eyebrow) {
margin: 18px 0;
color: var(--muted);
line-height: 1.7;
}
.quick-start-card strong {
color: var(--text);
}
.landing-footer { .landing-footer {
display: flex; display: flex;
gap: 10px; gap: 10px;
@@ -259,10 +364,21 @@ p {
} }
@media (max-width: 720px) { @media (max-width: 720px) {
.launcher-grid { .launcher-grid,
.quick-start-grid,
.workflow-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.workflow-card__heading {
align-items: start;
flex-direction: column;
}
.workflow-list li {
min-height: auto;
}
.launcher-card { .launcher-card {
min-height: 260px; min-height: 260px;
} }
+28 -1
View File
@@ -1,3 +1,30 @@
# Integration Tests # Integration Tests
두 앱 사이 manifest 계약과 end-to-end golden scenario를 검증합니다. 실제 브라우저 API, Worker, 다운로드와 앱 간 데이터 계약을 함께 검증합니다.
## 실행
1. 저장소의 `start.cmd`를 실행합니다.
2. 최신 Edge 또는 Chrome에서 <http://127.0.0.1:4173/tests/>을 엽니다.
3. 모든 항목이 끝날 때까지 페이지를 열어 둡니다.
Unit과 integration test가 같은 페이지에서 실행됩니다. WebGPU 장치 초기화와 192채널 baseline은
수 초 이상 걸릴 수 있습니다. 페이지 제목과 요약이 `PASS`인지 확인하고 실패 항목의 stack을
확인하세요. `index.html`을 직접 열면 HTTP, module과 Worker 테스트가 올바르게 동작하지 않습니다.
## 범위
- `cpu-baseline.test.js`: 192채널·50×50 synthetic fixture의 CPU DAS와 위치 기준
- `webgpu.test.js`: 실제 adapter가 있으면 CPU/GPU map 오차, peak와 강제 bin/point batching;
adapter가 없으면 Auto CPU fallback
- `server.test.js`: 숨김 저장소 경로 차단, GET/HEAD 제한과 보안 response headers
- `builder-ui.test.js`: 이미지·오디오 연결, canvas 편집, Preview WAV/Scene 다운로드, SHA 재연결과
잘못된 import 보존
- `camera-ui.test.js`: Dedicated Worker CPU/WebGPU 실행, 취소, Result schema, rectangular/Fermat 비교
- `workflow-ui.test.js`: Dataset Builder의 Scene JSON과 원본 assets를 Acoustic Camera에 넘겨
Result JSON을 저장하는 end-to-end 흐름
WebGPU adapter가 없는 환경에서는 장치가 필요한 비교 항목을 건너뛰거나 Auto fallback을 검증하므로
CPU-only PASS도 가능합니다. 실제 장치의 scaling과 benchmark JSON 저장은
<http://127.0.0.1:4173/benchmarks/webgpu-scaling.html>에서 별도로 실행하세요. 기존 실측 조건과
해석은 [`../../docs/benchmarks/README.md`](../../docs/benchmarks/README.md)에 있습니다.
+24 -1
View File
@@ -1,3 +1,26 @@
# Unit Tests # Unit Tests
음향 수식, 좌표 변환, CPU/GPU 수치 일치 테스트를 둡니다. 브라우저에서 실행되는 결정론적 수치·계약 테스트입니다. 별도 Node.js test runner가 필요하지
않습니다.
## 실행
1. 저장소의 `start.cmd`를 실행합니다.
2. 최신 Edge 또는 Chrome에서 <http://127.0.0.1:4173/tests/>을 엽니다.
3. 페이지 상단의 통과/실패 합계와 각 테스트 결과를 확인합니다.
Unit과 integration module은 같은 browser runner에서 순서대로 실행됩니다. 다시 실행하려면 페이지를
새로 고치세요. 다른 포트를 사용했다면 URL의 포트도 바꾸며, `tests/index.html`을 파일로 직접 열면
안 됩니다.
## 범위
- `contracts.test.js`: Array·Scene·Experiment·Result schema, 관계 검증, 안전한 asset 경로와 fixture
- `acoustics.test.js`: seed 재현성, mix/정규화, 자유음장 전파, calibration, STFT와 CPU DAS
- `builder.test.js`: Builder scene model, source/asset 조작, 오디오 크기와 WAV 사전 검증
- `evaluation.test.js`: local peak, truth matching, 위치 오차, energy, sidelobe와 main-lobe 지표
- `camera.test.js`: canonical input hash, workload 추정, 거리 이동, Result 생성과 schema 검증
- `webgpu.test.js`: WGSL buffer packing, bin/point batching, 장치 한계와 typed CPU fallback 정책
WebGPU unit test 대부분은 실제 GPU 없이 planner와 binary layout을 검증합니다. 실제 GPU 수치 일치는
integration test에서 adapter가 있을 때 수행합니다.