ccb44f0ea6
신 저장소에 맞춘 레퍼런스(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>
74 lines
3.2 KiB
Kotlin
74 lines
3.2 KiB
Kotlin
package com.medithings.vesiscan.managers
|
|
|
|
import com.medithings.vesiscan.walldetect.MethodDRunner
|
|
import org.junit.Test
|
|
import java.io.File
|
|
|
|
/**
|
|
* 기존(팬텀) 경로가 이번 이식으로 **한 글자도 안 바뀌었는지** 실증하는 덤프 하네스.
|
|
*
|
|
* 검출 → BV 전 구간을 `align_cm{0,1}.csv` 의 모든 cycle 에 대해 돌려 결과를 파일로
|
|
* 떨군다. HEAD 코드와 작업트리 코드에서 각각 돌린 덤프가 **바이트 단위로 같으면**
|
|
* 기존 경로 무변경이 증명된다.
|
|
*
|
|
* 이 파일은 [AlgoMode] 를 참조하지 않는다 — HEAD 에는 그 타입이 없기 때문이다.
|
|
* 기본값이 기존 경로이므로 기본 호출만으로 그 경로가 그대로 실행된다.
|
|
*/
|
|
class LegacyEquivalenceDumpTest {
|
|
|
|
private fun res(name: String) =
|
|
javaClass.classLoader!!.getResourceAsStream(name)!!.bufferedReader().use { it.readText() }
|
|
|
|
/** cycle → 6채널 신호 (`cycle,channel,s0..s99`). */
|
|
private fun loadCycles(name: String): Map<Int, List<DoubleArray>> {
|
|
val out = HashMap<Int, MutableList<Pair<Int, DoubleArray>>>()
|
|
for (line in res(name).lineSequence()) {
|
|
val t = line.trim()
|
|
if (t.isEmpty()) continue
|
|
val f = t.split(",")
|
|
if (f.size < 3) continue
|
|
val cyc = f[0].toIntOrNull() ?: continue
|
|
val ch = f[1].toIntOrNull() ?: continue
|
|
val sig = DoubleArray(f.size - 2) { f[it + 2].trim().toDouble() }
|
|
out.getOrPut(cyc) { ArrayList() }.add(ch to sig)
|
|
}
|
|
return out.mapValues { (_, v) ->
|
|
val byCh = v.toMap()
|
|
(0..5).map { byCh[it] ?: DoubleArray(0) }
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun dump() {
|
|
val sb = StringBuilder()
|
|
for (cm in 0..1) {
|
|
val cycles = loadCycles("align_cm$cm.csv")
|
|
for (cyc in cycles.keys.sorted()) {
|
|
val sig = cycles[cyc]!!
|
|
for (cross in listOf(false, true)) {
|
|
val dets = MethodDRunner.detectMultichannel(sig, applyCross = cross)
|
|
val walls = dets.map { r ->
|
|
r?.let { WallWithSpan(it.antRefined, it.postRefined, it.lowStart, it.lowEnd) }
|
|
}
|
|
val bv = estimateBv(walls)
|
|
sb.append("cm$cm cyc$cyc cross=$cross")
|
|
dets.forEachIndexed { i, r ->
|
|
sb.append(if (r == null) " CH$i=-" else
|
|
" CH$i=%d/%d/%.4f/%.4f/%d/%d".format(
|
|
r.ant, r.post, r.antRefined, r.postRefined, r.lowStart, r.lowEnd))
|
|
}
|
|
sb.append(if (bv == null) " BV=-" else
|
|
" BV=%.6f lr=%.6f nch=%d topH=%.4f botH=%.4f vT=%.4f vB=%.4f".format(
|
|
bv.volumeMl, bv.lrRatio, bv.validChannels.size,
|
|
bv.topHMm, bv.bottomHMm, bv.vTopMm3, bv.vBottomMm3))
|
|
sb.append('\n')
|
|
}
|
|
}
|
|
}
|
|
val out = File(System.getProperty("legacyDump") ?: "build/legacy_dump.txt")
|
|
out.parentFile?.mkdirs()
|
|
out.writeText(sb.toString())
|
|
println("[dump] ${out.absolutePath} lines=${sb.lines().size}")
|
|
}
|
|
}
|