Files
VesiscanClinicalAndroid/docs/ALGO_MODE.md
T
dw.jang ccb44f0ea6 feat(algo): 레퍼런스 경로를 dev 스위치로 · 기본은 현행 유지
신 저장소에 맞춘 레퍼런스(piezo-phantom-test/vesiscan_test) 단계들을 옮기되,
demo-final 의 기본 동작은 건드리지 않았다. 2026-09-03 임상이 현재 HEAD 위에서
돌았으므로 그 값을 움직이는 변경을 기본 경로에 넣을 수 없다.

`AlgoMode.reference` (계측 화면 dev 패널 → "알고리즘", 기본 꺼짐) 를 켜면:

  · CCC 3단계 → 7단계
    envelope(①.9) · ant_tiebreak(①) · Λ-dip(①.7) · edge_post(①.8) ·
    lumen_return(①.95) · NTV(② Rule A 만) · fp_gate(③). inward_post 는 빠진다.
  · 파이프라인 4개 추가
    scale_recovery(2) · missing_outside 산출(4) · channel_confidence→untrusted(5b) ·
    CONF_DEMOTE(6). missing_outside 는 CCC **앞**에서 계산한다 — CCC 가 복구한
    채널은 방광 밖이 아니다.
  · BV
    si_floor/missing_outside 로 극 cap clamp 의 방향(상한/하한)을 관측 증거로 가름,
    bottom cap 제한 신설, 중앙 채널 보간 은행가 반올림,
    adaptive_large_bladder_relax 제거(b_si 를 0.85·a_ap 로 바닥 처리해 cap 축 비를
    정확히 0.85 로 만들어 바로 다음 cap 축 폴백을 경계에서 막고 있었다).

검출 파라미터(d_max 15 · inward_walk_win 4) · ANT_FLOOR_REF · cap 축 폴백 ·
ELLIPSE_CAP_R_MAX · TOP_CAP_EDGE_K · cap_d_adaptive · 프리셋 유도 dps/delay 는
ebddc52/20a48ad 에서 이미 기본 경로에 들어가 있어 양쪽 공통이다. 되돌리지 않았다.

검증
  · 기존 경로 무변경 — LegacyEquivalenceDumpTest 가 44 cycle × CCC on/off 를
    검출→BV 로 돌려 덤프하고, HEAD 코드 덤프와 대조. 88 행 전부 일치.
    (이 테스트는 일부러 AlgoMode 를 참조하지 않는다 — HEAD 에 없는 타입이라
     참조하면 대조 자체가 불가능해진다.)
  · 토글 배선 — AlgoModeSwitchTest: 켜면 44 중 40 cycle 이 갈리고
    missing_outside 가 산출되며, 끄면 기존 값이 정확히 재현된다.
  · 전체 30개 통과.

함께 고친 것

저장소가 갈리며 없어진 픽스처 경로(medilightv2android/data123)를
vesiscan-design-archive/data123 로 옮겼다. 5개 테스트가 FileNotFoundException 으로
조용히 실패하고 있었다.

그래서 ebddc52 이후 한 번도 안 돌던 CenterAlignerValidationTest 가 다시 돌았고
hit 7→8 · 10→11 로 어긋났다. 현행 레퍼런스 AnchorGuide(ver='v1') 를 같은 세션에
직접 돌려 보니 8 · 11 이었다 — 코틀린이 맞고 기대값이 낡은 것이다(구
v3_python_reference.py 기준, 현재 저장소에 없음). 기대값을 갱신했다.
bv_cv 는 현행 레퍼런스가 더 이상 내지 않아 대조할 수단이 없다. "Python 과
bit-exact" 라던 주석은 근거가 없어졌으므로 걷어내고 회귀 고정으로 성격을 바꿨다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 17:31:58 +09:00

