fix(algo): 검출·BV 를 piezo-phantom-test 레퍼런스와 일치시킴
실측 세션(HUMAN-kai VBT26050202, v1) 2위치로 stage 대조한 결과 다섯 곳이 레퍼런스와 어긋나 있었다. 전부 고쳐 **nch · ch3 · ch3_rate · cap_frac 이 전 항목 일치**한다. | # | 어긋난 곳 | 값 | 증상 | |---|---|---|---| | 1 | `d_max` | 10 → **15** | 후벽 탐색창 부족 | | 2 | `inward_walk_win` | 3 → **4** | span(low_start/low_end) 이탈 | | 3 | prominence 기준점 | 인접 골 → **span 바닥** | 전벽 오선택 (py 4 vs kt 13) | | 4 | cap 축 붕괴 폴백 | **누락** → 구현 | BV −14.9 mL | | 5 | dps · delay | 전역 상수 → **프리셋 유도** | 좌표 전체 이탈 | 1·2·5 는 레퍼런스가 갱신된 것을 포팅이 따라가지 못한 경우다. 3 은 SPEC 이 "특히 틀리기 쉬운 곳"으로 짚어 둔 항목(`ANT_FLOOR_REF`)이고, 4 는 `runners.estimate_bv` 의 `CAP_AXIS_RATIO_MAX` 블록이 통째로 빠져 있었다. 곁들여 바로잡은 것: - `ELLIPSE_CAP_R_MAX` 0.92 적용 (종전 사실상 1.0) - `TOP_CAP_EDGE_K` 0.6 (최상단 채널이 최광폭일 때 top cap 제한) — 누락돼 있었다 - top cap clamp 게이트를 `n < nTotalCh` → **center 검출 채널 ≥ 2** 로 (레퍼런스 변경 반영) - `estimateBv` 의 조기 return 들이 cap 축 폴백을 건너뛰던 것 수정. 특히 `validChannels.size < 4` 때문에 채널 하나만 빠져도 폴백이 사라졌다. ## ⚠ dps 기본값이 바뀐다 종전 전역 기본 1.968 은 "300ml 팬텀이 1.936 에서 130~140ml 로 나온다"는 관찰로 되돌렸던 값인데, 프리셋이 정한 값을 전역 상수가 덮는 구조라 레퍼런스 대조가 불가능했다. 이제 v0/v1/v2 = 1.936 · 6.85, r1/r2/r3 = 1.897 · 7.651 로 프리셋에서 유도한다. 팬텀 시연에서 스케일이 달라 보이면 dev 패널에서 dps 를 직접 지정하면 된다(`PiezoHW.distancePerSample`, 해제는 `clearDpsOverride()`). ## 검증 - 전처리(light/heavy) max|Δ| = 0 - CCC-off 검출 12/12 채널 일치 (ant·post·refined·span) - CCC-on 검출 12/12 채널 일치 - 정렬 지표(nch·ch3·ch3_rate·cap_frac) 2위치 전 항목 일치 — AnchorMeasureParityTest 의 @Ignore 를 떼어 상시 게이트로 전환 - BV 제품 경로 고정 walls 155.4257 mL 일치 — BvProductPathParityTest 신규 (cap 축 폴백이 빠지면 140.5 로 떨어져 여기서 잡힌다) - 선택 규칙 200/200 은 그대로 유지 정렬 화면의 "검증 전" 경고를 걷어내고 매니페스트 `selection_verified_against_reference` 를 true 로 바꿨다. 원시 데이터 저장은 유지한다 — 재판정 여지를 남기는 것은 검증과 별개다. 남은 것: 이 다섯 가지는 신 저장소(vesiscan_pre_product)의 같은 코드에도 그대로 있다. 임상 끝나고 이관해야 한다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,9 @@ data class MethodDParams(
|
||||
val otsuRatio: Double = 0.88,
|
||||
val lowMinLen: Int = 3,
|
||||
val mergeGapMax: Int = 3,
|
||||
val dMax: Int = 10,
|
||||
// 2026-09-03: 10 → 15. 레퍼런스(config_wd.d_max)가 바뀜 것을 따라가지 못했다.
|
||||
// 원문 주석: "low echo-ref 후벽이 깊은 진짜 후벽(span 끕+~11샘플)에 닿도록 확장".
|
||||
val dMax: Int = 15,
|
||||
val antDMax: Int = 18,
|
||||
val postMaxIdx: Int = 100,
|
||||
val minUrineLen: Int = 10,
|
||||
@@ -27,7 +29,9 @@ data class MethodDParams(
|
||||
val gapPeakMinProm: Double = 50.0,
|
||||
val minWallLumenRatio: Double = 1.16,
|
||||
val minPostRawRatio: Double = 1.08,
|
||||
val inwardWalkWin: Int = 3,
|
||||
// 2026-09-03: 3 → 4. 레퍼런스(config_wd.inward_walk_win)와 달라 실측 span 이 어긋나갔다.
|
||||
// Python 함수 기본값은 3 이지만 호출부는 DEFAULT_PARAMS(=4)를 넘긴다 — 그쪽이 살아있는 값이다.
|
||||
val inwardWalkWin: Int = 4,
|
||||
val inwardWalkSlopeTol: Double = 10.0,
|
||||
// 02bed02 (4494098 VBTWD201): OS-CFAR window 5→9
|
||||
// heavy median 강화로 span_e 안정화 → post 벽 검출 일관성 (CH0 post SD 1.45→0.36).
|
||||
|
||||
+17
-1
@@ -128,11 +128,27 @@ object MethodDWallSelect {
|
||||
}
|
||||
}
|
||||
|
||||
// prominence 기준점 — **인접 골이 아니라 low echo 바닥**(span 구간 최소값)이다.
|
||||
//
|
||||
// 인접 골 기준은 span 경계 근처의 얕은 골 때문에 경계 쪽 약한 peak 을 과대평가한다.
|
||||
// 후벽은 span far-edge 가 후벽 rising edge 에 얹히면 인접-골 prominence 가 붕괴해
|
||||
// 얕은 peak 을 오선택하고(BV 과소), 전벽도 같은 이유로 경계 쪽으로 끌린다.
|
||||
//
|
||||
// 2026-09-03: 레퍼런스는 `ANT_FLOOR_REF=True` 로 **전벽·후벽 모두** 바닥 기준인데
|
||||
// 여기는 양쪽 다 인접 골을 쓰고 있었다. 실측(HUMAN-kai v1)에서 이 한 곳 때문에
|
||||
// 전벽이 Python 4 → Kotlin 13, 7 → 13 처럼 크게 어긋났다.
|
||||
val floorIdx: Int? =
|
||||
if (spanE >= spanS) {
|
||||
var mi = spanS
|
||||
for (i in spanS..spanE) if (sig[i] < sig[mi]) mi = i
|
||||
mi
|
||||
} else null
|
||||
|
||||
val scored = mutableListOf<Candidate>()
|
||||
for ((p, ctype) in candIdxType) {
|
||||
// view 확장은 경계 peak 확정 전용 — 수용은 [lo, hi] 로 제한.
|
||||
if (p !in lo..hi) continue
|
||||
val vIdx = adjacentValley(sig, p, edge)
|
||||
val vIdx = floorIdx ?: adjacentValley(sig, p, edge)
|
||||
val prom = sig[p] - sig[vIdx]
|
||||
val gate = if (ctype == CType.PEAK) params.minPeakProm else params.minShoulderProm
|
||||
if (prom <= 0 || prom < gate) continue
|
||||
|
||||
Reference in New Issue
Block a user