app+csdk+docs: 噪声预算真机实测(0.559σn 与理论一致,链路无额外噪声源)+ 修 MDT raw/NUC 配对与 LUT 32位语义;csdk 合入 android_lut/fpa_temp_fix(C 未编译,算法由 golden 测试锁定);118 单测全绿
This commit is contained in:
+10
-5
@@ -294,13 +294,18 @@ build-artifacts\csdk_test_display.exe
|
||||
用的(PC 版)**结构不同**——固定基值 `16*(448+447*b/200)`、步进上限
|
||||
`max(1,(0x1800+4j)·k>>13)`(`k=min(0x200,16·span/range)`)、反向 CDF 从
|
||||
`hist[pivot]` 起、pivot=`max(center_idx,(meanBelow+meanAbove)/2)`。
|
||||
该变体已逐指令移植进 Android 管线(`RenderPipeline.androidLutOf`,含黄金向量单测);
|
||||
本 C 参考**保持 PC 语义**(`cdf_pivot_75 = 1`,与 `analysis/render_offline.c`
|
||||
黄金夹具一致)。Android 版 LUT 的 C 侧移植待办,规格见
|
||||
`docs/android_app/rendering_diff_phone_20261006.md` §10.3。
|
||||
**C 侧已实现**:`mag160c_render.h` 的配置项 `android_lut`(1 = 用 Android 版
|
||||
`HistDeRedundance`,即 `mag160c_render.c: android_lut_rebuild()`,是 Kotlin 侧
|
||||
`RenderPipeline.androidLutOf` 的逐行转写,两边同一套 golden 向量;0 = 保持 PC 语义,
|
||||
与 `analysis/render_offline.c` 的黄金夹具一致),两个 demo 默认打开(真机验证:
|
||||
同场景照片逐像素平均差 4.5/255、逐计数中位差 2 级,见 §10/§11)。
|
||||
⚠️ **本机没有 C 编译器**(gcc/clang/NDK 都没有),所以这段 C **未编译**——
|
||||
算法本身由 Kotlin golden 测试锁定,合入前请在带编译器的环境编译 + 跑回归。
|
||||
- 顺带(同一条链):**曲线插值温度要加 `BasePara2.fpaTempFix`**。官方 App 传给 SDK 的是
|
||||
`帧尾 FPA + fpaTempFix`(真机 1699),`SelectCaliCurve`/`GenerateVolatileLens`
|
||||
用它选/插值每像素标定表;温度换算链路仍用帧尾原值。C 侧移植时不要漏。
|
||||
用它选/插值每像素标定表;温度换算链路仍用帧尾原值。C 侧**已实现**
|
||||
(配置项 `fpa_temp_fix`,在 `rebuild_tables()` 的调用处加到帧尾 FPA 上;
|
||||
两个 demo 设 1699 = 本机 BasePara2 实测值)。
|
||||
2. **温度换算待移植**:官方对这台设备走的是"放射学分支"(每波段线性映射 + 环境温度误差修正
|
||||
+ 发射率/透过率 revise),不是本 SDK 现在用的 T2E 表分支。完整反编译结论、伪代码与
|
||||
参数来源见 `docs/android_app/temperature_chain_re_20261006.md`(§4d 可直接落地,
|
||||
|
||||
@@ -50,6 +50,16 @@ typedef struct mag160c_render_cfg_t {
|
||||
int startup_force_ffc; /* official frame-75 forced FFC, 1 = on */
|
||||
int warm_frames; /* frames dropped after start, official 20 */
|
||||
int cdf_pivot_75; /* 0x8c: CDF pivot at 75% cumulative, 1 = on */
|
||||
/* Android libcxsdk parity (see docs/android_app/rendering_diff_phone_20261006.md
|
||||
section 10/11): its HistDeRedundance is a structurally different function from
|
||||
the PC one this reference implements. 0 = PC path (default), 1 = Android. */
|
||||
int android_lut;
|
||||
/* BasePara2.fpaTempFix: the official app interpolates the per-pixel calibration
|
||||
tables with (frame-tail FPA + this). The vendor's own temperature chain keeps
|
||||
the raw tail value. Device 160043865 reports 1699. */
|
||||
int fpa_temp_fix;
|
||||
/* Brightness offset for the Android LUT base; the official app passes 0. */
|
||||
int lut_brightness;
|
||||
mag160c_render_ffc_cb_t ffc_cb;
|
||||
void *ffc_user;
|
||||
mag160c_render_post_nuc_cb_t post_nuc_cb;
|
||||
|
||||
+119
-1
@@ -59,6 +59,7 @@ struct mag160c_render_t {
|
||||
mag160c_render_post_gray_cb_t post_gray_cb;
|
||||
void *post_gray_user;
|
||||
int ffc_period, ffc_drift, warm_frames, cdf_pivot_75, force75;
|
||||
int android_lut, fpa_temp_fix, lut_brightness;
|
||||
};
|
||||
|
||||
static int counts_to_temp_mc(int counts) {
|
||||
@@ -263,7 +264,121 @@ static void stats_window(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
|
||||
/* ---------------- LUT1024 rebuild (0x180011950 + 0x180011ee0) ---------------- */
|
||||
|
||||
|
||||
/* ------------- Android libcxsdk HistDeRedundance (parity port) -------------
|
||||
*
|
||||
* Line-by-line transcript of the disassembly of the official app's
|
||||
* libcxsdk.so (`_ZN10CFunctions16HistDeRedundanceEv`, see
|
||||
* docs/android_app/rendering_diff_phone_20261006.md section 10.3 and the golden
|
||||
* vector in android/app/src/test/.../AndroidLutTest.kt). It differs from the PC
|
||||
* variant above in structure, not just constants:
|
||||
* - fixed LUT base 16*(448 + 447*brightness/200) instead of an auto-centre search
|
||||
* - step cap max(1, (0x1800 + 4j)*k >> 13), k = min(0x200, 16*span/range)
|
||||
* - backward CDF walk starts at hist[pivot]; hist[1023] = hist[1022]
|
||||
* - pivot = max(center index, (meanBelow + meanAbove) >> 1) when the vendor's
|
||||
* flag (theFunc._96_4_ = 1, set by the Controller constructor) is on
|
||||
* - lut[1023] is never written (the vendor loop stops at 1022)
|
||||
* NOTE: written but NOT compiled here - this machine has no C toolchain; the
|
||||
* algorithm itself is locked by the Kotlin golden test.
|
||||
*/
|
||||
static void android_lut_rebuild(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
int span = r->win_hi - r->win_lo;
|
||||
if (span < 1) span = 1;
|
||||
unsigned S = 0xFFC00000u / (unsigned)span;
|
||||
memset(r->hist1024, 0, sizeof(r->hist1024));
|
||||
for (int i = 0; i < r->npix; ++i) {
|
||||
unsigned idx = (((unsigned)nuc[i] - (unsigned)r->win_lo) * S >> 20) & 0xFFCu;
|
||||
r->hist1024[idx >> 2]++;
|
||||
}
|
||||
unsigned orig[1024];
|
||||
memcpy(orig, r->hist1024, sizeof(orig));
|
||||
for (int j = 0; j < 1023; ++j) r->hist1024[j] = (orig[j + 1] + orig[j]) >> 1;
|
||||
r->hist1024[1023] = r->hist1024[1022];
|
||||
|
||||
unsigned v82 = (unsigned)r->npix >> 12;
|
||||
if (v82 < 1) v82 = 1;
|
||||
int pivot = (int)((1023u * (unsigned)(r->stat_mean - r->win_lo)) / (unsigned)span);
|
||||
if (pivot > 1023) pivot = 1023;
|
||||
{
|
||||
unsigned long long below_n = 0, below_s = 0, above_n = 0, above_s = 0;
|
||||
for (int k = 0; k < pivot; ++k) {
|
||||
below_n += r->hist1024[k];
|
||||
below_s += (unsigned long long)r->hist1024[k] * (unsigned)k;
|
||||
}
|
||||
for (int k = pivot + 1; k < 1024; ++k) {
|
||||
above_n += r->hist1024[k];
|
||||
above_s += (unsigned long long)r->hist1024[k] * (unsigned)k;
|
||||
}
|
||||
if (below_n && above_n) {
|
||||
unsigned m = (unsigned)((above_s / above_n) + (below_s / below_n)) >> 1;
|
||||
if ((int)m > pivot) pivot = (int)m;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned cdf_f[1024], cdf_b[1024];
|
||||
memset(cdf_f, 0, sizeof(cdf_f));
|
||||
memset(cdf_b, 0, sizeof(cdf_b));
|
||||
unsigned unit = 0x10000u / v82;
|
||||
{
|
||||
unsigned acc = 0, steps = 0;
|
||||
for (int j = pivot; j >= 0; --j) {
|
||||
acc += r->hist1024[j];
|
||||
if (acc >= v82) { acc = 0; steps++; }
|
||||
cdf_f[j] = (steps << 8) + ((unit * acc) >> 8);
|
||||
}
|
||||
}
|
||||
{
|
||||
unsigned acc = 0, steps = 0;
|
||||
for (int j = pivot; j < 1023; ++j) {
|
||||
acc += r->hist1024[j];
|
||||
if (acc >= 8 * v82) { steps += 2; acc = 0; }
|
||||
else if (acc >= v82) { steps += 1; acc = 0; }
|
||||
cdf_b[j] = ((unit * acc) >> 8) + (steps << 8);
|
||||
}
|
||||
}
|
||||
|
||||
int v66 = 447 * (r->lut_brightness < -100 ? -100 : (r->lut_brightness > 100 ? 100 : r->lut_brightness)) / 200;
|
||||
int range = (r->dev24 * 1000) >> r->dev4c_shift;
|
||||
if (range < 0x80) range = 0x80;
|
||||
unsigned k = (unsigned)(16 * span) / (unsigned)range;
|
||||
if (k >= 0x200) k = 512;
|
||||
if (range > 16 * span) k = 1;
|
||||
unsigned step[1024];
|
||||
unsigned acc6 = 0x1800u * k;
|
||||
for (int j = 0; j < 1024; ++j) {
|
||||
unsigned v = acc6 >> 13;
|
||||
step[j] = v ? v : 1;
|
||||
acc6 += 4 * k;
|
||||
}
|
||||
|
||||
unsigned base = (unsigned)(16 * (v66 + 448));
|
||||
if (cdf_f[0]) {
|
||||
unsigned denom = (unsigned)((v66 + 448) << 18) / cdf_f[0];
|
||||
unsigned prev = base;
|
||||
for (int j = pivot; j >= 0; --j) {
|
||||
unsigned cand = base - ((cdf_f[j] * denom) >> 14);
|
||||
unsigned v = prev - step[j];
|
||||
if (prev - cand <= step[j]) v = cand;
|
||||
r->lut[j] = (unsigned char)(v >> 6);
|
||||
prev = v;
|
||||
}
|
||||
}
|
||||
if (cdf_b[1022]) {
|
||||
unsigned denom = (unsigned)((576 - v66) << 18) / cdf_b[1022];
|
||||
unsigned prev = (unsigned)(16 * v66 + 7152);
|
||||
for (int j = pivot + 1; j < 1023; ++j) {
|
||||
unsigned cand = ((cdf_b[j] * denom) >> 14) + (unsigned)(16 * v66 + 7152);
|
||||
unsigned v = prev + step[j + 1];
|
||||
if (cand - prev <= step[j + 1]) v = cand;
|
||||
r->lut[j] = (unsigned char)(v >> 6);
|
||||
prev = v;
|
||||
}
|
||||
}
|
||||
/* lut[1023] intentionally left untouched (vendor loop stops at 1022) */
|
||||
}
|
||||
|
||||
static void lut_rebuild(mag160c_render_t *r, const unsigned short *nuc) {
|
||||
if (r->android_lut) { android_lut_rebuild(r, nuc); return; }
|
||||
int span = r->win_hi - r->win_lo;
|
||||
unsigned S = 0xFFC00000u / (unsigned)span;
|
||||
memset(r->hist1024, 0, sizeof(r->hist1024));
|
||||
@@ -487,7 +602,7 @@ static int ffc_step(mag160c_render_t *r, int shutter) {
|
||||
r->ffc_idx++;
|
||||
int idx = r->ffc_idx;
|
||||
if (idx <= OFF_N1) {
|
||||
if (idx == 1) rebuild_tables(r, r->shutter_at_ffc);
|
||||
if (idx == 1) rebuild_tables(r, r->shutter_at_ffc + r->fpa_temp_fix);
|
||||
return 1;
|
||||
}
|
||||
if (idx <= OFF_N0 + OFF_N1) {
|
||||
@@ -549,6 +664,9 @@ mag160c_error_t mag160c_render_init(mag160c_render_t **out,
|
||||
r->ffc_drift = cfg->ffc_drift ? cfg->ffc_drift : 250;
|
||||
r->warm_frames = cfg->warm_frames ? cfg->warm_frames : 20;
|
||||
r->cdf_pivot_75 = cfg->cdf_pivot_75;
|
||||
r->android_lut = cfg->android_lut;
|
||||
r->fpa_temp_fix = cfg->fpa_temp_fix;
|
||||
r->lut_brightness = cfg->lut_brightness;
|
||||
r->force75 = cfg->startup_force_ffc;
|
||||
r->dev24 = 5;
|
||||
r->dev4c_shift = 3;
|
||||
|
||||
@@ -611,7 +611,13 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
|
||||
structurally different function (fixed base, step cap, mean-of-partials pivot)
|
||||
that is ported in the Android pipeline, not here. See
|
||||
docs/android_app/rendering_diff_phone_20261006.md section 10. */
|
||||
cfg.cdf_pivot_75 = 1;
|
||||
/* Android libcxsdk parity (validated on device 2026-10-06, see
|
||||
docs/android_app/rendering_diff_phone_20261006.md sections 10-11):
|
||||
the phone app renders with its own HistDeRedundance variant, and it feeds the
|
||||
per-pixel table interpolation (frame-tail FPA + BasePara2.fpaTempFix). */
|
||||
cfg.cdf_pivot_75 = 1; /* kept for the PC path */
|
||||
cfg.android_lut = 1;
|
||||
cfg.fpa_temp_fix = 1699; /* BasePara2.fpaTempFix of device 160043865 */
|
||||
cfg.ffc_cb = ffc_cb;
|
||||
cfg.post_nuc_cb = post_nuc_cb;
|
||||
cfg.post_gray_cb = post_gray_cb;
|
||||
|
||||
@@ -127,7 +127,13 @@ int main(int argc, char **argv) {
|
||||
cfg.warm_frames = 20;
|
||||
/* PC/Linux reference LUT keeps its own pivot (see the same note in
|
||||
mag160c_demo3.c); the Android-parity LUT lives in the Android pipeline. */
|
||||
cfg.cdf_pivot_75 = 1;
|
||||
/* Android libcxsdk parity (validated on device 2026-10-06, see
|
||||
docs/android_app/rendering_diff_phone_20261006.md sections 10-11):
|
||||
the phone app renders with its own HistDeRedundance variant, and it feeds the
|
||||
per-pixel table interpolation (frame-tail FPA + BasePara2.fpaTempFix). */
|
||||
cfg.cdf_pivot_75 = 1; /* kept for the PC path */
|
||||
cfg.android_lut = 1;
|
||||
cfg.fpa_temp_fix = 1699; /* BasePara2.fpaTempFix of device 160043865 */
|
||||
cfg.ffc_cb = ffc_cb;
|
||||
mag160c_render_t *render = NULL;
|
||||
if (mag160c_render_init(&render, &cfg) != MAG160C_OK) {
|
||||
|
||||
Reference in New Issue
Block a user