114 lines
7.3 KiB
Markdown
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.
# 알고리즘 경로 두 갈래 (`AlgoMode`)
demo-final 은 팬텀 시연과 임상 계측이 함께 도는 브랜치다. 여기에 신 저장소
(`vesiscan_pre_product/vesiscan_android_user`)가 레퍼런스
(`piezo-phantom-test/vesiscan_test`)와 1:1 로 맞춘 검출·BV 단계들을 옮기되,
**기본 동작은 그대로 두고** dev 패널에서만 켤 수 있게 했다.
- 코드: [`managers/AlgoMode.kt`](../app/src/main/java/com/medithings/vesiscan/managers/AlgoMode.kt)
- UI: 계측 화면 dev 패널 → **알고리즘** 스위치 (기본 꺼짐)
- 상태는 프로세스 수명 동안만 유지된다. 앱을 다시 켜면 기존 경로로 돌아간다.
## "기존" 이 가리키는 것
`ebddc52`(검출·BV 레퍼런스 정합) 와 `20a48ad`(실리콘 cap 억제) 가 들어간 **현재
demo-final HEAD** 다. 2026-09-03 임상이 이 코드 위에서 돌았으므로, 그 값을 움직이는
변경은 기본 경로에 넣지 않는다.
따라서 아래 항목들은 **양쪽 공통**이며 모드와 무관하다:
| 항목 | 값 | 들어간 커밋 |
|---|---|---|
| `d_max` · `inward_walk_win` | 15 · 4 | `ebddc52` |
| prominence 기준 | span 바닥 (`ANT_FLOOR_REF`) | `ebddc52` |
| cap 축 붕괴 폴백 | `CAP_AXIS_RATIO_MAX` 0.85 / `CAP_AXIS_DMAX_MAX` 100 | `ebddc52` |
| `ELLIPSE_CAP_R_MAX` · `TOP_CAP_EDGE_K` | 0.92 · 0.6 | `ebddc52` |
| `cap_d_adaptive` (실리콘) | ("aspect", 80.0, 2.0) | `20a48ad` |
| dps · delay | 프리셋 유도 (v\*=1.936/6.85, r\*=1.897/7.651) | 기존 |
## 스위치를 켜면 바뀌는 것
부분 적용은 없다. 단계들이 서로를 전제하므로 섞으면 어느 쪽도 아닌 값이 나온다.
### 1. 교차채널 보정 (CCC) — 3단계 → 7단계
| | 기존 | 레퍼런스 |
|---|---|---|
| 순서 | `ant_tiebreak` → NTV(Rule A+B) → `inward_post` | ①.9 `envelope_research` → ① `ant_tiebreak` → ①.7 Λ-dip → ①.8 `edge_post_recovery` → ①.95 `lumen_return_recovery` → ② NTV(**Rule A 만**) → ③ `fp_channel_gate` |
| `inward_post` | 있음 | **없음** |
| NTV Rule B | 있음 | 없음 |
레퍼런스 쪽 데이터에서 각 단계가 결과를 바꾼 횟수(파이썬 ablation, 1512 케이스):
`ant_tiebreak` 158 · `edge_post_recovery` 73 · Λ-dip 7 · `fp_gate` 4 · 나머지 0.
### 2. 검출 파이프라인 — 4개 단계 추가
| 단계 | 하는 일 | 왜 |
|---|---|---|
| 2 `scale_recovery` | 검출 **실패** 채널만 SG5 로 재탐색 | window 를 전역으로 바꾸면 모든 벽이 함께 밀려 부피가 통째로 이동한다. 실패 채널만 건드려 커버리지(nch)만 얻는다. |
| 4 `missing_outside` | span 조차 없는 채널 = 진짜 방광 밖 | **CCC 앞**에서 계산해야 한다. CCC 가 복구한 채널은 방광 밖이 아니다. |
| 5b `channel_confidence` | 약한 채널을 CCC **복구 단계에서만** 숨김 | 판정(NTV·FP) 전에 되돌린다. 끝까지 숨기면 CCC 가 그 채널을 FP 로 기각할 기회를 잃어 오히려 살아남는다. |
| 6 `CONF_DEMOTE` | CCC 후 여전히 약한 채널을 단면에서 제외 | 남는 채널이 3 미만이 되면 아무것도 하지 않는다. |
신뢰도는 **비-TGC** `min(ant_amp, post_amp) / lumen_min` 을 trace 최고값으로 나눈 값이다.
TGC 로 재면 원거리 이득이 약한 채널을 정상 채널에 붙여 놓는다(CH1↔CH3 간격 TGC 0.05 vs
비-TGC 0.21).
### 3. BV
- **`si_floor` / `missing_outside`** — 극 cap clamp 의 방향이 관측 증거로 갈린다.
미검출 채널에 span 이 **없으면** 방광이 그 빔에 없다는 뜻이라 **상한**(종전),
span 이 **있으면** 방광이 거기까지는 온다는 뜻이라 **하한**이다. 외삽을 관측으로
바꾸는 것이다. `SI_FLOOR_WALK` 는 span 이 이어지는 가장 바깥 채널까지 걸어간다.
- **bottom cap 제한 신설** — top 만 있고 bottom 은 제약이 전혀 없었다. sitting 에서
CH3(치골)이 소실될 때 bottom cap 이 과외삽된다.
- **중앙 채널 보간 은행가 반올림** — 레퍼런스는 `int(round((int(prev)+int(next))/2.0))`
이다. 보간 채널은 subsample 정밀도를 가질 근거가 없다(이웃 둘의 정수 위치뿐).
실측 f01 tr11: 이 차이로 CH2 단면이 2971.0 vs 2788.2 mm² 로 갈렸다.
- **`adaptive_large_bladder_relax` 제거** — 레퍼런스에서 빠진 완화다. 남아 있는 동안
`b_si` 를 0.85·`a_ap` 로 바닥 처리해 cap 축 비를 정확히 0.85 로 만들었고, 그래서 바로
다음의 cap 축 폴백(`< 0.85`)이 **경계에서 발동하지 않았다**.
실측 PHANTOM mean: Python 160.03 mL vs 완화가 살아 있는 코틀린 232.49 mL.
폴백 재계산에는 `missing_outside`·`si_floor` 를 **넘기지 않는다** — 레퍼런스
`runners.estimate_bv` 의 폴백 호출이 그 인자를 빼고 부르기 때문이다.
실측 f04 cyc20: 넘기면 249.95 mL, 안 넘기면 278.43 mL(= 레퍼런스 값).
## 검증
| 무엇을 | 어떻게 | 결과 |
|---|---|---|
| 기존 경로 무변경 | `LegacyEquivalenceDumpTest` 가 `align_cm{0,1}.csv` 44 cycle × CCC on/off 를 검출→BV 로 돌려 덤프. HEAD 코드와 작업트리 코드의 덤프를 대조. | **88 행 전부 일치** |
| 토글 배선 | `AlgoModeSwitchTest` — 켜면 값이 달라지고 `missing_outside` 가 산출되며, 끄면 기존 값이 정확히 재현되는지 | 44 cycle 중 **40 개가 갈림**, 끈 뒤 완전 복귀 |
| 정렬 판정 | 현행 레퍼런스 `AnchorGuide(ver='v1')` 를 `data123` 3 세션에 돌려 대조 | nch·ch3·hit **전부 일치** (0cm 3/X/2·11, 1cm 4/O/8·11, 3cm 3/O/11·11) |
| 전체 | `:app:testDemoDebugUnitTest` | **30개 전부 통과** |
`LegacyEquivalenceDumpTest` 는 의도적으로 `AlgoMode` 를 참조하지 않는다 — HEAD 에는 그
타입이 없어서, 참조하면 대조 자체가 불가능해진다.
## 이번에 함께 고친 것
- **테스트 픽스처 경로** — 저장소가 갈리면서 `c:/Projects/medilightv2android/data123` 가
없어졌다. 5개 테스트가 `FileNotFoundException` 으로 조용히 실패하고 있었다.
`c:/Projects/vesiscan-design-archive/data123` 로 옮겼다.
- **`CenterAlignerValidationTest` 기대값** — 위 픽스처가 깨져 있어 `ebddc52` 이후 이
테스트가 한 번도 돌지 않았다. 다시 돌리니 hit 7→8 · 10→11 로 어긋났는데, **현행
레퍼런스를 같은 세션에 돌려 보니 8 · 11 이었다** — 코틀린이 맞고 기대값이 낡은
것이다(구 `v3_python_reference.py` 기준, 현재 저장소에 없음). 기대값을 현행 레퍼런스
산출값으로 갱신했다.
`bv_cv` 는 현행 레퍼런스가 더 이상 내지 않는 값이라 대조할 수단이 없다. "Python 과
bit-exact" 라던 종전 주석은 근거가 없어져 걷어내고, 성격을 **회귀 고정**으로 바꿔
적었다.
## 아직 안 옮긴 것
| 항목 | 왜 |
|---|---|
| `post_tie_lock` | 상태기 — 스트리밍 경로와 함께 설계해야 한다 |
| `_sibeam_cap_bounds` | nch=2 전용 |
| F83 cap 보간 | 실리콘 + supine 조건 |
레퍼런스 저장소의 `library/analysis/abdomen.py` 가 커밋되어 있지 않아
`pipeline_dump.py` · `compare.py` 가 그대로는 돌지 않는다. 이번 대조는 스크래치 사본에
호출 시 즉시 실패하는 스텁을 놓아 우회했다(정렬·검출·BV 경로는 이 모듈을 쓰지 않는다).