app+docs: 追查'官方更细腻'——定位到时域累积器(CAccumulator);实测证明差异在参考帧(FFC baseline)而非显示侧平滑;累积器已实现但默认关闭
This commit is contained in:
@@ -63,12 +63,24 @@ object OfficialTemp {
|
||||
* the official app's own wire parse (`UsbCommunication.parseCmd`,
|
||||
* D2P_SendParameter2), minus the leading magic word our reader consumes.
|
||||
*
|
||||
* [baselineAcc] is the accumulator mode the SDK's `CAccumulator` runs with
|
||||
* (`Reset(pixels, mode, 0)`, mode a power of two ≤ 0x40): the official
|
||||
* pipeline pushes every raw frame through it before the NUC — a `>>log2(mode)`
|
||||
* IIR that measurably removes ~2.3x of the noise/FPN the official phone app
|
||||
* does not show (see docs/android_app/rendering_diff_phone_20261006.md §9).
|
||||
*
|
||||
* Note on [fpaTempFix]: the SDK's compensation term `v50` was measured on a
|
||||
* reference capture as `dev+0x5C - dev+0x54` (0…89 mC, i.e. filtered vs
|
||||
* instantaneous FPA temperature), **not** as this field (the device reports
|
||||
* 1699 for it). [fpaTempFix] is kept for completeness / future mapping.
|
||||
*/
|
||||
class AtPara(val atZeroErrorPoint: Int, val atErrorSlope: Float, val fpaTempFix: Int)
|
||||
class AtPara(
|
||||
val atZeroErrorPoint: Int,
|
||||
val atErrorSlope: Float,
|
||||
val fpaTempFix: Int,
|
||||
/** CAccumulator mode (power of two; 1 = off). */
|
||||
val baselineAcc: Int = 1,
|
||||
)
|
||||
|
||||
/**
|
||||
* Parse the radiance record from the DDT tail. Layout verified against the
|
||||
@@ -110,7 +122,10 @@ object OfficialTemp {
|
||||
val atZero = MagProtocol.u32(payload, 40)
|
||||
val atSlope = Float.fromBits(MagProtocol.u32(payload, 44))
|
||||
if (atSlope == 0f || atSlope.isNaN() || kotlin.math.abs(atSlope) > 1f) return null
|
||||
return AtPara(atZero, atSlope, fpaTempFix)
|
||||
// baseLineAcc: CAccumulator mode, power of two <= 0x40 (SDK Reset() rejects others)
|
||||
var acc = MagProtocol.u32(payload, 0)
|
||||
if (acc < 1 || acc > 0x40 || (acc and (acc - 1)) != 0) acc = 1
|
||||
return AtPara(atZero, atSlope, fpaTempFix, acc)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- curve math
|
||||
|
||||
@@ -671,6 +671,7 @@ class RenderPipeline(
|
||||
val st = ffcStep(shutterValue)
|
||||
lastPhase = st
|
||||
if (st != 0) {
|
||||
accReady = false // SDK: accumulator Restart() at every FFC
|
||||
if (st == 2) {
|
||||
// collect the reference in a SCRATCH buffer: decoding into `nuc`
|
||||
// would leave raw (uncompensated) counts there, and the OSD
|
||||
@@ -685,6 +686,7 @@ class RenderPipeline(
|
||||
val off = if (hasHdr) 0x1C else 0
|
||||
ByteReader.decodeU16(frame, off, npix, nuc)
|
||||
if (!hasRef) return false
|
||||
accumulateInPlace()
|
||||
|
||||
nucAndBlind(nuc, nuc)
|
||||
statsWindow()
|
||||
@@ -810,11 +812,45 @@ class RenderPipeline(
|
||||
private var radRecord: OfficialTemp.RadRecord? = null
|
||||
private var atPara: OfficialTemp.AtPara? = null
|
||||
|
||||
// ---- official display accumulator (CAccumulator, mode = BasePara2.baseLineAcc) ----
|
||||
// The SDK pushes every raw frame through it *before* the NUC
|
||||
// (`CFunctions::Raw2Temperature`), restarts it at every FFC (`PushBaselineRaw`),
|
||||
// and skips it while the baseline/shutter frames are collected. With mode 4 it
|
||||
// is a `>>2` IIR, which is what makes the official image ~2.3x smoother than an
|
||||
// accumulator-less render (docs/android_app/rendering_diff_phone_20261006.md §9).
|
||||
private var accumShift = 0 // log2(mode); 0 = off
|
||||
private val accBuf = IntArray(npix)
|
||||
private var accReady = false
|
||||
|
||||
/** Supply the official chain inputs once the session has parsed them. */
|
||||
fun setTempParams(rec: OfficialTemp.RadRecord?, at: OfficialTemp.AtPara?) {
|
||||
synchronized(lock) {
|
||||
radRecord = rec
|
||||
atPara = at
|
||||
// Accumulator is implemented but stays OFF by default: measurements show the
|
||||
// official receiver is not display-smoothed (its frame-to-frame noise equals
|
||||
// ours, 4.12 vs 4.02) while its *spatial* noise is 2.3x lower -> the official
|
||||
// difference comes from a cleaner FFC reference, not from temporal filtering
|
||||
// (a strong temporal IIR here even *reveals* static banding; see
|
||||
// docs/android_app/rendering_diff_phone_20261006.md §9).
|
||||
accumShift = 0
|
||||
accReady = false
|
||||
}
|
||||
}
|
||||
|
||||
/** In-place `buf = (buf*(M-1) + new) >> log2(M)`; first frame is copied verbatim. */
|
||||
private fun accumulateInPlace() {
|
||||
if (accumShift <= 0) return
|
||||
if (!accReady) {
|
||||
System.arraycopy(nuc, 0, accBuf, 0, npix)
|
||||
accReady = true
|
||||
return
|
||||
}
|
||||
val m1 = (1 shl accumShift) - 1
|
||||
for (i in 0 until npix) {
|
||||
val v = (accBuf[i] * m1 + nuc[i]) shr accumShift
|
||||
accBuf[i] = v
|
||||
nuc[i] = v
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user