Private
Public Access
82 lines
3.1 KiB
JavaScript
82 lines
3.1 KiB
JavaScript
/* Pure measurement processing; the video uses 32 clockwise edge patches. */
|
|
function calibrationFit(baseline, patches, model) {
|
|
if (!model || baseline.length !== 16 || patches.length !== 32) throw new Error('Incomplete calibration measurements.');
|
|
var candidates = [],
|
|
edges = [0, 0, 0, 0];
|
|
for (var region = 0; region < 16; region++) {
|
|
var responses = patches.map(function(p) {
|
|
if (!p || p.length !== 16) throw new Error('Missing video measurements.');
|
|
return Math.max(0, p[region] - baseline[region]);
|
|
});
|
|
var peak = Math.max.apply(Math, responses),
|
|
best = responses.indexOf(peak);
|
|
if (peak < 8) continue;
|
|
// Reject regions responding equally around the screen (global histograms).
|
|
var strong = responses.filter(function(v) {
|
|
return v >= peak * .65;
|
|
}).length;
|
|
if (strong > 10) continue;
|
|
var distant = responses.some(function(v, i) {
|
|
var d = Math.min(Math.abs(i - best), 32 - Math.abs(i - best));
|
|
return v >= peak * .65 && d > 6;
|
|
});
|
|
if (distant) continue;
|
|
var sum = 0,
|
|
weighted = 0;
|
|
for (var offset = -3; offset <= 3; offset++) {
|
|
var index = (best + offset + 32) % 32,
|
|
weight = Math.max(0, responses[index] - peak * .35);
|
|
weighted += offset * weight;
|
|
sum += weight;
|
|
}
|
|
var position = ((best + .5 + weighted / sum) / 8 + 4) % 4;
|
|
candidates.push({
|
|
position: position,
|
|
region: region,
|
|
score: peak / strong
|
|
});
|
|
}
|
|
// Nested LG regions can measure the same edge position. Keep the most
|
|
// localized response rather than making duplicate interpolation anchors.
|
|
candidates.sort(function(a, b) {
|
|
return b.score - a.score || a.region - b.region;
|
|
});
|
|
var points = [];
|
|
candidates.forEach(function(p) {
|
|
if (points.some(function(q) {
|
|
var d = Math.abs(p.position - q.position);
|
|
return Math.min(d, 4 - d) < .06;
|
|
})) return;
|
|
points.push({
|
|
position: p.position,
|
|
region: p.region
|
|
});
|
|
edges[Math.floor(p.position)]++;
|
|
});
|
|
points.sort(function(a, b) {
|
|
return a.position - b.position;
|
|
});
|
|
if (points.length < 4 || edges.some(function(n) {
|
|
return !n;
|
|
})) throw new Error('No reliable spatial response on every edge. Keep the video full-screen; this TV may not expose local histogram regions. Your saved profile is unchanged.');
|
|
return {
|
|
model: model,
|
|
points: points
|
|
};
|
|
}
|
|
|
|
function calibrationLuma(regions) {
|
|
return regions.map(function(c) {
|
|
return (c[0] + c[1] + c[2]) / 3;
|
|
});
|
|
}
|
|
|
|
function calibrationAverage(samples) {
|
|
if (samples.length < 3) throw new Error('Not enough fresh histogram samples. Keep the app visible and repeat calibration.');
|
|
return samples[0].map(function(_, r) {
|
|
return samples.reduce(function(sum, s) {
|
|
return sum + s[r];
|
|
}, 0) / samples.length;
|
|
});
|
|
}
|