The frame-by-frame stitcher accumulated drift, so later passes landed rotated and offset, doubling text. myscan/pagegraph.py instead solves all frame poses together: ECC odometry between consecutive frames (bridging single corrupt frames), SIFT+RANSAC loop closures between overlapping passes, and a robust Gauss-Newton pose graph. Loop matches must agree over the whole overlap, which rejects matches on repeated text. Off-page frames and frames that disagree with the mosaic are left out of the render. calib.Undistort applies the EEPROM lens model (OpenCV k1..k3, p1, p2 plus the tilt homography, as decoded in EEPROM.md); up to 8.8 px at the corners. scan.py registers frames on a worker thread so capture no longer drops frames, then assembles the page after the stop press (~11 s for a full page). stitch.py uses the new assembly by default; --live keeps the old one. EEPROM.md documents the full calibration blob layout, checksum and signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 KiB
EEPROM contents (Hama myScan / Dacuda LuckyCat)
This is a field-by-field map of the per-device calibration blob, from one unit's dump (calib/eeprom_3000.bin).
For the read protocol, see FINDINGS.md ("Per-device calibration in EEPROM"). For the app side, see
RE_colour_stitch.md section 1.4.
Status tags:
- [P]: parsed by
myscan/calib.py. - [D]: decoded by hand from the bytes; the value is clear.
- [G]: guessed field name or meaning.
- [?]: unknown.
- [V]: verified in disassembly or decompilation; the function address is given as evidence.
EEPROM map
| Address | Size | Content |
|---|---|---|
| 0x3000 | 2 | u16 LE blob length N (19417 on this unit). Valid if 0 < N <= 20000 (0x4E20) [V 0x138c4e0, read of 0x3000 at 0x138c743, bound check 0x138c7a0]. The write path rejects N >= 0x4FB7 [V 0x138cc90] |
| 0x3002 | 4 | u32 LE standard CRC-32 (zlib polynomial) over the N blob bytes, signature included. Verified in Python against the dump: 0x3cd95f42 [D]. The app does not recompute or compare it on read: it is only a cache key (see below) [V 0x1335430] |
| 0x3006 | N | boost::archive::binary_iarchive blob (i386 sizes), then a 256-byte RSA signature at its tail |
| 0x7EB5 | 64 | mouse ID: raw 64-byte buffer, handled as a string. Read by OVMouseManager::readMouseId (0x138e1e0 [V], read at 0x138e4fa), written by writeMouseId(const char*) (0x138db20 [V], write at 0x138dd80). Not yet read from the device. The HalLuckyCat wrapper is 0x1334a60 |
| < 0x3000 | ? | The app never touches anything below 0x3000. A scan of every call to OVIF_DaEEPROMRead/Write (the stubs 0x17ff6fa / 0x17ff700) in the main binary gives only the callers 0x138c4e0 (0x3000, 0x3006), 0x138d410 (0x3002), 0x138e1e0 (0x7EB5), 0x138cc90 (writeBinaryData: a single write starting at 0x3000 for header + checksum + blob, call at 0x138cf3f; the caller builds the whole buffer, the CRC is not computed there) and 0x138db20 (0x7EB5). UVCControl.framework just wraps the raw read/write (EEPROMRead:withStartAddr:withLength:). So whatever is below 0x3000 is firmware-side only [V by absence] |
Everything the app reads from the EEPROM is therefore: length, checksum, blob and the 64-byte ID.
Blob layout
All offsets below are relative to the blob start (EEPROM 0x3006). The whole blob parses with no gaps (a stand-alone parser consumes exactly bytes 0 .. N-256, then the signature follows).
Stream = boost::archive::binary_iarchive, library version 7 (boost ~1.42), i386 sizes. Each class is written
as: tracking u8, class version u8, then its members, in the order of serialize<binary_iarchive>.
All five classes here have 00 0e = tracking 0, class version 14. Fixed C arrays (T[n]) are written as
u32 count + elements. std::vector<T> is u32 count, u32 item_version, elements. A class that is
first met inside a container (std::vector, boost::array) writes its own 00 00 (tracking 0, version 0) once.
The app accepts class versions 7..15 only (else WrongArchiveVersionException, "Incompatible archive version!",
[V] 0x14cc1a0 and the other four serialize functions).
Serialize functions ([V], serialize<binary_iarchive>): Info 0x14cc1a0, Color 0x14cde20, Undistortion 0x14cefe0,
Sensor 0x14cfcd0, Roi 0x14d0990. Names come from the property-tree writers: Undistortion 0x14c0510, Sensor 0x14c1300,
Roi 0x14c23a0, Info 0x14c2bc0, Color 0x14bedb0 (all [V]); the umbrella HardwareData has members
color @0x00, undistortion @0x68, sensor @0xF4, roi @0x144, info @0x158 (0x14be690 [V]).
Header
| Offset | Value | Meaning |
|---|---|---|
| 0x00 | u32 22, serialization::archive |
boost signature [D] |
| 0x1A | 07 04 04 04 08 |
library version 7; sizeof int/long/float/double = 4/4/4/8 [D] |
| 0x1F | u32 1 | endianness check [D] |
RoiParams (0x23: 00 0e) [V 0x14d0990, names 0x14c23a0]
Five plain i32 in this order, no version conditions.
| Offset | Member offset | Type | Name | Value |
|---|---|---|---|---|
| 0x25 | +0x00 | i32 | yBaseOffset |
105 |
| 0x29 | +0x04 | i32 | roiX0 |
0 |
| 0x2D | +0x08 | i32 | roiY0 |
0 |
| 0x31 | +0x0C | i32 | roiX1 |
640 |
| 0x35 | +0x10 | i32 | roiY1 |
295 |
yBaseOffset was previously unknown. It is the first Roi integer (105).
SensorParams (0x39: 00 0e) [V 0x14cfcd0, names 0x14c1300]
Read order below. "Version" is the stream class version. This unit is 14.
| Offset | Member offset | Type | Name | Needs ver | Value |
|---|---|---|---|---|---|
| 0x3B | +0x08 | f64 | metersPerLeftMouseUnit |
any | 1.922e-5 |
| 0x43 | +0x10 | f64 | metersPerRightMouseUnit |
any | 1.878e-5 |
| 0x4B | +0x00 | f64 | laserReadoutDelay |
>= 9 | 0.0 |
| 0x53 | +0x18 | f32 | camToLeftMouseAngle |
>= 14 | 0.0 |
| 0x57 | +0x1C | f64[2] (count 2) | camToLeftMouseVec (m) |
any | (0.01125, 0.0538) |
| 0x6B | +0x2C | f32 | camToRightMouseAngle |
>= 14 | 0.0 |
| 0x6F | +0x30 | f64[2] (count 2) | camToRightMouseVec (m) |
any | (0.03355, -0.00067) |
| (absent) | +0x40 | f64 | gyroDegPerStep |
>= 15 | not in this blob |
| (absent) | +0x48 | f64 | gyroBias |
>= 15 | not in this blob |
So the gyro fields do not exist for this unit: it is a version-14 blob, and the gyro pair needs 15.
The two angles are float (setters movss 0x14c73e0 / 0x14c7450, getters fld dword 0x14c7400 / 0x14c7470) [V].
imagerGain and exposureTimeUs are ColorParams members, not Sensor members. yBaseOffset is in Roi.
UndistortionParams (0x83: 00 0e) [V 0x14cefe0, names 0x14c0510]
| Offset | Member offset | Type | Name | Needs ver | Value |
|---|---|---|---|---|---|
| 0x85 | +0x00 | f64 | focalLength (px) |
any | 737.328 |
| 0x8D | +0x08 | f64 | principalPointX (px) |
any | 317.546 |
| 0x95 | +0x10 | f64 | principalPointY (px) |
any | 132.733 |
| 0x9D | +0x18 | i32 | distModel (Utilities::Enums::EDistortionModel) |
>= 8 | 4 |
| 0xA1 | +0x1C | f64[5] (count 5) | distCoefficients |
any | 0, -0.403169, 1.3805e-4, 2.6490e-3, 0 |
| 0xCD | +0x44 | f64[8] (count 8) | tiltH |
any | see below |
| 0x111 | +0x84 | f64 | pixelSize (m) |
any | 6.1305e-5 |
tiltH = 1.002744, 0.007833, -0.985882, -0.000164, 1.006565, -0.435809, -1.17e-6, 2.47e-5.
Eight numbers for a 3x3 homography whose ninth entry is implicit (=1). Usage: see "How it is used" below.
ColorParams (0x119: 00 0e) [V 0x14cde20, names 0x14bedb0]
The sample sets are std::vector<std::vector<SamplingPointAbstract<short,unsigned char,3>>> (member +0x00,
two inner vectors: black, white). Framing, in stream order:
| Offset | Content |
|---|---|
| 0x11B | 00 00 class info of outer vector |
| 0x11D | u32 2 (outer count), u32 0 (item version) |
| 0x125 | 00 00 class info of inner vector |
| 0x127 | u32 827 (count), u32 0 (item version), 00 00 class info of the sample type |
| 0x131 | 827 samples: i16 x, i16 y, u8[3] colour (u32 count 3 + b, g, r). The first sample also carries a 00 00 class info for boost::array between y and the count (13 bytes); the rest are 11 bytes |
| 0x24BD | u32 873 (count), u32 0 (item version), then 873 samples of 11 bytes (x, y, 3, b, g, r) |
Sample arrays are SamplingPointAbstract<short, uchar, 3>: the two coordinates are signed shorts. Scalar members follow:
| Offset | Member offset | Type | Name | Needs ver | Value |
|---|---|---|---|---|---|
| 0x4A47 | +0x0C | u32 | exposureTimeUs |
>= 8 | 440 |
| 0x4A4B | +0x18 | i32 | gainR |
>= 8 | 115 |
| 0x4A4F | +0x1C | i32 | gainG |
>= 8 | 89 |
| 0x4A53 | +0x20 | i32 | gainB |
>= 8 | 64 |
| 0x4A57 | +0x10 | f64 | imagerGain |
>= 9 | 1.5 |
| 0x4A5F | +0x24 | f64[3] (count 3) | lambda (one per channel) |
>= 10 | 1.921e12, 5.193e7, 5.098e7 |
| 0x4A7B | +0x3C | f64 | lambdaNormFactor |
>= 11 | 6025357260.0 |
| 0x4A83 | +0x44 | i32 | nurbsModelSize |
>= 12 | 5 |
| 0x4A87 | +0x48 | f32[3] (count 3) | nurbsSmoothingFactors |
>= 12 | 50, 50, 50 |
| 0x4A97 | +0x54 | i32 | nurbsNSmoothingIterations |
>= 12 | 100 |
| 0x4A9B | +0x58 | i32[4] (count 4) | clipOffset |
>= 14 | 8, 12, 8, 12 |
The names are from the property-tree writer 0x14bedb0 (offsets confirm the mapping). Corrections to older notes:
imagerGain is the 1.5 (not "lambda"), lambda is the 3-vector (not "lambdaNormFactor"), the 6.0e9 double is
lambdaNormFactor, the "u32 5" is nurbsModelSize (a scalar, not a list), and 4, 8, 12, 8, 12 is the
clipOffset int[4] with its count (so clipOffset = 8, 12, 8, 12).
clipOffset is read as a QList<int> by 0x14c5600 and used in QualityTrackManager setup (0x115bc70 [V]): it is subtracted
from the ROI to size the overlap estimator, height_px = (roiY1 - roiY0) - clip[1] - clip[3], width_px = (roiX1 - roiX0) - clip[0] - clip[2]
(then times pixelSize), so the order is {left, top, right, bottom} margins in pixels [G for the edge names, V for the pairing].
The app's built-in default (HWSetup ini, string 8 28 8 12) has the same shape.
InfoParams (0x4AAF: 00 0e) [V 0x14cc1a0, names 0x14c2bc0]
The first 16 bytes of the Info member are a GUID (Data1 u32, Data2 u16, Data3 u16, Data4 u8[8]; the
array is written with u32 count 8). The previous "8 bytes before fingerprint" are Data1..Data3, and the old
"fingerprint" is Data4.
| Offset | Member offset | Type | Name | Needs ver | Value |
|---|---|---|---|---|---|
| 0x4AB1 | +0x00 | u32 | fingerprint Data1 |
any | 0x84F3094A |
| 0x4AB5 | +0x04 | u16 | Data2 | any | 0x7B88 |
| 0x4AB7 | +0x06 | u16 | Data3 | any | 0x49C6 (UUID version 4) |
| 0x4AB9 | +0x08 | u8[8] (count 8) | Data4 | any | 86 57 3a 6a 38 5e 6f 68 |
| 0x4AC5 | +0x10 | u32 | calibrationTime (time_t) |
any | 0x5269BEBF = 2013-10-24 23:35:27 UTC |
| 0x4AC9 | +0x14 | f32 | avgReprojErrorIntrinsic (px) |
any | 0.158 |
| 0x4ACD | +0x18 | f32 | avgReprojErrorHomography (px) |
any | 0.133 |
| 0x4AD1 | +0x1C | u32 | product (Utilities::Enums::EProduct; not in the ptree writer) |
>= 13 | 4 |
| 0x4AD5 | +0x20 | u32 | archiveVersion |
>= 11 (else it takes the class version) | 14 |
The old "enum 14" is archiveVersion, which equals the class version here. The old "archiveVersion 4" at +0x1C is the
product enum: its accessors are 0x14c7880 (set) and 0x14c7890 (get) [V]; HalLuckyCat::open (0x1331ae0) copies it into the
driver config (config+8) and validates it via 0x1300ff0, which maps it to a profile resource :/mouse_driver_profiles/<EProduct_*>.xml.
For archives older than 13 the product reads as 0, and loadHardwareData (0x1335430, "CONVERT_LSM200_HACK") rewrites it to 1 for old
units whose calibrationTime falls in a window around Jan-May 2012 (compared with QDate(2012,1,1) and QDate(2012,5,1)) and whose pixelSize is below a threshold,
and it also replaces camToLeft/Right vectors with fixed values (0.0141, 0.0525) and (0.036, 0) there [V 0x1335430, exact date logic not worked out]. This unit (2013) is unaffected. open picks DriverFactoryLG for product in {2, 13, 14}, otherwise
DriverFactoryLL; product 4 (this unit) therefore uses DriverFactoryLL. The profile resources are named
EProduct_ADI_WIRED/WIRELESS, ASD_SLAMBASIC/SLAMBASIC2/SLAMGYRO/SLAMLITE/WIRELESS, FOX_WIRED/WIRED_GYRO/WIRELESS, LG_LSM100/150/200/300, Passport, WING_WIRED; the
numeric order of the enum (moc table) is not readable in the binary, so which product is 4 is [?].
Full UUID: 84f3094a-7b88-49c6-8657-3a6a385e6f68 (the unit's unique id, matches the old "fingerprint").
None of the product/vendor/ODM text strings are in this archive; those live in the HWSetup ini struct
(0x1441300: productString EProduct_LG_LSM100, vendorString EVendor_LG, odmString EOdm_Chicony,
exposureTimeUsMin/Max, skewedImageGain).
Signature
| Offset | Value | Meaning |
|---|---|---|
| 0x4AD9 | 256 bytes, to the end of the blob | RSA-2048 PKCS#1 v1.5 signature with SHA-1 (DigestInfo 3021300906052b0e03021a05000414) over blob bytes [0, N-256), i.e. everything before the signature, header excluded [D]. Verifies against calib/calib_rsa.pub.pem (public exponent 35, an unusual value) with cryptography. |
Integrity
Checksum (0x3002) [D, matched against the dump; not seen in disassembly]
CRC-32 (the zlib crc32(), polynomial 0xEDB88320, init/xorout 0xFFFFFFFF) over all N blob bytes including
the 256-byte signature, stored little-endian. On this unit: zlib.crc32(blob) == 0x3cd95f42 and the EEPROM bytes at
0x3002 are 42 5f d9 3c. The sum, Adler-32, SHA-1 and MD5 variants were tried and do not match.
Use in the app (HalLuckyCat::loadHardwareData, 0x1335430 [V]): the checksum is read via readBinaryDataCheckSum
(0x138d410) and turned into a cache file name <PathsPool::logDir()>/<crc as 8 hex digits>.bin.
- Checksum 0 or 0xFFFFFFFF (unprogrammed EEPROM): skip the cache and read the blob from the mouse.
- Otherwise the app tries the cache file ("Read HardwareData from file.", parser 0x14ba4e0). On failure it logs "Error reading
HardwareData from file. Try reading from mouse" and reads the blob from the EEPROM (
ovtReadHardwareData, 0x137e0e0). After a read from the mouse it writes the cache (0x14bc150). So the CRC is a cache key; a wrong value would only cause a cache miss or a stale cache hit, not a rejection. No CRC computation happens on the read path (the CRC-32 table is at 0x1c0c730, used by a zlib routine at 0xeff640).
Signature [V]
hal::verifyRSASignature(data, dataLen, sig, sigLen) is 0x12ff5d0: it loads the PEM public key from the Qt resource
:/calib_rsa.pub.txt (PEM_read_bio_PUBKEY) and runs EVP_sha1 + EVP_DigestUpdate + EVP_VerifyFinal, i.e.
RSASSA-PKCS1-v1_5 with SHA-1.
It is called from ovtReadHardwareData (0x137e0e0) after HardwareDataSerializer::fromRawData (0x14c3720). fromRawData
returns the archive stream position after InfoParams; that is the length of the signed part. The acceptance rule is:
ok = (archiveVersion < 11) or (0 < consumed < N and verifyRSASignature(blob, consumed, blob + consumed, N - consumed))
So blobs with archiveVersion >= 11 must carry a valid signature over blob[0 .. consumed) (everything before the
signature; the 6-byte EEPROM header is not included). Older archives (< 11) are accepted unsigned.
This unit: consumed = N - 256 = 19161, the signature is the last 256 bytes, and it verifies:
calib/calib_rsa.pub.pem (RSA-2048, public exponent 35) recovers
0001ff..ff00 3021300906052b0e03021a05000414 b5dab6a61406d37057c5381e242b7424f22a952e and
SHA1(blob[:19161]) = b5dab6a61406d37057c5381e242b7424f22a952e. The key is copied to calib/calib_rsa.pub.pem.
(Verification only. The private key is not in the app; writes are produced by the factory tool.)
How the calibration is used
Undistortion map (generateMapFromParams, 0x120e160 [V]; per-pixel kernel 0x10f1750 [V])
The app builds the K matrix and the distortion vector from the Undistortion members and fills two float
maps mapX, mapY (one entry per output pixel). Accessors: focal 0x14c5d00, ppX 0x14c5d10, ppY 0x14c5d20,
distCoefficients 0x14c5d90 (raw pointer to member +0x1C), tiltH 0x14c5e00 (member +0x44), pixelSize 0x14c5e30.
K = [[f, 0, cx], [0, f, cy], [0, 0, 1]] (fx = fy = focalLength)
H = tiltH row-major, 3x3, H[2][2] = 1.0 (h00 h01 h02 h10 h11 h12 h20 h21)
M = inv(K) * inv(H) (two cvInvert, one cvGEMM; OpenCV 2.4.3)
for every output pixel (x, y):
(X, Y, W) = M * (x, y, 1); x' = X/W, y' = Y/W
r2 = x'^2 + y'^2
radial = 1 + c0*r2 + c1*r2^2 + c4*r2^3
xd = x'*radial + 2*c2*x'*y' + c3*(r2 + 2*x'^2)
yd = y'*radial + c2*(r2 + 2*y'^2) + 2*c3*x'*y'
mapX = K00*xd + K01*yd + K02 ; mapY = K10*xd + K11*yd + K12
distCoefficients= OpenCV order[k1, k2, p1, p2, k3]([V] from the kernel: indices 0, 1, 4 radial; 2, 3 tangential). The unit's values: k1 = 0, k2 = -0.40317, p1 = 1.38e-4, p2 = 2.65e-3, k3 = 0. This is the standard OpenCV 5-parameter model (the same code path as OpenCV'sinitUndistortRectifyMapwithnewCameraMatrix = H*Kin the sensedest = H * K * normalised).tiltHis a homography in pixel space, applied after K: forward direction isdest = H * K * (x_d-undistorted normalised coords), which is whatM = inv(K)*inv(H)encodes. It is not a rotation on normalised rays. For this unit H is close to identity (shift about (-0.99, -0.44) px, scale 1.003 / 1.007, tiny perspective terms).distModelis never read in this build: its getter 0x14c5d40 has no callers (vtable slot only). TheEDistortionModelenum has a name map (0x14cadf0) but the map builder always uses the 5-coefficient model above. So value 4 is cosmetic here [V by absence of callers]. What 4 means in the enum is [?].- The cvUndistortPoints / cvInitUndistortMap error text "Distortion coefficients must be 1x4, 4x1, 1x5..." (0xb65990, 0xb6db50) is plain OpenCV 2.4.3 code; it takes the same 5-vector.
Sensor set-up and misc (HalLuckyCat::open, 0x1331ae0 [V])
open copies the blob into the driver config (config = param_2) with the HardwareData accessors (color 0x14c4ca0 @+0x00,
undistortion 0x14c4cb0 @+0x68, sensor 0x14c4cd0 @+0xF4, roi 0x14c4cf0 @+0x144, info 0x14c4d10 @+0x158):
| Blob field | Goes to | Evidence |
|---|---|---|
exposureTimeUs |
config+0x2F0 = float(exposureTimeUs) * 0.001f (so ms: 440 -> 0.44 ms) |
getter 0x14c5470 called at 0x13328a8; mul at 0x13328ba (constant 0.001f at 0x1cf9a94). This confirms the u32 440 |
imagerGain |
config+0x2F4 (double) |
getter 0x14c54a0 at 0x13328d5 |
gainR/G/B |
config+0x2FC/0x300/0x304 |
getters 0x14c54d0 / 0x14c54e0 / 0x14c54f0 |
| roi x0, y0, x1, y1 | config+0x2D4..0x2E0 (defaults 0, 0, 640, 296) |
0x14c7680 |
yBaseOffset |
config+0x2E4 |
getter 0x14c76d0 |
product |
config+8 |
0x14c7890 |
The reverse direction (config -> blob, used when the app writes/derives a new calibration) uses the setters 0x14c5460 (exposure,
int(ms / 0.001)), 0x14c5480, 0x14c54b0, 0x14c7620, 0x14c76c0, 0x14c7880.
Colour correction (NURBS surfaces)
The blob stores samples, not surfaces. FUN_011e7950 ([V], 3 passes, one per colour channel c = 0, 1, 2) turns the black and the
white sample set into B-spline surfaces (library UCBspl: SplineSurface, MGsystem multigrid, LSsystem least squares):
for c in (0, 1, 2):
x[i], y[i] = sample.x, sample.y ; z[i] = sample.colour_byte[c] # sample = 8-byte struct: i16 x, i16 y, u8[3]
sys = MGsystem(1, 1, nurbsModelSize = 5, x, y, z) # 0x11fc2d0: multilevel B-spline, levels grow as 2^k
if nurbsSmoothingFactors[c] != 0:
sys.setSmoothing(nurbsSmoothingFactors[c]) # 0x11fdb70 (double)
sys.iterate(nurbsNSmoothingIterations, 3) # 0x11fda30
surfaces.append(SplineSurface(sys)) # 0x11fd660
So nurbsModelSize (5) is the number of refinement levels of the multilevel B-spline, nurbsSmoothingFactors[c] is the per-channel
smoothing weight (50 here) and nurbsNSmoothingIterations (100) is the iteration count of the smoothing solver; accessors
0x14c55a0 (modelSize), 0x14c55d0 (factors pointer), 0x14c55f0 (iterations) have the single caller 0x11e7950 [V]. The black surface and
the white surface are evaluated per pixel by the colour balancer (NurbsColorBalancerPolicy); the linear map
out = slope*in + intercept and the target 250 (colorDestBrightImage) are in RE_colour_stitch.md.
The sample sets themselves are the nurbsModelBlack/White members (the vector-of-vectors at ColorParams +0x00).
The three passes use colour-byte index 0, 1, 2 of each sample, which is the stored order b, g, r.
lambda / lambdaNormFactor: the library message "LSsystem::calcLambda(): e_norm is zero when calculating lambda" (string at 0x1b106c4)
suggests [G] that lambda is the regularisation weight computed by the least-squares solver for a fit
(LSsystem::calcLambda, balances data fit against the smoothness term), stored once per channel at calibration time
(1.92e12, 5.19e7, 5.10e7). That is an inference from the library name only; the strings have no direct code reference in the decompilation. lambdaNormFactor (6.025e9) normalises it. The only reader is accessor 0x14c5570, which returns
lambda[0] / lambdaNormFactor (= 318.9 on this unit) [V]. Its two callers are the tracker set-ups FUN_011543e0 (0x115460b, FastTrackLKM
parameters) and FUN_0115bc70 (0x115c341, QualityTrackManager), where it is passed as one double of the parameter struct next to
pixelSize. The factory-line settings key colorExpectedNormalizedLambda1Min (read at 0x1422b40, also 0x14800d0) looks like a lower bound
for this normalised value, i.e. a calibration quality gate [G]. What the tracker does with the value is [?]. lambda[1] and lambda[2]
have no direct accessor call: the pointer accessor 0x14c5530 has only a vtable data reference.
Open questions
- Read the 64-byte ID at 0x7EB5 from the device (not done; static analysis only so far).
- Which product the
EProductvalue 4 is (the moc enumerator table is not in the binary in readable form). - The
EDistortionModelenum value 4 (its name map is built at 0x14cadf0), though the value is never read. - What the tracker (
FastTrackLKM,QualityTrackManager) does withlambda[0]/lambdaNormFactor;lambda[1]/lambda[2]are never read.