- 根因:demo3 之前渲染 160x120 原始灰度并最近邻放大,官方输出是 2x 升采样后的 320x240(2-tap 平滑本身抑噪) - 已排除:0x230(FUN_1800175a0 帧差调整)、0x41fe8 隔行平滑(=0)、 temporal filter 0x41fe0(=1) 均未启用,f20 mode=1 直通 - 修复:render_bmp 改用 g_gray320 上色,BMP/窗口改 320x240, StretchBlt 加 HALFTONE,BMP 头支持 >255 尺寸 - 验证:同场景与官方 pair_000.rgb 直方图相关性 0.9796,颜色数 227=官方
1046 lines
41 KiB
C
1046 lines
41 KiB
C
/* MAG160C Windows Demo v5 - FULL official pipeline replication.
|
|
*
|
|
* Pixel-verified against CoreSDKLib.dll (Ghidra decompile + live same-frame
|
|
* captures, 2026-08-13):
|
|
*
|
|
* 1. DDT file load (%TEMP%\Core<serial> / mag160c_official.ddt)
|
|
* v3 header {magic 0x5aa50003, w, h, epcount, nsegs, 0x47944,
|
|
* 0x47948, 0x4155c, 0x41560} + endpoint temps T[epcount] +
|
|
* per-endpoint tables {thr (nsegs-1)*npix+0x4155c/2 i16,
|
|
* gain/off nsegs*npix*2 u16} + blind records (0x28 bytes each).
|
|
* 2. Endpoint select: sel = #T[i] < shutter (shutter = tail word at
|
|
* frame offset len+0x24, i.e. g_frame[0x9608]); cap at epcount-2.
|
|
* Verified live: shutter 29289 -> sel=2 (T=28495..33637).
|
|
* 3. Q12 interpolation: t = ((shutter-T[sel])<<12)/(T[sel+1]-T[sel]),
|
|
* clamp [-0x3fff,0x3fff]; work = a + ((b-a)*t>>12).
|
|
* Verified 0/38400 thr, 0/115200 gain.
|
|
* 4. Ref = 4-frame mean collected on startup/post-FFC frames 6..9
|
|
* (reset at frame 6), FFC(1) at frame 9. Frozen between FFCs.
|
|
* 5. NUC: d2 = (f20-ref)>>1; seg by pixel thresholds; out = off +
|
|
* (gain*d2>>12); clamp. + blind comp (records: avg of N neighbours).
|
|
* Verified 0/19200 vs official raw.
|
|
* 6. Window: [min(fmin, mean-half), max(fmax, mean+half)],
|
|
* half = max(128, 0x24*1000>>0x4794c)/2 (live: 5*1000>>3/2 = 312).
|
|
* Stats: min/max/mean/std -> dev+0x42010..24.
|
|
* 7. LUT1024 rebuild: 1024-bin hist -> 3-tap smooth -> CDF both
|
|
* directions from mean-bin -> contrast curve -> center binary
|
|
* search -> LUT fill (+ temporal center smoothing).
|
|
* gray = LUT[(nuc-lo)*0xffc00000/(hi-lo)>>22].
|
|
* 8. 2x bilinear upscale (exact weights, edge extrapolation).
|
|
* Verified 0/76800 vs official 320x240 gray.
|
|
* 9. Palette: captured official 256x4 BGR.
|
|
* 10. FFC: period 1800 frames or |shutter drift| > 250; after FFC(0)
|
|
* the 13-frame startup cycle repeats (rebuild/ref/rebuild).
|
|
*
|
|
* Ghost behavior is the official one: the ref is frozen between FFCs and
|
|
* re-collected (4 frames) after each FFC, so a moving object's old
|
|
* position reads as a mild dark residual exactly like the official app.
|
|
*/
|
|
#define _WIN32_WINNT 0x0601
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <math.h>
|
|
#include <libusb.h>
|
|
#include "mag160c/mag160c.h"
|
|
#include "mag160c/mag160c_display.h"
|
|
#include "mag160c_official_palette256.h"
|
|
#include "mag160c_official_t2e.h"
|
|
|
|
#define MAG_T2E_OFFSET 0x249f0
|
|
#define TEMP_C 5797
|
|
|
|
static int counts_to_temp_mc(int counts) {
|
|
int64_t x = (int64_t)counts * 3 - TEMP_C;
|
|
if (x < 0) x = 0;
|
|
int lo = 0, hi = 645;
|
|
while (lo < hi) {
|
|
int mid = (lo + hi + 1) >> 1;
|
|
if (mag160c_official_t2e[mid] <= x) lo = mid;
|
|
else hi = mid - 1;
|
|
}
|
|
int i = lo;
|
|
if (i > 644) i = 644;
|
|
int64_t diff = x - mag160c_official_t2e[i];
|
|
int64_t t2 = mag160c_official_t2e[i + 1] - mag160c_official_t2e[i];
|
|
int64_t slope = t2 ? (0x1000000 + t2 / 2) / t2 : 0;
|
|
int64_t temp = ((slope * diff) >> 12) + ((int64_t)i << 12) - MAG_T2E_OFFSET;
|
|
return (int)temp;
|
|
}
|
|
|
|
static double counts_to_c(double v) {
|
|
return counts_to_temp_mc((int)v) / 1000.0;
|
|
}
|
|
|
|
#define W 160
|
|
#define H 120
|
|
#define NPIX (W * H)
|
|
#define VW 320
|
|
#define VH 240
|
|
|
|
/* official timing constants (live: dev+0x2d78..0x2d94 = 4,5,4,1800,250) */
|
|
#define OFF_N0 4 /* ref frames after FFC(1) window start */
|
|
#define OFF_N1 5 /* table rebuild frames */
|
|
#define OFF_N2 4 /* second table rebuild frames */
|
|
#define OFF_FFC_PERIOD 1800
|
|
#define OFF_FFC_DRIFT 250
|
|
#define OFF_WARM_FRAMES 20 /* mode 1/2: drop first 20 frames */
|
|
|
|
#define OFF_DDT "mag160c_official.ddt"
|
|
|
|
static libusb_context *g_ctx;
|
|
static libusb_device_handle *g_h;
|
|
static unsigned char g_frame[40000];
|
|
static unsigned char g_bmp[54 + VW * VH * 3];
|
|
static char g_title[256];
|
|
static unsigned short g_live[NPIX];
|
|
|
|
/* ---- official DDT state ---- */
|
|
static int g_ep_count;
|
|
static int g_nsegs;
|
|
static int g_c5c; /* 0x4155c */
|
|
static int g_c60; /* 0x41560 */
|
|
static int g_dev24 = 5; /* window base */
|
|
static int g_dev4c_shift = 3; /* 0x4794c */
|
|
static int g_dev48; /* contrast -100..100 */
|
|
static int g_dev4c; /* window scale -100..100 */
|
|
static int g_shift; /* 0x11c = 12 */
|
|
static int g_ep_temps[64]; /* T[] */
|
|
static int g_blind_cnt[64];
|
|
static short *g_ep_thr[64]; /* per endpoint (nsegs-1)*npix + c5c/2 */
|
|
static unsigned short *g_ep_gain[64]; /* per endpoint nsegs*npix*2 */
|
|
static unsigned int *g_ep_blind[64]; /* records: target,type,neigh[8] */
|
|
static int g_ep_loaded[64];
|
|
static int g_sel; /* selected endpoint */
|
|
static int g_sel_prev;
|
|
static short g_thr_work[NPIX * 2 + 4096]; /* (nsegs-1)*npix + c5c/2 */
|
|
static unsigned short g_gain_work[NPIX * 3 * 2]; /* nsegs*npix*2 */
|
|
static unsigned int *g_blind_work;
|
|
static int g_blind_count;
|
|
static int g_tables_ready;
|
|
|
|
/* ---- official frame state ---- */
|
|
static int g_ffc_idx = -1; /* frame index in the FFC cycle (-1 = pre-init) */
|
|
static int g_global_frames; /* frames since start */
|
|
static int g_shutter; /* current frame shutter (device temp proxy) */
|
|
static int g_shutter_at_ffc; /* shutter at last FFC(0) */
|
|
static int g_has_ref;
|
|
static unsigned short g_ref[NPIX]; /* ref smoother output */
|
|
static unsigned int g_ref_acc[NPIX]; /* u32 acc */
|
|
static int g_ref_cnt;
|
|
static int g_f20[NPIX];
|
|
static int g_fcount;
|
|
static int g_manual_ffc;
|
|
|
|
/* ---- render state ---- */
|
|
static unsigned short g_nuc[NPIX];
|
|
static unsigned int g_hist1024[1024];
|
|
static unsigned int g_cdf[1024];
|
|
static unsigned char g_lut[1024];
|
|
static int g_win_lo, g_win_hi;
|
|
static int g_stat_min, g_stat_max, g_stat_mean, g_stat_std;
|
|
static unsigned char g_gray160[NPIX];
|
|
static unsigned char g_gray320[320 * 240];
|
|
static int g_center_prev = 341;
|
|
static int g_center_ema = 341;
|
|
static int g_mean_prev;
|
|
static double g_fps;
|
|
static double g_max_temp = -100;
|
|
static double g_center_temp = -100;
|
|
static int g_probe_x = -1, g_probe_y = -1;
|
|
static int g_max_x = -1, g_max_y = -1;
|
|
static HWND g_hwnd;
|
|
static HFONT g_font;
|
|
|
|
static int sendcmd(unsigned magic, unsigned param, int len) {
|
|
unsigned char cmd[8] = {0};
|
|
cmd[0] = (unsigned char)(magic);
|
|
cmd[1] = (unsigned char)(magic >> 8);
|
|
cmd[2] = (unsigned char)(magic >> 16);
|
|
cmd[3] = (unsigned char)(magic >> 24);
|
|
if (len >= 8) {
|
|
cmd[4] = (unsigned char)(param);
|
|
cmd[5] = (unsigned char)(param >> 8);
|
|
cmd[6] = (unsigned char)(param >> 16);
|
|
cmd[7] = (unsigned char)(param >> 24);
|
|
}
|
|
int xfer = 0;
|
|
if (libusb_bulk_transfer(g_h, 0x03, cmd, len, &xfer, 2000)) return -1;
|
|
unsigned char resp[0x1000];
|
|
if (libusb_bulk_transfer(g_h, 0x82, resp, sizeof(resp), &xfer, 2000)) return -1;
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------- DDT loading (FUN_18000f130 v3 format) ---------------- */
|
|
|
|
static int load_ddt(const char *path) {
|
|
FILE *f = fopen(path, "rb");
|
|
if (!f) return 0;
|
|
unsigned char hdr[256];
|
|
size_t got = fread(hdr, 1, 0x100, f);
|
|
if (got < 0x98) { fclose(f); return 0; }
|
|
unsigned magic = *(unsigned *)(hdr + 0);
|
|
unsigned w = *(unsigned *)(hdr + 4);
|
|
unsigned hh = *(unsigned *)(hdr + 8);
|
|
if (magic != 0x5aa50003 && magic != 0x5aa50004) { fclose(f); return 0; }
|
|
if (w != W || hh != H) { fclose(f); return 0; }
|
|
g_ep_count = *(int *)(hdr + 12);
|
|
g_nsegs = *(int *)(hdr + 16);
|
|
if (g_ep_count < 2 || g_ep_count > 64 || g_nsegs < 1 || g_nsegs > 8) { fclose(f); return 0; }
|
|
int v4 = (magic == 0x5aa50004);
|
|
g_dev24 = *(int *)(hdr + 20); /* v3: 0x47944 slot (power-of-2) */
|
|
g_dev4c_shift = 3;
|
|
if (!v4) {
|
|
g_c5c = *(int *)(hdr + 28);
|
|
g_c60 = *(int *)(hdr + 32);
|
|
if (g_c5c > 0x10000) g_c5c = 0;
|
|
} else {
|
|
g_c5c = *(int *)(hdr + 0x2c + 0x120);
|
|
g_c60 = *(int *)(hdr + 0x30 + 0x120);
|
|
}
|
|
if (g_c5c & 1) g_c5c &= ~1;
|
|
/* read arrays */
|
|
int arro = v4 ? 0x120 + 36 : 36;
|
|
if (fseek(f, arro, SEEK_SET)) { fclose(f); return 0; }
|
|
if (fread(g_ep_temps, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
|
|
unsigned tmp[64];
|
|
if (fread(tmp, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
|
|
if (fread(tmp, 4, g_ep_count, f) != (size_t)g_ep_count) { fclose(f); return 0; }
|
|
if (fread(g_blind_cnt, 4, g_ep_count - 1, f) != (size_t)(g_ep_count - 1)) { fclose(f); return 0; }
|
|
g_blind_cnt[g_ep_count - 1] = 0;
|
|
for (int i = 0; i < g_ep_count; ++i)
|
|
if (g_blind_cnt[i] > 4096) g_blind_cnt[i] = 4096;
|
|
|
|
size_t thr_sz = (size_t)(g_nsegs - 1) * NPIX + g_c5c / 2;
|
|
size_t gain_sz = (size_t)g_nsegs * NPIX * 2;
|
|
for (int e = 0; e < g_ep_count; ++e) {
|
|
g_ep_thr[e] = malloc(thr_sz * 2);
|
|
g_ep_gain[e] = malloc(gain_sz * 2);
|
|
g_ep_blind[e] = malloc((size_t)g_blind_cnt[e] * 40);
|
|
if (!g_ep_thr[e] || !g_ep_gain[e] ||
|
|
(g_blind_cnt[e] && !g_ep_blind[e])) { fclose(f); return 0; }
|
|
if (fread(g_ep_thr[e], 2, thr_sz, f) != thr_sz) { fclose(f); return 0; }
|
|
if (fread(g_ep_gain[e], 2, gain_sz, f) != gain_sz) { fclose(f); return 0; }
|
|
g_ep_loaded[e] = 1;
|
|
}
|
|
/* blind records live AFTER all endpoint blocks, one block per endpoint
|
|
* (EP0..EPcount-2; EPcount-1 has none). The official file layout puts
|
|
* them at the end of the file, NOT inside each endpoint block. */
|
|
for (int e = 0; e < g_ep_count - 1; ++e) {
|
|
if (g_blind_cnt[e] &&
|
|
fread(g_ep_blind[e], 40, g_blind_cnt[e], f) != (size_t)g_blind_cnt[e]) {
|
|
fclose(f); return 0;
|
|
}
|
|
}
|
|
fclose(f);
|
|
g_shift = 12;
|
|
g_tables_ready = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* endpoint select (0x180016ae0): sel = #T[i+1] < shutter, capped epcount-2 */
|
|
static int select_endpoint(int shutter) {
|
|
int sel = 0;
|
|
if (g_ep_count != 2) {
|
|
while (sel < g_ep_count - 2 && shutter > g_ep_temps[sel + 1]) sel++;
|
|
}
|
|
return sel;
|
|
}
|
|
|
|
/* Q12 interp (0x180016dd0 / 0x180016f10) */
|
|
static void interp_i16(const short *a, const short *b, short *dst, int n, int t) {
|
|
for (int i = 0; i < n; ++i) {
|
|
dst[i] = (short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
|
|
}
|
|
}
|
|
static void interp_u16(const unsigned short *a, const unsigned short *b,
|
|
unsigned short *dst, int n, int t) {
|
|
for (int i = 0; i < n; ++i) {
|
|
dst[i] = (unsigned short)((int)a[i] + (((int)b[i] - (int)a[i]) * t >> 12));
|
|
}
|
|
}
|
|
|
|
static void rebuild_tables(int shutter) {
|
|
g_sel = select_endpoint(shutter);
|
|
int sel = g_sel;
|
|
int t = ((shutter - g_ep_temps[sel]) << 12) / (g_ep_temps[sel + 1] - g_ep_temps[sel]);
|
|
if (t > 0x3fff) t = 0x3fff;
|
|
if (t < -0x3fff) t = -0x3fff;
|
|
interp_i16(g_ep_thr[sel], g_ep_thr[sel + 1], g_thr_work,
|
|
(g_nsegs - 1) * NPIX + g_c5c / 2, t);
|
|
interp_u16(g_ep_gain[sel], g_ep_gain[sel + 1], g_gain_work,
|
|
g_nsegs * NPIX * 2, t);
|
|
g_blind_work = g_ep_blind[sel];
|
|
g_blind_count = g_blind_cnt[sel];
|
|
}
|
|
|
|
/* ---------------- ref smoother (FUN_1800011a0 mode=N) ---------------- */
|
|
/* returns 1 when the target count is reached */
|
|
static int ref_push(const unsigned short *frame) {
|
|
if (g_ref_cnt == 0) {
|
|
for (int i = 0; i < NPIX; ++i) g_ref_acc[i] = frame[i];
|
|
} else {
|
|
for (int i = 0; i < NPIX; ++i) g_ref_acc[i] += frame[i];
|
|
}
|
|
g_ref_cnt++;
|
|
if (g_ref_cnt >= OFF_N0) {
|
|
long long accsum = 0;
|
|
for (int i = 0; i < NPIX; ++i) accsum += g_ref_acc[i];
|
|
long long fsum = 0;
|
|
for (int i = 0; i < NPIX; ++i) fsum += frame[i];
|
|
FILE *f2 = fopen("demo3_ref.txt", "a");
|
|
if (f2) { fprintf(f2, "complete: acc_mean=%.0f last_frame_mean=%.0f cnt=%d acc0=%u f0=%u acc1=%u f1=%u\n",
|
|
(double)accsum / NPIX, (double)fsum / NPIX, g_ref_cnt,
|
|
g_ref_acc[0], frame[0], g_ref_acc[1], frame[1]); fclose(f2); }
|
|
for (int i = 0; i < NPIX; ++i)
|
|
g_ref[i] = (unsigned short)(g_ref_acc[i] >> 2);
|
|
g_ref_cnt = 0;
|
|
g_has_ref = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* ---------------- NUC + blind (0x180017200 + 0x180017330) ---------------- */
|
|
|
|
static void official_nuc_and_blind(const unsigned short *f20, unsigned short *out) {
|
|
const short *thr = g_thr_work;
|
|
const unsigned short *gg = g_gain_work;
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
int d2 = ((int)f20[i] - (int)g_ref[i]) >> 1;
|
|
int seg = 0;
|
|
if (d2 > thr[i * 2]) {
|
|
seg = 1;
|
|
if (d2 > thr[i * 2 + 1]) seg = 2;
|
|
}
|
|
const unsigned short *p = gg + (seg * NPIX + i) * 2;
|
|
int v = (int)p[1] + ((int)p[0] * d2 >> 12);
|
|
if (v < 0) v = 0;
|
|
if (v > 65535) v = 65535;
|
|
out[i] = (unsigned short)v;
|
|
}
|
|
/* blind compensation: fixed records */
|
|
if (g_blind_work && g_blind_count > 0) {
|
|
for (int r = 0; r < g_blind_count; ++r) {
|
|
const unsigned int *rec = g_blind_work + r * 10;
|
|
unsigned target = rec[0];
|
|
unsigned typ = rec[1];
|
|
const unsigned int *ng = rec + 2;
|
|
if (target >= NPIX || typ < 3 || typ > 8) continue;
|
|
long sum = 0;
|
|
for (unsigned j = 0; j < typ; ++j) sum += out[ng[j]];
|
|
if (typ == 8) out[target] = (unsigned short)(sum >> 3);
|
|
else if (typ == 4) out[target] = (unsigned short)(sum >> 2);
|
|
else out[target] = (unsigned short)(sum / typ);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------- stats + window (0x180010780 + 0x1800109c0) ---------------- */
|
|
|
|
static void official_stats_window(const unsigned short *nuc) {
|
|
unsigned long long ssum = 0, s2sum = 0;
|
|
unsigned mn = 0xffff, mx = 0;
|
|
int mi = 0, xi = 0;
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
unsigned v = nuc[i];
|
|
ssum += v;
|
|
s2sum += (unsigned long long)v * v;
|
|
if (v < mn) { mn = v; mi = i; }
|
|
if (v > mx) { mx = v; xi = i; }
|
|
}
|
|
g_stat_min = (int)mn;
|
|
g_stat_max = (int)mx;
|
|
g_stat_mean = (int)(ssum / NPIX);
|
|
double dmean = (double)ssum / NPIX;
|
|
double var = (double)s2sum / NPIX - dmean * dmean;
|
|
g_stat_std = (int)sqrt(var > 0 ? var : 0);
|
|
(void)mi; (void)xi;
|
|
/* window half (FUN_1800108a0): base = max(0x80, dev24*1000>>shift) */
|
|
int base = (g_dev24 * 1000) >> g_dev4c_shift;
|
|
if (base < 0x80) base = 0x80;
|
|
/* dev4c scale adjust (magic: half = base + base*dev4c*k) */
|
|
if (g_dev4c != 0) {
|
|
int c = g_dev4c < 0 ? -0x51eb851f : -0x1b4e81b5;
|
|
long long m = (long long)c * (long long)(base * g_dev4c) >> 32;
|
|
base += (m >> 4) - (m >> 31);
|
|
}
|
|
int half = base >> 1;
|
|
/* temperature-dependent growth (only 1-2 endpoint DDTs; this unit has 6) */
|
|
if (g_tables_ready && g_ep_count <= 2) {
|
|
int te = g_ep_temps[g_ep_count - 1] + g_ep_temps[0] / 2;
|
|
(void)te;
|
|
}
|
|
int lo = g_stat_mean - half;
|
|
if ((unsigned)lo > (unsigned)g_stat_min) lo = g_stat_min;
|
|
if (lo < 0) lo = 0;
|
|
int hi = g_stat_mean + half;
|
|
if ((unsigned)hi < (unsigned)g_stat_max) hi = g_stat_max;
|
|
if (hi > 65535) hi = 65535;
|
|
g_win_lo = lo;
|
|
g_win_hi = hi;
|
|
if (g_win_hi <= g_win_lo) g_win_hi = g_win_lo + 1;
|
|
}
|
|
|
|
/* ---------------- LUT1024 rebuild (FUN_180011950 + FUN_180011ee0) ------- */
|
|
|
|
static void lut_rebuild(const unsigned short *nuc) {
|
|
/* 1. 1024-bin histogram (FUN_180011950 binning, u32 arithmetic) */
|
|
int span = g_win_hi - g_win_lo;
|
|
unsigned S = 0xFFC00000u / (unsigned)span;
|
|
memset(g_hist1024, 0, sizeof(g_hist1024));
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
unsigned idx = ((unsigned)nuc[i] - (unsigned)g_win_lo) * S >> 0x16;
|
|
if (idx < 1024) g_hist1024[idx]++;
|
|
}
|
|
/* 2. 3-tap in-place smoothing (bins 0..1022) */
|
|
for (int i = 0; i < 1023; i += 3) {
|
|
g_hist1024[i] = (g_hist1024[i + 1] + g_hist1024[i]) >> 1;
|
|
g_hist1024[i + 1] = (g_hist1024[i + 2] + g_hist1024[i + 1]) >> 1;
|
|
g_hist1024[i + 2] = (g_hist1024[i + 3] + g_hist1024[i + 2]) >> 1;
|
|
}
|
|
unsigned total = 0;
|
|
for (int i = 0; i < 1024; ++i) total += g_hist1024[i];
|
|
unsigned u21 = total >> 12;
|
|
if (u21 == 0) u21 = 1;
|
|
int lres = ((g_stat_mean - g_win_lo) * 0x3ff) / span;
|
|
if (lres < 0) lres = 0;
|
|
if (lres > 1023) lres = 1023;
|
|
|
|
/* 3. CDF: down from lres, up from lres+1 */
|
|
unsigned u11 = 0, u13 = 0;
|
|
for (int k = lres; k >= 0; --k) {
|
|
u11 += g_hist1024[k];
|
|
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
|
|
g_cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
|
|
}
|
|
u11 = 0; u13 = 0;
|
|
for (int k = lres + 1; k < 1024; ++k) {
|
|
u11 += g_hist1024[k];
|
|
if (u11 < u21 * 8) {
|
|
if (u21 <= u11) { u13 += 0x100; u11 = 0; }
|
|
} else {
|
|
u13 += 0x200;
|
|
u11 = 0;
|
|
}
|
|
g_cdf[k] = ((0x10000 / u21) * u11 >> 8) + u13;
|
|
}
|
|
|
|
/* 4. contrast curve + center binary search + LUT fill */
|
|
static unsigned curve[1024];
|
|
for (int k = 0; k < 1024; ++k) {
|
|
unsigned v = (0x300000u + (unsigned)k * 0x800) >> 0xd;
|
|
curve[k] = v ? v : 1;
|
|
}
|
|
int iv6 = (g_dev48 * 0x155) / 200;
|
|
int iv7 = iv6 + 0x155;
|
|
int iv26 = iv7;
|
|
if (iv6 + 0x156 > 0x331) iv26 = 0x330;
|
|
int iv10 = 0x332;
|
|
int b3 = 1;
|
|
unsigned iv22 = 0xFFC00000u / (unsigned)span;
|
|
while (iv26 + 1 < iv10) {
|
|
iv7 = (iv10 + iv26) / 2;
|
|
if (b3) { b3 = 0; iv7 = iv26; }
|
|
/* fill LUT for center iv7 */
|
|
unsigned u5 = g_cdf[0];
|
|
if (u5 != 0) {
|
|
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
|
|
for (int k = lres; k >= 0; --k) {
|
|
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
|
|
(g_cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
|
|
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
|
|
g_lut[k] = (unsigned char)(u12 >> 6);
|
|
u21v = u12;
|
|
}
|
|
}
|
|
u5 = g_cdf[1023];
|
|
if (u5 != 0) {
|
|
unsigned u21v = (unsigned)(iv7 * 0x10);
|
|
for (int k = lres + 1; k < 1024; ++k) {
|
|
unsigned u12 = (g_cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
|
|
(unsigned)(iv7 * 0x10);
|
|
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
|
|
g_lut[k] = (unsigned char)(u12 >> 6);
|
|
u21v = u12;
|
|
}
|
|
}
|
|
int iv4 = iv7;
|
|
unsigned bfmin = ((unsigned)g_stat_min - (unsigned)g_win_lo) * iv22 >> 0x16;
|
|
unsigned bfmax = ((unsigned)g_stat_max - (unsigned)g_win_lo) * iv22 >> 0x16;
|
|
if (bfmin < 1024 && bfmax < 1024 &&
|
|
g_lut[bfmin] == 0 && g_lut[bfmax] != 0xff) {
|
|
iv4 = iv10;
|
|
iv26 = iv7;
|
|
}
|
|
iv10 = iv4;
|
|
}
|
|
/* 5. temporal center smoothing + refill */
|
|
int dm = g_stat_mean - g_mean_prev;
|
|
if (dm < 0) dm = -dm;
|
|
g_mean_prev = g_stat_mean;
|
|
int iv18 = iv10 + ((dm & 0xffff) << g_dev4c_shift) * 2 + g_stat_mean;
|
|
if (iv18 < 0x9c4) {
|
|
if (iv18 < 100) iv18 = 100;
|
|
iv7 = (iv18 * iv7 + g_center_prev * 500) / (iv18 + 500);
|
|
g_center_prev = iv7;
|
|
unsigned u5 = g_cdf[0];
|
|
if (u5 != 0) {
|
|
unsigned u21v = (unsigned)(iv7 * 0x10 + 0x10);
|
|
for (int k = lres; k >= 0; --k) {
|
|
unsigned u12 = (unsigned)(iv7 * 0x10 + 0x10) -
|
|
(g_cdf[k] * ((iv7 * 0x40000u + 0x40000u) / u5) >> 0xe);
|
|
if (curve[k] < u21v - u12) u12 = u21v - curve[k];
|
|
g_lut[k] = (unsigned char)(u12 >> 6);
|
|
u21v = u12;
|
|
}
|
|
}
|
|
u5 = g_cdf[1023];
|
|
if (u5 != 0) {
|
|
unsigned u21v = (unsigned)(iv7 * 0x10);
|
|
for (int k = lres + 1; k < 1024; ++k) {
|
|
unsigned u12 = (g_cdf[k] * ((0xffc0000u - iv7 * 0x40000u) / u5) >> 0xe) +
|
|
(unsigned)(iv7 * 0x10);
|
|
if (curve[k] < u12 - u21v) u12 = curve[k] + u21v;
|
|
g_lut[k] = (unsigned char)(u12 >> 6);
|
|
u21v = u12;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------- gray + 2x upscale (FUN_180019740) ---------------- */
|
|
|
|
static void gray_map(const unsigned short *nuc) {
|
|
unsigned S = 0xFFC00000u / (unsigned)(g_win_hi - g_win_lo);
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
unsigned v = nuc[i];
|
|
unsigned idx;
|
|
if (v <= (unsigned)g_win_lo) idx = 0;
|
|
else if (v >= (unsigned)g_win_hi) idx = 1023;
|
|
else idx = (v - (unsigned)g_win_lo) * S >> 0x16;
|
|
if (idx > 1023) idx = 1023;
|
|
g_gray160[i] = g_lut[idx];
|
|
}
|
|
}
|
|
|
|
static void upscale2x(void) {
|
|
const unsigned char *s = g_gray160;
|
|
unsigned char *o = g_gray320;
|
|
int W2 = W * 2;
|
|
for (int y = 0; y < H - 1; ++y) {
|
|
const unsigned char *r0 = s + y * W;
|
|
const unsigned char *r1 = r0 + W;
|
|
unsigned char *o0 = o + y * W2 * 2;
|
|
unsigned char *o1 = o0 + W2;
|
|
int x;
|
|
for (x = 0; x < W - 2; x += 2) {
|
|
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
|
|
unsigned b0 = r1[x], b1 = r1[x + 1], b2 = r1[x + 2];
|
|
int c = x * 2;
|
|
o0[c] = (unsigned char)a0;
|
|
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
|
|
o0[c + 2] = (unsigned char)a1;
|
|
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
|
|
o1[c] = (unsigned char)((b0 + a0) >> 1);
|
|
o1[c + 1] = (unsigned char)((b0 + b1 + a0 + a1) >> 2);
|
|
o1[c + 2] = (unsigned char)((b1 + a1) >> 1);
|
|
o1[c + 3] = (unsigned char)((b2 + b1 + a2 + a1) >> 2);
|
|
}
|
|
/* edge pair x = W-2 */
|
|
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
|
|
unsigned b0 = r1[W - 2], b1 = r1[W - 1];
|
|
int c = (W - 2) * 2;
|
|
o0[c] = (unsigned char)a0;
|
|
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
|
|
o1[c] = (unsigned char)((b0 + a0) >> 1);
|
|
o1[c + 1] = (unsigned char)((a1 + b1 + b0 + a0) >> 2);
|
|
/* last column */
|
|
c = (W - 1) * 2;
|
|
o0[c] = (unsigned char)a1;
|
|
o0[c + 1] = (unsigned char)((3 * a1 >> 2) + (a0 >> 2));
|
|
o1[c] = (unsigned char)((b1 + a1) >> 1);
|
|
o1[c + 1] = (unsigned char)((3 * (b1 + a1) + b0 + a0) >> 3);
|
|
}
|
|
/* bottom row */
|
|
{
|
|
int y = H - 1;
|
|
const unsigned char *r0 = s + y * W;
|
|
const unsigned char *r1 = r0 - W;
|
|
unsigned char *o0 = o + y * W2 * 2;
|
|
unsigned char *o1 = o0 + W2;
|
|
int x;
|
|
for (x = 0; x < W - 2; x += 2) {
|
|
unsigned a0 = r0[x], a1 = r0[x + 1], a2 = r0[x + 2];
|
|
unsigned u0 = r1[x], u1 = r1[x + 1], u2 = r1[x + 2];
|
|
int c = x * 2;
|
|
o0[c] = (unsigned char)a0;
|
|
o0[c + 1] = (unsigned char)((a0 + a1) >> 1);
|
|
o0[c + 2] = (unsigned char)a1;
|
|
o0[c + 3] = (unsigned char)((a2 + a1) >> 1);
|
|
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
|
|
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
|
|
o1[c + 2] = (unsigned char)((3 * a1 >> 2) + (u1 >> 2));
|
|
o1[c + 3] = (unsigned char)((3 * (a2 + a1) + u2 + u1) >> 3);
|
|
}
|
|
unsigned a0 = r0[W - 2], a1 = r0[W - 1];
|
|
unsigned u0 = r1[W - 2], u1 = r1[W - 1];
|
|
int c = (W - 2) * 2;
|
|
o0[c] = (unsigned char)a0;
|
|
o0[c + 1] = (unsigned char)((a1 + a0) >> 1);
|
|
o1[c] = (unsigned char)((3 * a0 >> 2) + (u0 >> 2));
|
|
o1[c + 1] = (unsigned char)((3 * (a0 + a1) + u0 + u1) >> 3);
|
|
c = (W - 1) * 2;
|
|
o0[c] = (unsigned char)a1;
|
|
o0[c + 1] = (unsigned char)((3 * a1 + a0) >> 2);
|
|
o1[c] = (unsigned char)((3 * a1 + u1) >> 2);
|
|
o1[c + 1] = (unsigned char)((o0[c + 1] + o1[c] + a1) / 3);
|
|
}
|
|
}
|
|
|
|
/* ---------------- palette + BMP ---------------- */
|
|
|
|
static void render_bmp(void) {
|
|
unsigned char *px = g_bmp + 54;
|
|
/* 官方输出是 320x240:用 2x 升采样后的灰度上色(2-tap 平滑本身
|
|
* 就抑制了颗粒感;官方 app 显示的也是这个 2x 缓冲)。 */
|
|
for (int y = 0; y < VH; ++y) {
|
|
for (int x = 0; x < VW; ++x) {
|
|
int i = y * VW + x;
|
|
unsigned char gv = g_gray320[i];
|
|
unsigned char r = mag160c_official_palette256[gv][2];
|
|
unsigned char g = mag160c_official_palette256[gv][1];
|
|
unsigned char b = mag160c_official_palette256[gv][0];
|
|
int dst = (VH - 1 - y) * VW * 3 + x * 3;
|
|
px[dst + 0] = b; px[dst + 1] = g; px[dst + 2] = r;
|
|
}
|
|
}
|
|
if (g_max_x >= 0) {
|
|
int mx2 = g_max_x * 2, my2 = (VH - 1 - g_max_y * 2);
|
|
for (int k = -3; k <= 3; ++k) {
|
|
if (mx2 + k >= 0 && mx2 + k < VW) {
|
|
int d2 = my2 * VW * 3 + (mx2 + k) * 3;
|
|
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
|
}
|
|
if (my2 + k >= 0 && my2 + k < VH) {
|
|
int d2 = (my2 + k) * VW * 3 + mx2 * 3;
|
|
px[d2] = 255; px[d2 + 1] = 255; px[d2 + 2] = 255;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static LRESULT CALLBACK wndproc(HWND hw, UINT msg, WPARAM wp, LPARAM lp) {
|
|
switch (msg) {
|
|
case WM_PAINT: {
|
|
PAINTSTRUCT ps;
|
|
HDC dc = BeginPaint(hw, &ps);
|
|
HDC mem = CreateCompatibleDC(dc);
|
|
HBITMAP bm = CreateCompatibleBitmap(dc, VW, VH);
|
|
HGDIOBJ old = SelectObject(mem, bm);
|
|
SetDIBitsToDevice(mem, 0, 0, VW, VH, 0, 0, 0, VH, g_bmp + 54,
|
|
(BITMAPINFO *)(g_bmp + 14), DIB_RGB_COLORS);
|
|
SetStretchBltMode(dc, HALFTONE);
|
|
StretchBlt(dc, 10, 10, 640, 480, mem, 0, 0, VW, VH, SRCCOPY);
|
|
SelectObject(mem, old);
|
|
DeleteObject(bm);
|
|
DeleteDC(mem);
|
|
SelectObject(dc, g_font);
|
|
SetBkMode(dc, TRANSPARENT);
|
|
SetTextColor(dc, RGB(220, 220, 220));
|
|
int y = 20;
|
|
char line[256];
|
|
snprintf(line, sizeof(line), "frame : %u", g_fcount);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "fps : %.1f", g_fps);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
if (g_probe_x >= 0) {
|
|
double t = counts_to_c((double)g_live[g_probe_y * W + g_probe_x]);
|
|
snprintf(line, sizeof(line), "probe : (%d,%d) %.2f C", g_probe_x, g_probe_y, t);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
}
|
|
if (g_max_x >= 0) {
|
|
snprintf(line, sizeof(line), "max : (%d,%d) %.2f C", g_max_x, g_max_y, g_max_temp);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
}
|
|
snprintf(line, sizeof(line), "center: %.2f C", g_center_temp);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "pipeline: official full (DDT+NUC+blind+LUT+2x)");
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
snprintf(line, sizeof(line), "shutter: %d sel: %d win: [%d,%d]", g_shutter, g_sel,
|
|
g_win_lo, g_win_hi);
|
|
TextOutA(dc, 670, y, line, (int)strlen(line)); y += 24;
|
|
EndPaint(hw, &ps);
|
|
break;
|
|
}
|
|
case WM_LBUTTONDOWN: {
|
|
int x = LOWORD(lp), y = HIWORD(lp);
|
|
if (x >= 10 && x < 650 && y >= 10 && y < 490) {
|
|
g_probe_x = (x - 10) * W / 640;
|
|
g_probe_y = 119 - (y - 10) * H / 480;
|
|
InvalidateRect(hw, NULL, TRUE);
|
|
}
|
|
break;
|
|
}
|
|
case WM_COMMAND:
|
|
switch (LOWORD(wp)) {
|
|
case 1001:
|
|
g_manual_ffc = 1;
|
|
SetWindowTextA(hw, "FFC queued");
|
|
break;
|
|
case 1002: {
|
|
char path[MAX_PATH];
|
|
SYSTEMTIME st;
|
|
GetLocalTime(&st);
|
|
snprintf(path, sizeof(path), "thermal_%04d%02d%02d_%02d%02d%02d.bmp",
|
|
st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
|
|
FILE *f = fopen(path, "wb");
|
|
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); }
|
|
SetWindowTextA(hw, "saved");
|
|
break;
|
|
}
|
|
case 1004: /* recenter = trigger FFC pair (official ref refresh) */
|
|
g_manual_ffc = 1;
|
|
SetWindowTextA(hw, "FFC queued");
|
|
break;
|
|
case 1005:
|
|
g_probe_x = -1;
|
|
InvalidateRect(hw, NULL, TRUE);
|
|
break;
|
|
}
|
|
break;
|
|
case WM_ERASEBKGND:
|
|
return 1;
|
|
case WM_KEYDOWN:
|
|
if (wp == VK_ESCAPE) { DestroyWindow(hw); return 0; }
|
|
break;
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
return 0;
|
|
}
|
|
return DefWindowProc(hw, msg, wp, lp);
|
|
}
|
|
|
|
int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmd, int show) {
|
|
(void)prev; (void)cmd; (void)show;
|
|
libusb_init(&g_ctx);
|
|
if (!load_ddt(OFF_DDT)) {
|
|
char alt[MAX_PATH];
|
|
snprintf(alt, sizeof(alt), "%s\\Core160043865", getenv("TEMP") ? getenv("TEMP") : ".");
|
|
if (!load_ddt(alt)) {
|
|
MessageBoxA(NULL, "DDT calibration file not found", "MAG160C Demo", MB_ICONERROR);
|
|
return 1;
|
|
}
|
|
}
|
|
int ok = 0;
|
|
for (int attempt = 0; attempt < 4 && !ok; ++attempt) {
|
|
if (attempt > 0) {
|
|
if (g_h) libusb_reset_device(g_h);
|
|
if (g_h) { libusb_close(g_h); g_h = NULL; }
|
|
Sleep(2500);
|
|
}
|
|
libusb_device **list = NULL;
|
|
ssize_t cnt = libusb_get_device_list(g_ctx, &list);
|
|
for (ssize_t i = 0; i < cnt && !g_h; ++i) {
|
|
struct libusb_device_descriptor d;
|
|
libusb_get_device_descriptor(list[i], &d);
|
|
if (d.idVendor == 0x833c) libusb_open(list[i], &g_h);
|
|
}
|
|
libusb_free_device_list(list, 1);
|
|
if (!g_h) continue;
|
|
libusb_set_configuration(g_h, 2);
|
|
libusb_set_configuration(g_h, 1);
|
|
if (libusb_claim_interface(g_h, 0) != 0) { libusb_close(g_h); g_h = NULL; continue; }
|
|
if (sendcmd(0x6bb6b66b, 0, 4)) continue;
|
|
if (sendcmd(0x6bb6b66c, 0, 4)) continue;
|
|
if (sendcmd(0x6bb6b66f, 0, 4)) continue;
|
|
if (sendcmd(0x6bb6b672, 0, 8)) continue;
|
|
Sleep(100);
|
|
if (sendcmd(0x6bb6b672, 0, 8)) continue;
|
|
Sleep(300);
|
|
if (sendcmd(0x6bb6b673, 0, 4)) continue;
|
|
Sleep(700);
|
|
ok = 1;
|
|
}
|
|
if (!ok) {
|
|
MessageBoxA(NULL, "camera init failed (retried 4x)", "MAG160C Demo", MB_ICONERROR);
|
|
return 1;
|
|
}
|
|
|
|
WNDCLASSA wc = {0};
|
|
wc.lpfnWndProc = wndproc;
|
|
wc.hInstance = inst;
|
|
wc.lpszClassName = "Mag160cDemoV5";
|
|
wc.hCursor = LoadCursor(NULL, IDC_CROSS);
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
RegisterClassA(&wc);
|
|
g_hwnd = CreateWindowA("Mag160cDemoV5", "MAG160C Thermal Demo v5 (official pipeline)",
|
|
WS_OVERLAPPEDWINDOW, 60, 40, 1000, 620,
|
|
NULL, NULL, inst, NULL);
|
|
g_font = CreateFontA(18, 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET,
|
|
0, 0, CLEARTYPE_QUALITY, 0, "Consolas");
|
|
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
670, 160, 120, 32, g_hwnd, (HMENU)1001, inst, NULL);
|
|
CreateWindowA("BUTTON", "Save BMP", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
800, 160, 120, 32, g_hwnd, (HMENU)1002, inst, NULL);
|
|
CreateWindowA("BUTTON", "FFC", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
670, 200, 120, 32, g_hwnd, (HMENU)1004, inst, NULL);
|
|
CreateWindowA("BUTTON", "Clear probe", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
|
|
820, 200, 100, 32, g_hwnd, (HMENU)1005, inst, NULL);
|
|
|
|
unsigned sz = 54 + VW * VH * 3;
|
|
g_bmp[0] = 'B'; g_bmp[1] = 'M';
|
|
g_bmp[2] = (unsigned char)sz; g_bmp[3] = (unsigned char)(sz >> 8);
|
|
g_bmp[4] = (unsigned char)(sz >> 16); g_bmp[5] = (unsigned char)(sz >> 24);
|
|
g_bmp[10] = 54; g_bmp[14] = 40;
|
|
g_bmp[18] = VW & 0xFF; g_bmp[19] = (VW >> 8) & 0xFF;
|
|
g_bmp[20] = (VW >> 16) & 0xFF; g_bmp[21] = (VW >> 24) & 0xFF;
|
|
g_bmp[22] = VH & 0xFF; g_bmp[23] = (VH >> 8) & 0xFF;
|
|
g_bmp[24] = (VH >> 16) & 0xFF; g_bmp[25] = (VH >> 24) & 0xFF;
|
|
g_bmp[26] = 1; g_bmp[28] = 24;
|
|
|
|
ShowWindow(g_hwnd, SW_SHOW);
|
|
SetWindowTextA(g_hwnd, "starting - official pipeline");
|
|
|
|
/* initialize the DDT-based tables with the current shutter (unknown
|
|
* until the first frame arrives: use a mid endpoint default) */
|
|
g_ffc_idx = -1;
|
|
FILE *dbg = fopen("demo3_diag.txt", "w");
|
|
FILE *ffcdbg = fopen("demo3_ffc.txt", "a");
|
|
unsigned prev2 = 0xffffffff;
|
|
DWORD t0 = GetTickCount();
|
|
DWORD lfps_t = t0;
|
|
unsigned lfps_c = 0;
|
|
DWORD last_frame_t = t0;
|
|
DWORD last_reset_t = t0;
|
|
int ref_reset = 0;
|
|
int skipped = 0;
|
|
|
|
for (;;) {
|
|
MSG msg;
|
|
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
|
if (msg.message == WM_QUIT) goto done;
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
unsigned char hdr[64];
|
|
int xfer = 0;
|
|
if (libusb_bulk_transfer(g_h, 0x81, hdr, sizeof(hdr), &xfer, 200) && xfer < 28) {
|
|
DWORD now2 = GetTickCount();
|
|
if (now2 - last_frame_t > 3000 && now2 - last_reset_t > 5000) {
|
|
sendcmd(0x6bb6b672, 1, 8);
|
|
last_reset_t = now2;
|
|
}
|
|
continue;
|
|
}
|
|
if (xfer < 28) continue;
|
|
unsigned m = (unsigned)hdr[0] | ((unsigned)hdr[1] << 8) |
|
|
((unsigned)hdr[2] << 16) | ((unsigned)hdr[3] << 24);
|
|
if (m != 0x1bb1b11b) continue;
|
|
unsigned c = (unsigned)hdr[4] | ((unsigned)hdr[5] << 8) |
|
|
((unsigned)hdr[6] << 16) | ((unsigned)hdr[7] << 24);
|
|
if (c == prev2) continue;
|
|
prev2 = c;
|
|
if (libusb_bulk_transfer(g_h, 0x81, g_frame, sizeof(g_frame), &xfer, 200) || xfer < 38400) continue;
|
|
last_frame_t = GetTickCount();
|
|
g_global_frames++;
|
|
if (g_global_frames < OFF_WARM_FRAMES) continue;
|
|
|
|
/* shutter = tail word at frame offset len+0x24 (pixels start at
|
|
* g_frame[0]; tail = g_frame + 0x9600 + 0x24 - 0x1c) */
|
|
unsigned len = (unsigned)hdr[8] | ((unsigned)hdr[9] << 8) |
|
|
((unsigned)hdr[10] << 16) | ((unsigned)hdr[11] << 24);
|
|
int shutter = 0;
|
|
if (len == 0x9600 && xfer >= 0x9608 + 4)
|
|
shutter = (int)((unsigned)g_frame[0x9608] | ((unsigned)g_frame[0x9609] << 8) |
|
|
((unsigned)g_frame[0x960a] << 16) | ((unsigned)g_frame[0x960b] << 24));
|
|
g_shutter = shutter;
|
|
|
|
if (g_manual_ffc) {
|
|
g_manual_ffc = 0;
|
|
sendcmd(0x6bb6b672, 0, 8);
|
|
g_ffc_idx = 0;
|
|
g_shutter_at_ffc = shutter;
|
|
if (ffcdbg) { fprintf(ffcdbg, "frame=%u MANUAL FFC(0) shutter=%d\n", g_global_frames, shutter); fflush(ffcdbg); }
|
|
}
|
|
|
|
/* official FFC state machine (FUN_180009ee0) */
|
|
if (g_ffc_idx < 0) {
|
|
/* pre-init: send FFC(0), start the cycle */
|
|
sendcmd(0x6bb6b672, 0, 8);
|
|
g_ffc_idx = 0;
|
|
g_shutter_at_ffc = shutter;
|
|
if (ffcdbg) { fprintf(ffcdbg, "frame=%u INIT FFC(0) shutter=%d\n", g_global_frames, shutter); fflush(ffcdbg); }
|
|
}
|
|
g_ffc_idx++;
|
|
int idx = g_ffc_idx;
|
|
{
|
|
unsigned ftype = (unsigned)hdr[12] | ((unsigned)hdr[13] << 8) |
|
|
((unsigned)hdr[14] << 16) | ((unsigned)hdr[15] << 24);
|
|
long long fsum = 0;
|
|
for (int i = 0; i < NPIX; i += 16)
|
|
fsum += (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
|
|
if (dbg) { fprintf(dbg, "STAGE idx=%d type=%u fmean=%.0f shutter=%d\n", idx, ftype,
|
|
(double)fsum / (NPIX / 16), shutter); fflush(dbg); }
|
|
if (ftype != 0 && idx == 6) {
|
|
static int saved;
|
|
if (!saved) {
|
|
saved = 1;
|
|
FILE *ft = fopen("type1_frame.bin", "wb");
|
|
if (ft) { fwrite(g_frame, 1, 38400, ft); fclose(ft); }
|
|
}
|
|
}
|
|
}
|
|
if (idx <= OFF_N1) {
|
|
if (idx == 1) {
|
|
rebuild_tables(g_shutter_at_ffc);
|
|
if (dbg) { fprintf(dbg, "rebuild sel=%d T=[%d,%d] shutter=%d\n", g_sel,
|
|
g_ep_temps[g_sel], g_ep_temps[g_sel + 1], g_shutter_at_ffc); fflush(dbg); }
|
|
}
|
|
continue;
|
|
}
|
|
if (idx <= OFF_N0 + OFF_N1) {
|
|
if (idx == OFF_N0 + OFF_N1) {
|
|
sendcmd(0x6bb6b672, 1, 8);
|
|
if (ffcdbg) { fprintf(ffcdbg, "frame=%u FFC(1)\n", g_global_frames); fflush(ffcdbg); }
|
|
}
|
|
if (idx == OFF_N1 + 1) {
|
|
g_ref_cnt = 0;
|
|
g_has_ref = 0;
|
|
ref_reset = 1;
|
|
}
|
|
/* collect ref frame */
|
|
for (int i = 0; i < NPIX; ++i)
|
|
g_live[i] = (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
|
|
if (ref_reset) { ref_reset = 0; g_ref_cnt = 0; }
|
|
{
|
|
long long lsum = 0;
|
|
for (int i = 0; i < NPIX; ++i) lsum += g_live[i];
|
|
FILE *f3 = fopen("demo3_ref.txt", "a");
|
|
if (f3) { fprintf(f3, " push idx=%d live_mean=%.0f cnt=%d\n", idx,
|
|
(double)lsum / NPIX, g_ref_cnt); fclose(f3); }
|
|
}
|
|
ref_push(g_live);
|
|
continue;
|
|
}
|
|
if (idx <= OFF_N0 + OFF_N2 + OFF_N1) {
|
|
continue;
|
|
}
|
|
/* normal frame: FFC condition */
|
|
{
|
|
int drift = g_shutter - g_shutter_at_ffc;
|
|
if (drift < 0) drift = -drift;
|
|
int cool = OFF_N0 + OFF_N2 + OFF_N1;
|
|
int do_ffc = 0;
|
|
if (idx >= OFF_N0 + OFF_FFC_PERIOD)
|
|
do_ffc = 1;
|
|
else if (idx >= cool + 30 && drift > OFF_FFC_DRIFT)
|
|
do_ffc = 1;
|
|
if (do_ffc) {
|
|
sendcmd(0x6bb6b672, 0, 8);
|
|
g_ffc_idx = 0;
|
|
g_shutter_at_ffc = shutter;
|
|
if (ffcdbg) { fprintf(ffcdbg, "frame=%u AUTO FFC(0) idx=%d drift=%d\n", g_global_frames, idx, drift); fflush(ffcdbg); }
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* decode + render */
|
|
for (int i = 0; i < NPIX; ++i)
|
|
g_live[i] = (unsigned short)(g_frame[i * 2] | ((unsigned)g_frame[i * 2 + 1] << 8));
|
|
g_fcount++;
|
|
if (dbg) { fprintf(dbg, "RENDER f=%u has_ref=%d tables=%d\n", g_fcount, g_has_ref, g_tables_ready); fflush(dbg); }
|
|
if (g_has_ref && g_tables_ready) {
|
|
official_nuc_and_blind(g_live, g_nuc);
|
|
official_stats_window(g_nuc);
|
|
lut_rebuild(g_nuc);
|
|
gray_map(g_nuc);
|
|
upscale2x();
|
|
} else {
|
|
memset(g_nuc, 0, sizeof(g_nuc));
|
|
memset(g_gray160, 0, sizeof(g_gray160));
|
|
}
|
|
/* hot spot + temps */
|
|
unsigned hotv = 0;
|
|
int hot = 0;
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
if (g_nuc[i] > hotv && g_nuc[i] != 0) { hotv = g_nuc[i]; hot = i; }
|
|
}
|
|
if (hot >= 0 && hotv) {
|
|
g_max_x = hot % W;
|
|
g_max_y = hot / W;
|
|
g_max_temp = counts_to_c((double)hotv);
|
|
}
|
|
g_center_temp = counts_to_c((double)g_stat_mean);
|
|
render_bmp();
|
|
|
|
if (dbg) {
|
|
long long rsum = 0, lsum = 0, dsum = 0;
|
|
long long dmin = 1 << 30, dmax = -(1 << 30);
|
|
for (int i = 0; i < NPIX; ++i) {
|
|
rsum += g_ref[i]; lsum += g_live[i];
|
|
long long d = (long long)g_live[i] - g_ref[i];
|
|
dsum += d;
|
|
if (d < dmin) dmin = d;
|
|
if (d > dmax) dmax = d;
|
|
}
|
|
fprintf(dbg, "DIAG ref_mean=%.0f live_mean=%.0f d=(%lld..%lld m%.0f)\n",
|
|
(double)rsum / NPIX, (double)lsum / NPIX, dmin, dmax, (double)dsum / NPIX);
|
|
fflush(dbg);
|
|
}
|
|
if (dbg) {
|
|
fprintf(dbg, "f=%u shutter=%d sel=%d/%d win=[%d,%d] stats=(%d..%d m%d s%d) ref=%d lut[0]=%d lut[100]=%d lut[1023]=%d\n",
|
|
g_fcount, g_shutter, g_sel, g_ep_count, g_win_lo, g_win_hi,
|
|
g_stat_min, g_stat_max, g_stat_mean, g_stat_std,
|
|
g_has_ref, g_lut[0], g_lut[100], g_lut[1023]);
|
|
fflush(dbg);
|
|
}
|
|
DWORD now = GetTickCount();
|
|
if (now - lfps_t >= 1000) {
|
|
g_fps = (g_fcount - lfps_c) * 1000.0 / (now - lfps_t);
|
|
lfps_t = now;
|
|
lfps_c = g_fcount;
|
|
}
|
|
snprintf(g_title, sizeof(g_title),
|
|
"MAG160C v5 official - frame %u idx %d shutter %d sel %d win [%d,%d] skipped %d",
|
|
g_global_frames, g_ffc_idx, g_shutter, g_sel, g_win_lo, g_win_hi, skipped);
|
|
SetWindowTextA(g_hwnd, g_title);
|
|
if (g_fcount % 30 == 0 && g_fcount / 30 < 40) {
|
|
char pth[MAX_PATH];
|
|
snprintf(pth, sizeof(pth), "demo3_auto_%03u.bmp", g_fcount / 30);
|
|
FILE *f = fopen(pth, "wb");
|
|
if (f) { fwrite(g_bmp, 1, sizeof(g_bmp), f); fclose(f); }
|
|
}
|
|
if (g_fcount == 300 || g_fcount == 600 || g_fcount == 900) {
|
|
FILE *fc = fopen("demo3_frame_capture.bin", "wb");
|
|
if (fc) {
|
|
fwrite(g_live, 2, NPIX, fc);
|
|
fwrite(g_ref, 2, NPIX, fc);
|
|
fwrite(g_nuc, 2, NPIX, fc);
|
|
fwrite(g_gray160, 1, NPIX, fc);
|
|
fwrite(g_lut, 1, 1024, fc);
|
|
fwrite(g_thr_work, 2, (g_nsegs - 1) * NPIX + g_c5c / 2, fc);
|
|
fwrite(g_gain_work, 2, g_nsegs * NPIX * 2, fc);
|
|
int meta[8] = {g_win_lo, g_win_hi, g_stat_min, g_stat_max,
|
|
g_stat_mean, g_stat_std, g_shutter, g_sel};
|
|
fwrite(meta, 4, 8, fc);
|
|
fclose(fc);
|
|
if (dbg) { fprintf(dbg, "CAPTURED frame data\n"); fflush(dbg); }
|
|
}
|
|
}
|
|
InvalidateRect(g_hwnd, NULL, FALSE);
|
|
UpdateWindow(g_hwnd);
|
|
}
|
|
done:
|
|
if (dbg) fclose(dbg);
|
|
if (ffcdbg) fclose(ffcdbg);
|
|
Sleep(300);
|
|
libusb_clear_halt(g_h, 0x03);
|
|
libusb_clear_halt(g_h, 0x82);
|
|
sendcmd(0x6bb6b674, 0, 4);
|
|
libusb_close(g_h);
|
|
libusb_exit(g_ctx);
|
|
return 0;
|
|
}
|