feat: Method C (V4.1) 대규모 업데이트 — 2-pass pipeline + ChordConsensus

새 알고리즘 모듈 (7개):
- ImpulseReject: 2-pass Hampel filter (lumen 내 impulse 제거)
- MedianFilter: running-median pre-filter (window=7, speckle 제거)
- MorphClose: morphological closing on lumen mask (disabled)
- StaLta: STA/LTA impulse detector (Allen 1978, wall edge vs reverberation)
- ChordConsensus: Tukey MAD outlier + Fischler-Bolles pair test
- DetectionSanity: detection sanity checks
- SweepStabilizer: sweep temporal stability

변경된 모듈 (9개):
- V41Detector: 2-pass pipeline (ImpulseReject.detectWithLumenClean),
  BModeScore V41_WEIGHTS (wamp+stalta), ChordConsensus filtering
- BvEstimation: ChordConsensus filter → trusted channel only
- DetectLumenFirst: running-median pre-filter, V41_PARAMS,
  mergeGapMax=5, Kremkau half-amplitude ant fallback
- AnatomicalGate: antDepthMin 22→10mm, strict threshold 제거
- BModeScore: V41_WEIGHTS (far=0.15, dark=0.10, wamp=0.45, stalta=0.20)
- WallSelect: MAX_PEAK_CANDIDATES_POST=64
- SpanUtils, Otsu, BvDispatchResult 업데이트

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-04 17:01:55 +09:00
parent d6e981e1d2
commit 82634c2107
16 changed files with 1471 additions and 73 deletions
@@ -13,11 +13,26 @@ package com.example.medilightv2android.walldetect.dto
data class BvDispatchResult(
val bvMl: Float?, // null = no estimate
val rMm: Float?, // equivalent sphere radius (sphere or chord-derived)
val method: String, // FrustumLR | FrustumNoLR | SphereLM | ConeFallback | Verathon | None
val method: String, // FrustumLR | FrustumNoLR | ChordMedian | Verathon | None
val confidence: Float, // 0..1
val nCenter: Int, // gated center channels (CH0..CH3)
val nLateral: Int, // gated lateral channels (CH4, CH5)
val nCenter: Int, // gated center channels (CH0..CH3) AFTER consensus filter
val nLateral: Int, // gated lateral channels (CH4, CH5) AFTER consensus filter
val lrRatio: Float, // applied LR/AP ratio (1.0 if no lateral)
val warnings: List<String> = emptyList(),
val sphereCrossCheckBvMl: Float? = null,
// ── ChordConsensus integration (v4.1.1) ──────────────────────────────
/** Trusted channel indices after score ≥ 0.40 + Tukey/Fischler-Bolles
* consensus filter. Empty when no detections passed. */
val trustedChannels: List<Int> = emptyList(),
/** Channels rejected by the consensus filter, with reason string. */
val rejectedChannels: List<RejectedChannel> = emptyList(),
/** Median chord across the trusted set (mm). */
val consensusMedianChordMm: Float? = null,
/** MAD of trusted chords (mm). null when N < 3. */
val consensusMadMm: Float? = null,
/** Highest-score trusted channel (RANSAC leader). */
val leaderCh: Int? = null,
)
data class RejectedChannel(val ch: Int, val chordMm: Float, val reason: String)