stella-score-reader / docs /analyze-api-spec.md
CAY96
update docs
bc8019c
# Analyze API Spec (단일 μŠ€νƒœν”„ / v1)
이 λ¬Έμ„œλŠ” **단일 μ§„μ‹€ 곡급원(SSOT)**이닀. κ΅¬ν˜„Β·ν…ŒμŠ€νŠΈΒ·ν”„λ‘ νŠΈ 계약은 μ—¬κΈ° μ •μ˜ν•œ μŠ€ν‚€λ§ˆμ™€ 의미λ₯Ό λ”°λ₯Έλ‹€.
- **μ—”λ“œν¬μΈνŠΈ:** `POST /analyze`
- **좜λ ₯:** λͺ¨λ°”일/μ›Ήμ—μ„œ λ°”λ‘œ μ“Έ 수 μžˆλŠ” **JSON ν•œ 덩어리**(MusicXML 등은 HTTP 응닡에 ν¬ν•¨ν•˜μ§€ μ•ŠμŒ; μ„œλ²„ λ‚΄λΆ€ μ „μš© κ°€λŠ₯).
- **μ˜ˆμ‹œ 응닡 묢음:** `docs/analyze-response-format-examples.json`
---
## 1) μ œν’ˆ λ²”μœ„ (μ‹¬ν”Œ λͺ¨λ“œ)
### 1.1 μž…λ ₯ 이미지에 λŒ€ν•œ κ°€μ •
1. **ν•œ 이미지 = μ˜€μ„ (staff) μ •ν™•νžˆ ν•œ 쀄**만 μžˆλ‹€κ³  κ°€μ •ν•œλ‹€.
- 볡수 쀄(예: ν”Όμ•„λ…Έ λŒ€λ³΄ν‘œ), ν•©μ°½ 닀쀑 μŠ€νƒœν”„ 등은 **이 μŠ€νŽ™μ˜ λ²”μœ„ λ°–**이닀.
2. 사진은 **인쇄 악보λ₯Ό μΉ΄λ©”λΌλ‘œ 찍은 λ’€ 크둭**ν•œ 것일 수 μžˆλ‹€.
- 쒅이 νœ˜μ–΄μ§, 기울기, μ‘°λͺ…, 손글씨 λ‚™μ„œ 등이 μžˆμ„ 수 있으며, μ„œλ²„λŠ” **μ „μ²˜λ¦¬(OpenCV λ“±) + ν•„μš” μ‹œ λ”₯λŸ¬λ‹ 기반 OMR**으둜 이에 λŒ€μ‘ν•œλ‹€(κ΅¬ν˜„ λ””ν…ŒμΌμ€ μ½”λ“œΒ·λ³„λ„ λ¬Έμ„œ; μ—¬κΈ°μ„œλŠ” **μž…μΆœλ ₯ 계약**만 κ³ μ •ν•œλ‹€).
### 1.2 음ν–₯ λ‚΄μš©μ— λŒ€ν•œ κ°€μ •
- **λ‹¨μ„ μœ¨(λ‹¨μŒ)** λ˜λŠ” **ν™”μŒ(λ™μ‹œμ— μšΈλ¦¬λŠ” 음, μ΅œλŒ€ 2μ„±λΆ€)**이 올 수 μžˆλ‹€.
- ν™”μŒμΈ 경우, **κ²°κ³Ό λ©œλ‘œλ”” μ‹œν€€μŠ€λŠ” 항상 β€œκ°€μž₯ 높은 μŒν‘œβ€λ§Œ** μ‚¬μš©ν•œλ‹€(2μ„±λΆ€λ₯Ό ν•œ 음으둜 μ€„μ΄λŠ” κ·œμΉ™).
- **μŒμžλ¦¬ν‘œ(clef)** 와 **μ‘°ν‘œ(key signature)** λŠ” 이미지 맨 μ•žμ—μ„œ 잘렀 λ‚˜κ°”μ„ 수 μžˆλ‹€. 이 두 μ •λ³΄λŠ” **항상 ν΄λΌμ΄μ–ΈνŠΈκ°€ μš”μ²­κ³Ό ν•¨κ»˜ 제곡**ν•œλ‹€(μ•„λž˜ `score_context`).
### 1.3 μ—¬λŸ¬ μž₯ 이미지
- ν•œ μš”μ²­μ— **μ—¬λŸ¬ 이미지**λ₯Ό 보낼 수 μžˆλ‹€. 파일 **μ—…λ‘œλ“œ μˆœμ„œ = 악보 μ½λŠ” μˆœμ„œ(μ™Όμͺ½β†’μ˜€λ₯Έμͺ½ 이어짐)**이닀.
- μ„œλ²„λŠ” **μ΄λ―Έμ§€λ§ˆλ‹€ λ…λ¦½μ μœΌλ‘œ 인식 νŒŒμ΄ν”„λΌμΈ**을 돌릴 수 μžˆλ‹€.
- μ‘λ‹΅μ˜ **λ©œλ‘œλ”” νƒ€μž„λΌμΈμ€ ν•˜λ‚˜**둜 λ³‘ν•©ν•œλ‹€. 즉, `onset_div` / `duration_div` λŠ” **마치 ν•œ μž₯짜리 μž…λ ₯μ΄μ—ˆλ˜ κ²ƒμ²˜λŸΌ** 이어진 단일 μ‹œν€€μŠ€λ‹€.
---
## 2) μš”μ²­ (Request)
### 2.1 전솑 ν˜•μ‹
- **Content-Type:** `multipart/form-data`
### 2.2 ν•„λ“œ
| ν•„λ“œ | ν•„μˆ˜ | μ„€λͺ… |
|------|------|------|
| `score_context` | **예** | JSON λ¬Έμžμ—΄. μŒλ†’μ΄ 해석에 ν•„μš”ν•œ **clefΒ·μ‘°ν‘œ**(및 선택 ν•„λ“œ). ν˜•μ‹μ€ Β§2.3. |
| `file` | 쑰건뢀 | 단일 이미지 (`jpg`, `jpeg`, `png`, `webp`). |
| `files` | 쑰건뢀 | 동일 ν‚€ 반볡으둜 μ—¬λŸ¬ 이미지. 예: `-F "files=@a.png" -F "files=@b.png"` |
| `options` | μ•„λ‹ˆμ˜€ | JSON λ¬Έμžμ—΄. ν˜•μ‹μ€ Β§2.4. |
κ·œμΉ™:
- `file` κ³Ό `files` 쀑 **ν•˜λ‚˜λŠ” λ°˜λ“œμ‹œ** μ‘΄μž¬ν•΄μ•Ό ν•œλ‹€.
- λ‘˜ λ‹€ 있으면 **`files` μš°μ„ **ν•œλ‹€.
- `score_context` κ°€ μ—†κ±°λ‚˜ JSON νŒŒμ‹± μ‹€νŒ¨ μ‹œ **400**.
### 2.3 `score_context` (JSON 객체)
**ν•„μˆ˜:**
| ν‚€ | νƒ€μž… | μ„€λͺ… |
|----|------|------|
| `clef` | `string` | μŒμžλ¦¬ν‘œ. ν—ˆμš© κ°’: `treble`, `bass`. (μΆ”ν›„ `alto`, `tenor` λ“± ν™•μž₯ κ°€λŠ₯ν•˜λ‚˜ v1 κ΅¬ν˜„μ€ treble/bass μš°μ„ .) |
| `key_signature` | `object` | μ‘°ν‘œ. |
| `key_signature.fifths` | `integer` | μž₯μ‘° κΈ°μ€€ **μ‘°ν‘œμ˜ 샡/ν”Œλž« 개수**. λ²”μœ„ **-6 ~ 6**. 음수=ν”Œλž«, μ–‘μˆ˜=샡, `0`=λ‹€μž₯μ‘°. |
**선택 (v1μ—μ„œ ꢌμž₯Β·κΈ°λ³Έκ°’ 있음):**
| ν‚€ | νƒ€μž… | κΈ°λ³Έ | μ„€λͺ… |
|----|------|------|------|
| `time_signature` | `string` | `"4/4"` | `"<beats>/<beat-type>"` ν˜•μ‹. 예: `"3/4"`, `"6/8"`. |
| `tempo_bpm_reference` | `number \| null` | `null` | UI·클릭 νŠΈλž™μš© μ°Έκ³  ν…œν¬. λ―Έμ§€μ • μ‹œ ν”„λ‘ νŠΈκ°€ 자체 κΈ°λ³Έκ°’ μ‚¬μš©. |
| `divisions` | `integer` | `4` | ν•œε››εˆ†ιŸ³η¬¦(4λΆ„μŒν‘œ)λ₯Ό λͺ‡ 개의 **division λ‹¨μœ„**둜 μͺΌκ°€μ§€. 응닡 `timeline.divisions` 와 동일해야 ν•œλ‹€. |
**적용 λ²”μœ„:**
- v1μ—μ„œλŠ” `score_context` λ₯Ό **ν•΄λ‹Ή μš”μ²­μ˜ λͺ¨λ“  이미지에 동일 적용**ν•œλ‹€.
- μž₯μ°¨ β€œμ΄λ―Έμ§€λ§ˆλ‹€ λ‹€λ₯Έ clef/key”가 ν•„μš”ν•˜λ©΄ `score_context` λ°°μ—΄(파일 μˆœμ„œμ™€ 동일 길이) λ“±μœΌλ‘œ **버전 μ—…**ν•œλ‹€.
### 2.4 `options` (JSON 객체)
κΈ°λ³Έκ°’ μ˜ˆμ‹œ:
```json
{
"return_debug": false,
"quantization": "1/8"
}
```
| ν‚€ | νƒ€μž… | κΈ°λ³Έ | μ„€λͺ… |
|----|------|------|------|
| `return_debug` | `boolean` | `false` | `true` 이면 `meta.debug` λ“± 디버그 μ „μš© ν•„λ“œ 포함 κ°€λŠ₯. |
| `quantization` | `string` | `"1/8"` | 리듬 μ–‘μžν™” κ·Έλ¦¬λ“œ. ν—ˆμš©: `"1/4"`, `"1/8"`, `"1/16"`. |
### 2.5 μš©λŸ‰Β·ν¬λ§· μ œν•œ (λΉ„κΈ°λŠ₯)
- 이미지 1μž₯ μ΅œλŒ€ **10MB**, μš”μ²­ 합계 μ΅œλŒ€ **30MB** (κ΅¬ν˜„μ€ κΈ°μ‘΄ μƒμˆ˜μ™€ 맞좜 수 있음).
- 지원 ν™•μž₯자: `jpg`, `jpeg`, `png`, `webp`.
- ꢌμž₯: κΈ΄ λ³€ **4096px** μ΄ν•˜ λ“±(κ΅¬ν˜„μ²΄ ꢌ고).
---
## 3) 응닡 (Response) β€” HTTP 200
**Content-Type:** `application/json`
### 3.1 μ΅œμƒμœ„ ꡬ쑰
```json
{
"request_id": "uuid",
"source": { "total_images": 2, "filenames": ["oneline_0.png", "oneline_1.png"] },
"score_context": { "clef": "treble", "key_signature": { "fifths": -1 }, "time_signature": "4/4", "tempo_bpm_reference": null, "divisions": 4, "source": "client" },
"timeline": { "divisions": 4, "time_signature": "4/4", "tempo_bpm_reference": null },
"melody": {
"voice_id": "melody1",
"reduction_rule": "top_note_max_two_parts",
"events": []
},
"segment_map": [],
"warnings": [],
"meta": {}
}
```
### 3.2 ν•„λ“œ μ„€λͺ…
- **`request_id`:** μΆ”μ Β·λ‘œκ·Έμš© UUID.
- **`source`:** μž…λ ₯ μš”μ•½. `filenames` λŠ” μ—…λ‘œλ“œ μˆœμ„œμ™€ λ™μΌν•œ 원본 파일λͺ…(μ—†μœΌλ©΄ μ„œλ²„κ°€ λΆ€μ—¬ν•œ placeholder κ°€λŠ₯).
- **`score_context`:** μš”μ²­μ„ μ •κ·œν™”Β·μ—μ½”ν•œ κ°’. `source: "client"` κ³ μ •(μ„œλ²„κ°€ 쑰성을 μ΄λ―Έμ§€μ—μ„œ 읽지 μ•ŠμŒμ„ λͺ…μ‹œ).
- **`timeline`:** μ „μ—­ 리듬 해석 κΈ°μ€€. `divisions` λŠ” **ν•œ 4λΆ„μŒν‘œ = divisions λ””λΉ„μ „** (MusicXML 관둀와 동일).
- **`melody`:** λ³‘ν•©λœ **단일 μ„±λΆ€** κ²°κ³Ό.
- **`reduction_rule`:** ν™”μŒ 처리 κ·œμΉ™. v1 κ³ μ • λ¬Έμžμ—΄ `top_note_max_two_parts` (μ΅œλŒ€ 2성뢀일 λ•Œ **졜고음**만 채택).
- **`segment_map`:** 각 μž…λ ₯ 이미지가 병합 `events` 의 μ–΄λŠ ꡬ간에 λŒ€μ‘ν•˜λŠ”μ§€(νŽΈμ§‘Β·ν•˜μ΄λΌμ΄νŠΈμš©). Β§3.4.
- **`warnings`:** μΈμ‹Β·ν’ˆμ§ˆ κ²½κ³  μ½”λ“œ λ¬Έμžμ—΄ λ°°μ—΄.
- **`meta`:** νŒŒμ΄ν”„λΌμΈ λͺ¨λ“œ, 이미지별 μ „μ²˜λ¦¬ μš”μ•½, `return_debug` μ‹œ 디버그 λ“±.
### 3.3 `melody.events[]` (Event)
μ‹œκ°„ μˆœμ„œλ‘œ μ •λ ¬. `onset_div` λŠ” **병합 ν›„ μ „μ—­** νƒ€μž„λΌμΈ κΈ°μ€€.
곡톡:
| ν•„λ“œ | νƒ€μž… | ν•„μˆ˜ | μ„€λͺ… |
|------|------|------|------|
| `event_id` | `string` | 예 | ν΄λΌμ΄μ–ΈνŠΈ νŽΈμ§‘Β·ν‚€ μ•ˆμ •μš© 고유 ID. |
| `type` | `string` | 예 | `"note"` \| `"rest"` |
| `duration_div` | `integer` | 예 | 길이(division). μ–‘μ˜ μ •μˆ˜. |
| `onset_div` | `integer` | 예 | μ‹œμž‘ μ‹œκ°(division). **0 이상 μ •μˆ˜.** |
`type === "note"` 일 λ•Œ μΆ”κ°€:
| ν•„λ“œ | νƒ€μž… | ν•„μˆ˜ | μ„€λͺ… |
|------|------|------|------|
| `step` | `string` | 예 | `C`, `D`, …, `B` |
| `octave` | `integer` | 예 | Scientific pitch octave. |
| `alter` | `integer` | 예 | μž„μ‹œν‘œ: `-1` ν”Œλž«, `0` μ—†μŒ, `1` 샡, `2` 더블샡 λ“±. |
| `pitch_midi` | `integer` | μ•„λ‹ˆμ˜€ | 0–127. 있으면 μž¬μƒκΈ°κ°€ μš°μ„  μ‚¬μš© κ°€λŠ₯. |
| `confidence` | `number` | μ•„λ‹ˆμ˜€ | 0–1 근사. |
| `segment_order` | `integer` | μ•„λ‹ˆμ˜€ | 이 μ΄λ²€νŠΈκ°€ κΈ°μ›ν•œ **μž…λ ₯ 이미지 순번**(1-based). |
| `bbox` | `array` | μ•„λ‹ˆμ˜€ | ν•΄λ‹Ή 이미지 **원본 ν”½μ…€ μ’Œν‘œκ³„** `[x, y, width, height]` . |
`type === "rest"` 일 λ•Œ:
- `step` / `octave` / `alter` λŠ” **ν¬ν•¨ν•˜μ§€ μ•ŠλŠ”λ‹€**(λ˜λŠ” λͺ¨λ‘ μƒλž΅).
**병합 κ·œμΉ™ (논리):**
- 이미지 `k` 의 첫 이벀트의 `onset_div` λŠ”, 직전 μ΄λ―Έμ§€λ“€μ—μ„œ κ³„μ‚°λœ **λˆ„μ  길이(λ˜λŠ” μΈμ‹λœ λ§ˆλ”” 경계)** 직후뢀터 이어진닀.
- κ΅¬ν˜„μ²΄λŠ” **ν”½μ…€Β·OMR 기반**으둜 간격을 μΆ”μ •ν•  수 μžˆμœΌλ―€λ‘œ, λ§ˆμ§€λ§‰ 음과 λ‹€μŒ 이미지 첫 음 사이에 **μž‘μ€ gap** 이 생기면 `warnings` 에 μ½”λ“œλ‘œ 남길 수 μžˆλ‹€.
### 3.4 `segment_map[]`
각 μž…λ ₯ μ„Έκ·Έλ¨ΌνŠΈλ³„ 메타:
| ν•„λ“œ | νƒ€μž… | μ„€λͺ… |
|------|------|------|
| `segment_id` | `string` | 예: `seg1` |
| `order` | `integer` | 1-based, μ—…λ‘œλ“œ μˆœμ„œ. |
| `filename` | `string` | 원본 파일λͺ…. |
| `width` | `integer` | 원본 이미지 λ„ˆλΉ„. |
| `height` | `integer` | 원본 이미지 높이. |
| `event_index_range` | `object` | `start` / `end` (inclusive) β€” `melody.events` λ°°μ—΄ 인덱슀. 병합 κ²°κ³Όκ°€ λΉ„μ–΄ 있으면 동일 인덱슀둜 ν‘œν˜„ κ°€λŠ₯. |
### 3.5 `warnings` (μ˜ˆμ‹œ μ½”λ“œ)
μ„œλ²„λŠ” ν•„μš”ν•œ 만큼 μΆ”κ°€ν•  수 μžˆλ‹€. 예:
- `staff_count_mismatch_expected_one`: ν•œ μ΄λ―Έμ§€μ—μ„œ κ²€μΆœλœ staff 후보가 1이 μ•„λ‹˜.
- `line_break_between_images`: 닀쀑 이미지 병합 μ‹œ 경계 νœ΄λ¦¬μŠ€ν‹± μ‚¬μš©.
- `timing_from_pixel_gaps_heuristic`: 리듬이 ν”½μ…€ 간격 좔정에 의쑴.
- `annotation_overlap_detected`: λ‚™μ„œΒ·μ› 등이 μŒν‘œμ™€ κ²ΉμΉ  κ°€λŠ₯.
- `neural_omr_model_unavailable`: 신경망 λΉ„ν™œμ„±/μ‹€νŒ¨ ν›„ 폴백.
- `large_deskew_correction_applied`: 큰 기울기 보정.
### 3.6 `meta` (ꢌμž₯ ν•˜μœ„ ν•„λ“œ)
- `pipeline_mode`: `string` β€” 예: `single_staff_v1`.
- `preprocess.segments[]`: 이미지별 μ „μ²˜λ¦¬ μš”μ•½(원본/μž‘μ—… 해상도, deskew 각도, μ‘°λͺ… μ •κ·œν™” μ—¬λΆ€ λ“±). κΈ°μ‘΄ κ΅¬ν˜„κ³Ό λ™μΌν•œ **정보 성격**을 μœ μ§€ν•˜λ˜, ν•„λ“œλŠ” κ΅¬ν˜„μ— 맞게 μ±„μš΄λ‹€.
- `debug`: `options.return_debug === true` 일 λ•Œλ§Œ.
---
## 4) 였λ₯˜ 응닡
| HTTP | 쑰건 |
|------|------|
| `400` | 파일 λˆ„λ½, `score_context` λˆ„λ½/νŒŒμ‹± μ‹€νŒ¨, 잘λͺ»λœ `options` |
| `413` | μš©λŸ‰ 초과 |
| `415` | 미지원 ν™•μž₯자 |
| `422` | μ΄λ―Έμ§€λŠ” μžˆμœΌλ‚˜ **μ‹ λ’°ν•  λ§Œν•œ 단일 μŠ€νƒœν”„**λ₯Ό λ§Œλ“€ 수 μ—†μŒ, λ˜λŠ” μœ νš¨ν•œ μ΄λ²€νŠΈκ°€ μ—†μŒ |
| `500` | μ„œλ²„ λ‚΄λΆ€ 였λ₯˜ |
였λ₯˜ λ°”λ””λŠ” FastAPI κΈ°λ³Έ `detail` λ˜λŠ” μ•„λž˜ ν˜•νƒœμ˜ JSON 쀑 ν•˜λ‚˜λ‘œ 톡일할 수 μžˆλ‹€(κ΅¬ν˜„ μ‹œ ν•œ κ°€μ§€λ‘œ κ³ μ • ꢌμž₯):
```json
{
"error": {
"code": "UNPROCESSABLE_STAFF_LAYOUT",
"message": "Expected exactly one staff per image.",
"details": { "segment_order": 2, "detected_staff_candidates": 2 }
}
}
```
---
## 5) κ΅¬ν˜„Β·ν’ˆμ§ˆ λ‘œλ“œλ§΅ (μš”μ•½)
1. 단일 μŠ€νƒœν”„ ROI μ •κ·œν™”(곑선 μ˜€μ„  보정은 단계적 λ„μž… κ°€λŠ₯).
2. λ…ΈνŠΈΒ·μ‰Όν‘œΒ·beam λ“± 인식(λ”₯λŸ¬λ‹ + κ·œμΉ™ 폴백).
3. ν™”μŒ β†’ 졜고음 λ¦¬λ•μ…˜.
4. 닀쀑 이미지 μˆœμ„œ 병합 및 `segment_map` / `segment_order` μ±„μš°κΈ°.
5. νšŒκ·€ ν…ŒμŠ€νŠΈ: `sample_imgs/sample_oneline_0.png`, `sample_imgs/sample_oneline_chord.png` 및 연속 두 μž₯ μ‹œλ‚˜λ¦¬μ˜€.
---
## 6) λͺ…μ‹œμ μœΌλ‘œ ν•˜μ§€ μ•ŠλŠ” 것 (v1)
- 닀쀑 μŠ€νƒœν”„ 악보 νƒ€μž… μΆ”μ •(`a`/`b`/`c` λ“±) 및 반주 뢄리.
- μ„±λΆ€ 라벨 `soprano`/`alto` λ“± μžλ™ μΆ”μ •.
- MusicXML을 HTTP 응닡 본문으둜 λ°˜ν™˜.
- μ΄λ―Έμ§€μ—μ„œ clef/key μžλ™ νŒλ…(ν΄λΌμ΄μ–ΈνŠΈ 제곡이 SSOT).
---
## 7) SSOT λ³€κ²½ 절차
μŠ€ν‚€λ§ˆΒ·μ˜λ―Έ λ³€κ²½ μ‹œ **λ°˜λ“œμ‹œ** λ‹€μŒμ„ ν•¨κ»˜ κ°±μ‹ ν•œλ‹€.
1. 이 λ¬Έμ„œ (`docs/analyze-api-spec.md`)
2. `docs/frontend-integration-guide.md`
3. `docs/analyze-response-format-examples.json`