# 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"` | `"/"` 형식. 예: `"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`