@pilio/gemini-watermark-remover 1.0.39 → 1.0.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -15
- package/dist/video-app.js +1 -1
- package/package.json +1 -1
- package/src/cli/gwrCli.js +17 -17
- package/src/cli/gwrRemoveCommand.js +433 -433
- package/src/core/candidateEvaluation.js +137 -137
- package/src/core/pipelineCandidateQuality.js +823 -823
- package/src/core/pipelineInitialSelection.js +137 -59
- package/src/core/watermarkProcessor.js +22 -22
- package/src/sdk/node.d.ts +56 -56
- package/src/sdk/video.d.ts +23 -23
- package/src/sdk/video.js +19 -19
- package/src/sdk/videoProgress.js +108 -108
|
@@ -1,823 +1,823 @@
|
|
|
1
|
-
import {
|
|
2
|
-
assessCalibratedWatermarkResidualVisibility,
|
|
3
|
-
assessReferenceTextureAlignment,
|
|
4
|
-
assessRemovalDiffArtifacts,
|
|
5
|
-
calculateNearBlackRatio,
|
|
6
|
-
calculateNearWhiteRatio,
|
|
7
|
-
scoreRegion
|
|
8
|
-
} from './restorationMetrics.js';
|
|
9
|
-
import { getEmbeddedAlphaMap } from './embeddedAlphaMaps.js';
|
|
10
|
-
import {
|
|
11
|
-
buildPreviewNeighborhoodPrior,
|
|
12
|
-
measurePreviewBoundaryMetrics
|
|
13
|
-
} from './previewAlphaCalibration.js';
|
|
14
|
-
import { compareRankingKey } from './watermarkScoring.js';
|
|
15
|
-
import { shouldPreferFullStrengthNewMarginVariant } from './candidateEvaluation.js';
|
|
16
|
-
|
|
17
|
-
export const FINAL_EVIDENCE_WEIGHT = 0.35;
|
|
18
|
-
export const FINAL_RESIDUAL_WEIGHT = 0.40;
|
|
19
|
-
export const FINAL_DAMAGE_WEIGHT = 0.25;
|
|
20
|
-
export const FINAL_DAMAGE_WARNING_PENALTY = 0.08;
|
|
21
|
-
|
|
22
|
-
const DISCOVERY_ROLE_PENALTIES = {
|
|
23
|
-
'fixed-selected': 0,
|
|
24
|
-
'automatic-selected': 0.03,
|
|
25
|
-
'aggressive-fallback-alternative': 0.015,
|
|
26
|
-
'conservative-derived': 0.15,
|
|
27
|
-
'discovered-alternative': 0.25
|
|
28
|
-
};
|
|
29
|
-
// Keep these reporting thresholds aligned with assessWatermarkResidualVisibility.
|
|
30
|
-
const IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD = 6;
|
|
31
|
-
const IMPERFECTION_GRADIENT_THRESHOLD = 0.22;
|
|
32
|
-
const IMPERFECTION_SPATIAL_THRESHOLD = 0.18;
|
|
33
|
-
const IMPERFECTION_WARNING_RATIO = 0.5;
|
|
34
|
-
const DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL = 0.18;
|
|
35
|
-
const DARK_SUPPORT_RESOLUTION_MAX_GRADIENT = 0.18;
|
|
36
|
-
const DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO = 0.25;
|
|
37
|
-
const SEVERE_DARK_FLAT_TEXTURE_WARNING_THRESHOLD = 1.5;
|
|
38
|
-
const INDEPENDENT_DARK_SUPPORT_SIZE = 96;
|
|
39
|
-
const INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD = 12;
|
|
40
|
-
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_SPATIAL = 0.95;
|
|
41
|
-
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_GRADIENT = 0.9;
|
|
42
|
-
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_NEAR_BLACK = 0.1;
|
|
43
|
-
const INDEPENDENT_DARK_SUPPORT_MIN_CANDIDATE_NEAR_BLACK = 0.25;
|
|
44
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_OPPOSITE_HALO_LUMINANCE = 1;
|
|
45
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_OUTSIDE_ALPHA_CLIP_RATIO = 0.03;
|
|
46
|
-
const INDEPENDENT_DARK_SUPPORT_MIN_STRONG_ALPHA_CLIP_RATIO = 0.8;
|
|
47
|
-
const INDEPENDENT_DARK_SUPPORT_ALPHA_MIN = 0.05;
|
|
48
|
-
const INDEPENDENT_DARK_SUPPORT_ALPHA_STRONG_MIN = 0.2;
|
|
49
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_LUMINANCE = 8;
|
|
50
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_CANDIDATE_MEAN_LUMINANCE = 8;
|
|
51
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_DELTA = 1.5;
|
|
52
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MAE = 2.25;
|
|
53
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_WEIGHTED_MAE = 3.5;
|
|
54
|
-
const INDEPENDENT_DARK_SUPPORT_MAX_BOUNDARY_SCORE = 0.35;
|
|
55
|
-
const INDEPENDENT_DARK_SUPPORT_PRIOR_RADII = Object.freeze([4, 12]);
|
|
56
|
-
const INDEPENDENT_DARK_SUPPORT_RELAXATION_PASSES = 0;
|
|
57
|
-
|
|
58
|
-
function clamp01(value) {
|
|
59
|
-
if (!Number.isFinite(value)) return 0;
|
|
60
|
-
return Math.max(0, Math.min(1, value));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function haveEqualAlphaValues(left, right) {
|
|
64
|
-
if (!left || !right || left.length !== right.length) return false;
|
|
65
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
66
|
-
if (left[index] !== right[index]) return false;
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function resolveReferenceAlphaMap(
|
|
72
|
-
hypothesis,
|
|
73
|
-
explicitReferenceAlphaMap,
|
|
74
|
-
selectedAlphaMap
|
|
75
|
-
) {
|
|
76
|
-
const config = hypothesis?.config ?? hypothesis?.trial?.config;
|
|
77
|
-
const logoSize = config?.logoSize;
|
|
78
|
-
const alphaVariant = config?.alphaVariant;
|
|
79
|
-
const catalogKey = Number.isFinite(logoSize)
|
|
80
|
-
? typeof alphaVariant === 'string' && alphaVariant.length > 0
|
|
81
|
-
? `${logoSize}-${alphaVariant}`
|
|
82
|
-
: logoSize
|
|
83
|
-
: null;
|
|
84
|
-
// Keep one catalog-derived observer independent from any alpha profile
|
|
85
|
-
// mutated or injected by the candidate being evaluated.
|
|
86
|
-
const referenceAlphaMap = explicitReferenceAlphaMap ??
|
|
87
|
-
(catalogKey === null ? null : getEmbeddedAlphaMap(catalogKey));
|
|
88
|
-
return haveEqualAlphaValues(referenceAlphaMap, selectedAlphaMap)
|
|
89
|
-
? null
|
|
90
|
-
: referenceAlphaMap;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function extractCandidateQualityScratch(imageData, position) {
|
|
94
|
-
const x = Math.max(0, position.x - INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD);
|
|
95
|
-
const y = Math.max(0, position.y - INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD);
|
|
96
|
-
const right = Math.min(
|
|
97
|
-
imageData.width,
|
|
98
|
-
position.x + position.width + INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD
|
|
99
|
-
);
|
|
100
|
-
const bottom = Math.min(
|
|
101
|
-
imageData.height,
|
|
102
|
-
position.y + position.height + INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD
|
|
103
|
-
);
|
|
104
|
-
const scratch = {
|
|
105
|
-
width: right - x,
|
|
106
|
-
height: bottom - y,
|
|
107
|
-
data: new Uint8ClampedArray((right - x) * (bottom - y) * 4)
|
|
108
|
-
};
|
|
109
|
-
for (let row = 0; row < scratch.height; row++) {
|
|
110
|
-
const sourceStart = ((y + row) * imageData.width + x) * 4;
|
|
111
|
-
const sourceEnd = sourceStart + scratch.width * 4;
|
|
112
|
-
scratch.data.set(
|
|
113
|
-
imageData.data.subarray(sourceStart, sourceEnd),
|
|
114
|
-
row * scratch.width * 4
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
imageData: scratch,
|
|
119
|
-
position: {
|
|
120
|
-
x: position.x - x,
|
|
121
|
-
y: position.y - y,
|
|
122
|
-
width: position.width,
|
|
123
|
-
height: position.height
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function measureIndependentDarkSupportClipping({
|
|
129
|
-
originalImageData,
|
|
130
|
-
candidateImageData,
|
|
131
|
-
alphaMap,
|
|
132
|
-
position
|
|
133
|
-
}) {
|
|
134
|
-
let newlyClippedCount = 0;
|
|
135
|
-
let outsideAlphaSupportCount = 0;
|
|
136
|
-
let strongAlphaSupportCount = 0;
|
|
137
|
-
for (let row = 0; row < position.height; row++) {
|
|
138
|
-
for (let col = 0; col < position.width; col++) {
|
|
139
|
-
const localIndex = row * position.width + col;
|
|
140
|
-
const pixelIndex = (
|
|
141
|
-
(position.y + row) * originalImageData.width +
|
|
142
|
-
position.x +
|
|
143
|
-
col
|
|
144
|
-
) * 4;
|
|
145
|
-
const newlyClipped = [0, 1, 2].some((channel) => (
|
|
146
|
-
candidateImageData.data[pixelIndex + channel] === 0 &&
|
|
147
|
-
originalImageData.data[pixelIndex + channel] > 5
|
|
148
|
-
));
|
|
149
|
-
if (!newlyClipped) continue;
|
|
150
|
-
|
|
151
|
-
newlyClippedCount++;
|
|
152
|
-
const alpha = Math.abs(alphaMap[localIndex]);
|
|
153
|
-
if (alpha < INDEPENDENT_DARK_SUPPORT_ALPHA_MIN) {
|
|
154
|
-
outsideAlphaSupportCount++;
|
|
155
|
-
}
|
|
156
|
-
if (alpha >= INDEPENDENT_DARK_SUPPORT_ALPHA_STRONG_MIN) {
|
|
157
|
-
strongAlphaSupportCount++;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
outsideAlphaSupportRatio: newlyClippedCount > 0
|
|
163
|
-
? outsideAlphaSupportCount / newlyClippedCount
|
|
164
|
-
: 0,
|
|
165
|
-
strongAlphaSupportRatio: newlyClippedCount > 0
|
|
166
|
-
? strongAlphaSupportCount / newlyClippedCount
|
|
167
|
-
: 1
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function measureIndependentDarkSupportPrior({
|
|
172
|
-
candidateImageData,
|
|
173
|
-
priorImageData,
|
|
174
|
-
alphaMap,
|
|
175
|
-
position
|
|
176
|
-
}) {
|
|
177
|
-
let absoluteDelta = 0;
|
|
178
|
-
let weightedAbsoluteDelta = 0;
|
|
179
|
-
let alphaWeight = 0;
|
|
180
|
-
let candidateLuminance = 0;
|
|
181
|
-
let priorLuminance = 0;
|
|
182
|
-
let channelCount = 0;
|
|
183
|
-
let pixelCount = 0;
|
|
184
|
-
for (let row = 0; row < position.height; row++) {
|
|
185
|
-
for (let col = 0; col < position.width; col++) {
|
|
186
|
-
const localIndex = row * position.width + col;
|
|
187
|
-
const alpha = Math.abs(alphaMap[localIndex]);
|
|
188
|
-
const pixelIndex = (
|
|
189
|
-
(position.y + row) * candidateImageData.width +
|
|
190
|
-
position.x +
|
|
191
|
-
col
|
|
192
|
-
) * 4;
|
|
193
|
-
for (let channel = 0; channel < 3; channel++) {
|
|
194
|
-
const delta = Math.abs(
|
|
195
|
-
candidateImageData.data[pixelIndex + channel] -
|
|
196
|
-
priorImageData.data[pixelIndex + channel]
|
|
197
|
-
);
|
|
198
|
-
absoluteDelta += delta;
|
|
199
|
-
weightedAbsoluteDelta += delta * alpha;
|
|
200
|
-
channelCount++;
|
|
201
|
-
}
|
|
202
|
-
candidateLuminance +=
|
|
203
|
-
0.2126 * candidateImageData.data[pixelIndex] +
|
|
204
|
-
0.7152 * candidateImageData.data[pixelIndex + 1] +
|
|
205
|
-
0.0722 * candidateImageData.data[pixelIndex + 2];
|
|
206
|
-
priorLuminance +=
|
|
207
|
-
0.2126 * priorImageData.data[pixelIndex] +
|
|
208
|
-
0.7152 * priorImageData.data[pixelIndex + 1] +
|
|
209
|
-
0.0722 * priorImageData.data[pixelIndex + 2];
|
|
210
|
-
alphaWeight += alpha;
|
|
211
|
-
pixelCount++;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const candidateMeanLuminance = candidateLuminance / Math.max(1, pixelCount);
|
|
215
|
-
const priorMeanLuminance = priorLuminance / Math.max(1, pixelCount);
|
|
216
|
-
return {
|
|
217
|
-
meanAbsoluteDelta: absoluteDelta / Math.max(1, channelCount),
|
|
218
|
-
weightedMeanAbsoluteDelta:
|
|
219
|
-
weightedAbsoluteDelta / Math.max(1e-8, alphaWeight * 3),
|
|
220
|
-
candidateMeanLuminance,
|
|
221
|
-
priorMeanLuminance,
|
|
222
|
-
meanLuminanceDelta: Math.abs(candidateMeanLuminance - priorMeanLuminance)
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function assessIndependentDarkBackgroundSupportConvergence({
|
|
227
|
-
originalImageData,
|
|
228
|
-
candidateImageData,
|
|
229
|
-
hypothesis,
|
|
230
|
-
position,
|
|
231
|
-
alphaMap,
|
|
232
|
-
alphaGain,
|
|
233
|
-
original,
|
|
234
|
-
final,
|
|
235
|
-
visibility,
|
|
236
|
-
artifacts,
|
|
237
|
-
originalNearBlackRatio,
|
|
238
|
-
candidateNearBlackRatio,
|
|
239
|
-
rawDamageWarning
|
|
240
|
-
}) {
|
|
241
|
-
const trial = hypothesis?.trial;
|
|
242
|
-
const source = trial?.source;
|
|
243
|
-
const catalogAlphaMap = getEmbeddedAlphaMap('96-20260520');
|
|
244
|
-
if (
|
|
245
|
-
rawDamageWarning !== true ||
|
|
246
|
-
source !== 'standard' ||
|
|
247
|
-
position.width !== INDEPENDENT_DARK_SUPPORT_SIZE ||
|
|
248
|
-
position.height !== INDEPENDENT_DARK_SUPPORT_SIZE ||
|
|
249
|
-
alphaGain !== 1 ||
|
|
250
|
-
!haveEqualAlphaValues(alphaMap, catalogAlphaMap) ||
|
|
251
|
-
original.spatialScore < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_SPATIAL ||
|
|
252
|
-
original.gradientScore < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_GRADIENT ||
|
|
253
|
-
visibility?.visible !== false ||
|
|
254
|
-
Math.abs(final.spatialScore) > DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL ||
|
|
255
|
-
final.gradientScore > DARK_SUPPORT_RESOLUTION_MAX_GRADIENT ||
|
|
256
|
-
originalNearBlackRatio < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_NEAR_BLACK ||
|
|
257
|
-
candidateNearBlackRatio < INDEPENDENT_DARK_SUPPORT_MIN_CANDIDATE_NEAR_BLACK ||
|
|
258
|
-
Number(artifacts?.newlyClippedRatio ?? Infinity) >
|
|
259
|
-
DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO ||
|
|
260
|
-
Number(artifacts?.oppositeDirectionHaloLum ?? Infinity) >
|
|
261
|
-
INDEPENDENT_DARK_SUPPORT_MAX_OPPOSITE_HALO_LUMINANCE
|
|
262
|
-
) {
|
|
263
|
-
return null;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const originalScratch = extractCandidateQualityScratch(
|
|
267
|
-
originalImageData,
|
|
268
|
-
position
|
|
269
|
-
);
|
|
270
|
-
const candidateScratch = extractCandidateQualityScratch(
|
|
271
|
-
candidateImageData,
|
|
272
|
-
position
|
|
273
|
-
);
|
|
274
|
-
const priorMetrics = INDEPENDENT_DARK_SUPPORT_PRIOR_RADII.map((radius) => {
|
|
275
|
-
const priorImageData = buildPreviewNeighborhoodPrior({
|
|
276
|
-
previewImageData: originalScratch.imageData,
|
|
277
|
-
position: originalScratch.position,
|
|
278
|
-
radius,
|
|
279
|
-
relaxationPasses: INDEPENDENT_DARK_SUPPORT_RELAXATION_PASSES
|
|
280
|
-
});
|
|
281
|
-
return {
|
|
282
|
-
radius,
|
|
283
|
-
...measureIndependentDarkSupportPrior({
|
|
284
|
-
candidateImageData: candidateScratch.imageData,
|
|
285
|
-
priorImageData,
|
|
286
|
-
alphaMap,
|
|
287
|
-
position: candidateScratch.position
|
|
288
|
-
})
|
|
289
|
-
};
|
|
290
|
-
});
|
|
291
|
-
const clipping = measureIndependentDarkSupportClipping({
|
|
292
|
-
originalImageData: originalScratch.imageData,
|
|
293
|
-
candidateImageData: candidateScratch.imageData,
|
|
294
|
-
alphaMap,
|
|
295
|
-
position: originalScratch.position
|
|
296
|
-
});
|
|
297
|
-
const boundary = measurePreviewBoundaryMetrics(
|
|
298
|
-
candidateScratch.imageData,
|
|
299
|
-
originalScratch.imageData,
|
|
300
|
-
originalScratch.position
|
|
301
|
-
);
|
|
302
|
-
const maximumPriorMeanLuminance = Math.max(
|
|
303
|
-
...priorMetrics.map((item) => item.priorMeanLuminance)
|
|
304
|
-
);
|
|
305
|
-
const maximumCandidateMeanLuminance = Math.max(
|
|
306
|
-
...priorMetrics.map((item) => item.candidateMeanLuminance)
|
|
307
|
-
);
|
|
308
|
-
const maximumPriorMeanDelta = Math.max(
|
|
309
|
-
...priorMetrics.map((item) => item.meanLuminanceDelta)
|
|
310
|
-
);
|
|
311
|
-
const maximumPriorMae = Math.max(
|
|
312
|
-
...priorMetrics.map((item) => item.meanAbsoluteDelta)
|
|
313
|
-
);
|
|
314
|
-
const maximumPriorWeightedMae = Math.max(
|
|
315
|
-
...priorMetrics.map((item) => item.weightedMeanAbsoluteDelta)
|
|
316
|
-
);
|
|
317
|
-
const accepted = Boolean(
|
|
318
|
-
clipping.outsideAlphaSupportRatio <=
|
|
319
|
-
INDEPENDENT_DARK_SUPPORT_MAX_OUTSIDE_ALPHA_CLIP_RATIO &&
|
|
320
|
-
clipping.strongAlphaSupportRatio >=
|
|
321
|
-
INDEPENDENT_DARK_SUPPORT_MIN_STRONG_ALPHA_CLIP_RATIO &&
|
|
322
|
-
maximumPriorMeanLuminance <=
|
|
323
|
-
INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_LUMINANCE &&
|
|
324
|
-
maximumCandidateMeanLuminance <=
|
|
325
|
-
INDEPENDENT_DARK_SUPPORT_MAX_CANDIDATE_MEAN_LUMINANCE &&
|
|
326
|
-
maximumPriorMeanDelta <= INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_DELTA &&
|
|
327
|
-
maximumPriorMae <= INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MAE &&
|
|
328
|
-
maximumPriorWeightedMae <=
|
|
329
|
-
INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_WEIGHTED_MAE &&
|
|
330
|
-
boundary.normalizedScore <= INDEPENDENT_DARK_SUPPORT_MAX_BOUNDARY_SCORE
|
|
331
|
-
);
|
|
332
|
-
return {
|
|
333
|
-
accepted,
|
|
334
|
-
source: 'independent-standard-96-neighborhood-prior',
|
|
335
|
-
textureHardRejectResolved: accepted,
|
|
336
|
-
priorRadii: [...INDEPENDENT_DARK_SUPPORT_PRIOR_RADII],
|
|
337
|
-
maximumPriorMeanLuminance,
|
|
338
|
-
maximumCandidateMeanLuminance,
|
|
339
|
-
maximumPriorMeanDelta,
|
|
340
|
-
maximumPriorMae,
|
|
341
|
-
maximumPriorWeightedMae,
|
|
342
|
-
boundaryScore: boundary.normalizedScore,
|
|
343
|
-
clipping
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
export function classifyCandidateQuality({
|
|
348
|
-
residualVisible = false,
|
|
349
|
-
damageWarning = false
|
|
350
|
-
} = {}) {
|
|
351
|
-
if (residualVisible && damageWarning) return 'mixed';
|
|
352
|
-
if (residualVisible) return 'visible-residual';
|
|
353
|
-
if (damageWarning) return 'possible-content-damage';
|
|
354
|
-
return 'clean';
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export function createCandidateImperfectionSignals(visibility = {}) {
|
|
358
|
-
const darkPolarityHaloThreshold = Number.isFinite(visibility.darkPolarityHaloThreshold)
|
|
359
|
-
? visibility.darkPolarityHaloThreshold
|
|
360
|
-
: IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD;
|
|
361
|
-
const definitions = [
|
|
362
|
-
['spatial-residual', visibility.spatialResidual, IMPERFECTION_SPATIAL_THRESHOLD],
|
|
363
|
-
['gradient-residual', visibility.gradientResidual, IMPERFECTION_GRADIENT_THRESHOLD],
|
|
364
|
-
['positive-halo', visibility.positiveHaloLum, IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD],
|
|
365
|
-
['dark-polarity-halo', visibility.darkPolarityHaloLum, darkPolarityHaloThreshold]
|
|
366
|
-
];
|
|
367
|
-
const components = Object.fromEntries(definitions.map(([type, rawValue, threshold]) => {
|
|
368
|
-
const value = Number.isFinite(rawValue) ? Math.max(0, rawValue) : 0;
|
|
369
|
-
return [type, {
|
|
370
|
-
value,
|
|
371
|
-
threshold,
|
|
372
|
-
ratio: value / threshold
|
|
373
|
-
}];
|
|
374
|
-
}));
|
|
375
|
-
const score = Math.max(0, ...Object.values(components).map((component) => component.ratio));
|
|
376
|
-
const severity = score >= 1
|
|
377
|
-
? 'high'
|
|
378
|
-
: score >= IMPERFECTION_WARNING_RATIO
|
|
379
|
-
? 'moderate'
|
|
380
|
-
: score > 0
|
|
381
|
-
? 'low'
|
|
382
|
-
: 'none';
|
|
383
|
-
const types = definitions
|
|
384
|
-
.map(([type]) => type)
|
|
385
|
-
.filter((type) => components[type].ratio >= IMPERFECTION_WARNING_RATIO);
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
detected: score >= IMPERFECTION_WARNING_RATIO,
|
|
389
|
-
severity,
|
|
390
|
-
score,
|
|
391
|
-
types,
|
|
392
|
-
components
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export function createCandidateQualitySignals({
|
|
397
|
-
originalImageData,
|
|
398
|
-
candidateImageData,
|
|
399
|
-
hypothesis,
|
|
400
|
-
finalCandidate = null,
|
|
401
|
-
referenceAlphaMap = null
|
|
402
|
-
} = {}) {
|
|
403
|
-
const trial = hypothesis?.trial;
|
|
404
|
-
const position = finalCandidate?.position ?? trial?.position ?? hypothesis?.position;
|
|
405
|
-
const alphaMap = finalCandidate?.alphaMap ?? trial?.alphaMap;
|
|
406
|
-
const alphaGain = finalCandidate?.alphaGain ?? trial?.alphaGain ?? 1;
|
|
407
|
-
if (!originalImageData || !candidateImageData || !position || !alphaMap) {
|
|
408
|
-
throw new Error('Candidate quality measurement requires original pixels, candidate pixels, position, and alpha map');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const original = scoreRegion(originalImageData, alphaMap, position);
|
|
412
|
-
const final = scoreRegion(candidateImageData, alphaMap, position);
|
|
413
|
-
const visibility = assessCalibratedWatermarkResidualVisibility({
|
|
414
|
-
imageData: candidateImageData,
|
|
415
|
-
originalImageData,
|
|
416
|
-
position,
|
|
417
|
-
alphaMap,
|
|
418
|
-
alphaGain
|
|
419
|
-
});
|
|
420
|
-
const resolvedReferenceAlphaMap = resolveReferenceAlphaMap(
|
|
421
|
-
hypothesis,
|
|
422
|
-
referenceAlphaMap,
|
|
423
|
-
alphaMap
|
|
424
|
-
);
|
|
425
|
-
const referenceVisibility = resolvedReferenceAlphaMap
|
|
426
|
-
? assessCalibratedWatermarkResidualVisibility({
|
|
427
|
-
imageData: candidateImageData,
|
|
428
|
-
originalImageData,
|
|
429
|
-
position,
|
|
430
|
-
alphaMap: resolvedReferenceAlphaMap,
|
|
431
|
-
alphaGain
|
|
432
|
-
})
|
|
433
|
-
: null;
|
|
434
|
-
const imperfections = createCandidateImperfectionSignals(visibility);
|
|
435
|
-
const artifacts = assessRemovalDiffArtifacts({
|
|
436
|
-
originalImageData,
|
|
437
|
-
candidateImageData,
|
|
438
|
-
alphaMap,
|
|
439
|
-
position,
|
|
440
|
-
alphaGain
|
|
441
|
-
});
|
|
442
|
-
const texture = assessReferenceTextureAlignment({
|
|
443
|
-
originalImageData,
|
|
444
|
-
referenceImageData: originalImageData,
|
|
445
|
-
candidateImageData,
|
|
446
|
-
position
|
|
447
|
-
});
|
|
448
|
-
const originalNearBlackRatio = calculateNearBlackRatio(originalImageData, position);
|
|
449
|
-
const candidateNearBlackRatio = calculateNearBlackRatio(candidateImageData, position);
|
|
450
|
-
const originalNearWhiteRatio = calculateNearWhiteRatio(originalImageData, position);
|
|
451
|
-
const candidateNearWhiteRatio = calculateNearWhiteRatio(candidateImageData, position);
|
|
452
|
-
const nearBlackIncrease = candidateNearBlackRatio - originalNearBlackRatio;
|
|
453
|
-
const nearWhiteIncrease = candidateNearWhiteRatio - originalNearWhiteRatio;
|
|
454
|
-
|
|
455
|
-
const evidence = clamp01((
|
|
456
|
-
Math.max(0, original.spatialScore) +
|
|
457
|
-
Math.max(0, original.gradientScore)
|
|
458
|
-
) / 2);
|
|
459
|
-
const residualComponents = {
|
|
460
|
-
spatial: clamp01(Math.abs(final.spatialScore) / 0.4),
|
|
461
|
-
gradient: clamp01(Math.max(0, final.gradientScore) / 0.35),
|
|
462
|
-
halo: clamp01(Math.max(
|
|
463
|
-
visibility?.positiveHaloLum ?? 0,
|
|
464
|
-
visibility?.darkPolarityHaloLum ?? 0
|
|
465
|
-
) / 8)
|
|
466
|
-
};
|
|
467
|
-
const residualLoss =
|
|
468
|
-
residualComponents.spatial * 0.45 +
|
|
469
|
-
residualComponents.gradient * 0.35 +
|
|
470
|
-
residualComponents.halo * 0.20;
|
|
471
|
-
const damageComponents = {
|
|
472
|
-
nearBlack: clamp01(Math.max(0, nearBlackIncrease) / 0.05),
|
|
473
|
-
nearWhite: clamp01(Math.max(0, nearWhiteIncrease) / 0.05),
|
|
474
|
-
texture: clamp01((texture?.texturePenalty ?? 0) / 1),
|
|
475
|
-
clipped: clamp01((artifacts?.newlyClippedRatio ?? 0) / 0.02)
|
|
476
|
-
};
|
|
477
|
-
const rawDamageLoss =
|
|
478
|
-
damageComponents.nearBlack * 0.25 +
|
|
479
|
-
damageComponents.nearWhite * 0.25 +
|
|
480
|
-
damageComponents.texture * 0.25 +
|
|
481
|
-
damageComponents.clipped * 0.25;
|
|
482
|
-
const residualVisible = visibility?.visible === true ||
|
|
483
|
-
referenceVisibility?.visible === true;
|
|
484
|
-
const textureWarningCorroborated = (
|
|
485
|
-
texture?.visibleDarkHole === true ||
|
|
486
|
-
texture?.hardReject === true
|
|
487
|
-
) && (
|
|
488
|
-
damageComponents.nearBlack >= 0.4 ||
|
|
489
|
-
damageComponents.nearWhite >= 0.4 ||
|
|
490
|
-
damageComponents.clipped >= 0.4
|
|
491
|
-
);
|
|
492
|
-
const severeDarkFlatTextureWarning =
|
|
493
|
-
texture?.tooDark === true &&
|
|
494
|
-
texture?.tooFlat === true &&
|
|
495
|
-
Number(texture?.texturePenalty ?? 0) >=
|
|
496
|
-
SEVERE_DARK_FLAT_TEXTURE_WARNING_THRESHOLD;
|
|
497
|
-
const rawDamageWarning = damageComponents.nearBlack >= 1 ||
|
|
498
|
-
damageComponents.nearWhite >= 1 ||
|
|
499
|
-
damageComponents.clipped >= 1 ||
|
|
500
|
-
textureWarningCorroborated ||
|
|
501
|
-
severeDarkFlatTextureWarning;
|
|
502
|
-
const explicitDarkBackgroundSupportConvergence =
|
|
503
|
-
finalCandidate?.darkBackgroundSupportConvergence?.accepted === true
|
|
504
|
-
? finalCandidate.darkBackgroundSupportConvergence
|
|
505
|
-
: null;
|
|
506
|
-
const independentDarkBackgroundSupportConvergence =
|
|
507
|
-
explicitDarkBackgroundSupportConvergence
|
|
508
|
-
? null
|
|
509
|
-
: assessIndependentDarkBackgroundSupportConvergence({
|
|
510
|
-
originalImageData,
|
|
511
|
-
candidateImageData,
|
|
512
|
-
hypothesis,
|
|
513
|
-
position,
|
|
514
|
-
alphaMap,
|
|
515
|
-
alphaGain,
|
|
516
|
-
original,
|
|
517
|
-
final,
|
|
518
|
-
visibility,
|
|
519
|
-
artifacts,
|
|
520
|
-
originalNearBlackRatio,
|
|
521
|
-
candidateNearBlackRatio,
|
|
522
|
-
rawDamageWarning
|
|
523
|
-
});
|
|
524
|
-
const darkBackgroundSupportConvergence =
|
|
525
|
-
explicitDarkBackgroundSupportConvergence ??
|
|
526
|
-
independentDarkBackgroundSupportConvergence;
|
|
527
|
-
const textureHardRejectResolved =
|
|
528
|
-
darkBackgroundSupportConvergence?.textureHardRejectResolved === true;
|
|
529
|
-
const darkBackgroundSupportResolved = Boolean(
|
|
530
|
-
rawDamageWarning &&
|
|
531
|
-
darkBackgroundSupportConvergence &&
|
|
532
|
-
!residualVisible &&
|
|
533
|
-
(
|
|
534
|
-
texture?.hardReject !== true ||
|
|
535
|
-
textureHardRejectResolved
|
|
536
|
-
) &&
|
|
537
|
-
Math.abs(final.spatialScore) <=
|
|
538
|
-
DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL &&
|
|
539
|
-
final.gradientScore <= DARK_SUPPORT_RESOLUTION_MAX_GRADIENT &&
|
|
540
|
-
Number(artifacts?.newlyClippedRatio ?? Infinity) <=
|
|
541
|
-
DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO
|
|
542
|
-
);
|
|
543
|
-
const damageWarning = rawDamageWarning &&
|
|
544
|
-
!darkBackgroundSupportResolved;
|
|
545
|
-
const damageLoss = darkBackgroundSupportResolved ? 0 : rawDamageLoss;
|
|
546
|
-
const damageRiskResolution = darkBackgroundSupportResolved
|
|
547
|
-
? 'dark-background-support-converged'
|
|
548
|
-
: null;
|
|
549
|
-
const qualityStatus = classifyCandidateQuality({
|
|
550
|
-
residualVisible,
|
|
551
|
-
damageWarning
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
return {
|
|
555
|
-
evidenceLoss: 1 - evidence,
|
|
556
|
-
residualLoss,
|
|
557
|
-
damageLoss,
|
|
558
|
-
rawDamageLoss,
|
|
559
|
-
residualVisible,
|
|
560
|
-
damageWarning,
|
|
561
|
-
rawDamageWarning,
|
|
562
|
-
damageRiskResolution,
|
|
563
|
-
darkBackgroundSupportConvergence,
|
|
564
|
-
qualityStatus,
|
|
565
|
-
imperfections,
|
|
566
|
-
original,
|
|
567
|
-
final,
|
|
568
|
-
visibility,
|
|
569
|
-
referenceVisibility,
|
|
570
|
-
artifacts,
|
|
571
|
-
texture,
|
|
572
|
-
originalNearBlackRatio,
|
|
573
|
-
candidateNearBlackRatio,
|
|
574
|
-
nearBlackIncrease,
|
|
575
|
-
originalNearWhiteRatio,
|
|
576
|
-
candidateNearWhiteRatio,
|
|
577
|
-
nearWhiteIncrease,
|
|
578
|
-
residualComponents,
|
|
579
|
-
damageComponents
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
function getFinalScore(signals = {}) {
|
|
584
|
-
const weightedLoss = (signals.evidenceLoss ?? 1) * FINAL_EVIDENCE_WEIGHT +
|
|
585
|
-
(signals.residualLoss ?? 1) * FINAL_RESIDUAL_WEIGHT +
|
|
586
|
-
(signals.damageLoss ?? 1) * FINAL_DAMAGE_WEIGHT;
|
|
587
|
-
return weightedLoss + (signals.damageWarning === true ? FINAL_DAMAGE_WARNING_PENALTY : 0);
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
function getDiscoveryPenalty(hypothesis = {}) {
|
|
591
|
-
return DISCOVERY_ROLE_PENALTIES[hypothesis.discoveryRole] ?? 0;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
function dominates(left, right) {
|
|
595
|
-
const keys = ['evidenceLoss', 'residualLoss', 'damageLoss'];
|
|
596
|
-
const noWorse = keys.every((key) => (
|
|
597
|
-
(left.qualitySignals?.[key] ?? Infinity) <= (right.qualitySignals?.[key] ?? Infinity)
|
|
598
|
-
)) && left.discoveryPenalty <= right.discoveryPenalty;
|
|
599
|
-
const strictlyBetter = keys.some((key) => (
|
|
600
|
-
(left.qualitySignals?.[key] ?? Infinity) < (right.qualitySignals?.[key] ?? Infinity)
|
|
601
|
-
)) || left.discoveryPenalty < right.discoveryPenalty;
|
|
602
|
-
return noWorse && strictlyBetter;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
function hasCatastrophicBlock(signals = {}) {
|
|
606
|
-
if (
|
|
607
|
-
signals.damageWarning === false &&
|
|
608
|
-
signals.darkBackgroundSupportConvergence?.accepted === true
|
|
609
|
-
) {
|
|
610
|
-
return false;
|
|
611
|
-
}
|
|
612
|
-
const damage = signals.damageComponents ?? {};
|
|
613
|
-
return signals.texture?.hardReject === true &&
|
|
614
|
-
damage.clipped >= 1 &&
|
|
615
|
-
(damage.nearBlack >= 1 || damage.nearWhite >= 1);
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
function getCandidatePosition(candidate = {}) {
|
|
619
|
-
return candidate.hypothesis?.trial?.position ?? candidate.hypothesis?.position ?? null;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
function hasSameCandidateAnchor(left, right) {
|
|
623
|
-
const leftPosition = getCandidatePosition(left);
|
|
624
|
-
const rightPosition = getCandidatePosition(right);
|
|
625
|
-
if (!leftPosition || !rightPosition) return false;
|
|
626
|
-
return leftPosition.x === rightPosition.x &&
|
|
627
|
-
leftPosition.y === rightPosition.y &&
|
|
628
|
-
leftPosition.width === rightPosition.width &&
|
|
629
|
-
leftPosition.height === rightPosition.height;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const SAME_ANCHOR_96_SIZE = 96;
|
|
633
|
-
const SAME_ANCHOR_96_IMPERFECTION_IMPROVEMENT = 0.15;
|
|
634
|
-
const SAME_ANCHOR_96_EVIDENCE_TOLERANCE = 0.05;
|
|
635
|
-
const SAME_ANCHOR_96_DAMAGE_TOLERANCE = 0.05;
|
|
636
|
-
|
|
637
|
-
function finiteOr(value, fallback = Infinity) {
|
|
638
|
-
return Number.isFinite(value) ? value : fallback;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function isEligibleSameAnchor96Alternative(incumbent, alternative) {
|
|
642
|
-
const incumbentSignals = incumbent?.qualitySignals ?? {};
|
|
643
|
-
const alternativeSignals = alternative?.qualitySignals ?? {};
|
|
644
|
-
const incumbentScore = incumbentSignals.imperfections?.score;
|
|
645
|
-
const alternativeScore = alternativeSignals.imperfections?.score;
|
|
646
|
-
return hasSameCandidateAnchor(incumbent, alternative) &&
|
|
647
|
-
!hasCatastrophicBlock(alternativeSignals) &&
|
|
648
|
-
Number.isFinite(alternativeScore) &&
|
|
649
|
-
alternativeScore <=
|
|
650
|
-
incumbentScore - SAME_ANCHOR_96_IMPERFECTION_IMPROVEMENT &&
|
|
651
|
-
Number.isFinite(alternativeSignals.evidenceLoss) &&
|
|
652
|
-
alternativeSignals.evidenceLoss <=
|
|
653
|
-
incumbentSignals.evidenceLoss + SAME_ANCHOR_96_EVIDENCE_TOLERANCE &&
|
|
654
|
-
Number.isFinite(alternativeSignals.damageLoss) &&
|
|
655
|
-
alternativeSignals.damageLoss <=
|
|
656
|
-
incumbentSignals.damageLoss + SAME_ANCHOR_96_DAMAGE_TOLERANCE;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
export function applySameAnchor96ImperfectionPreference(baseRanked = []) {
|
|
660
|
-
const incumbent = baseRanked[0];
|
|
661
|
-
const position = getCandidatePosition(incumbent);
|
|
662
|
-
const signals = incumbent?.qualitySignals ?? {};
|
|
663
|
-
if (
|
|
664
|
-
!position ||
|
|
665
|
-
position.width !== SAME_ANCHOR_96_SIZE ||
|
|
666
|
-
position.height !== SAME_ANCHOR_96_SIZE ||
|
|
667
|
-
signals.imperfections?.severity !== 'high' ||
|
|
668
|
-
!Number.isFinite(signals.imperfections?.score) ||
|
|
669
|
-
!Number.isFinite(signals.evidenceLoss) ||
|
|
670
|
-
!Number.isFinite(signals.damageLoss)
|
|
671
|
-
) {
|
|
672
|
-
return baseRanked;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const eligible = baseRanked
|
|
676
|
-
.slice(1)
|
|
677
|
-
.map((candidate, offset) => ({ candidate, baseIndex: offset + 1 }))
|
|
678
|
-
.filter(({ candidate }) =>
|
|
679
|
-
isEligibleSameAnchor96Alternative(incumbent, candidate)
|
|
680
|
-
)
|
|
681
|
-
.sort((left, right) =>
|
|
682
|
-
left.candidate.qualitySignals.imperfections.score -
|
|
683
|
-
right.candidate.qualitySignals.imperfections.score ||
|
|
684
|
-
finiteOr(left.candidate.qualitySignals.residualLoss) -
|
|
685
|
-
finiteOr(right.candidate.qualitySignals.residualLoss) ||
|
|
686
|
-
left.baseIndex - right.baseIndex ||
|
|
687
|
-
String(left.candidate.hypothesis?.id ?? '').localeCompare(
|
|
688
|
-
String(right.candidate.hypothesis?.id ?? '')
|
|
689
|
-
)
|
|
690
|
-
);
|
|
691
|
-
if (eligible.length === 0) return baseRanked;
|
|
692
|
-
|
|
693
|
-
const promoted = eligible[0].candidate;
|
|
694
|
-
return [promoted, ...baseRanked.filter((candidate) => candidate !== promoted)];
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function applyFullStrengthNewMarginPreference(baseRanked = []) {
|
|
698
|
-
const incumbent = baseRanked[0];
|
|
699
|
-
if (!incumbent) return baseRanked;
|
|
700
|
-
|
|
701
|
-
const getFinalTrialView = (candidate) => {
|
|
702
|
-
const trial = candidate?.hypothesis?.trial ?? {};
|
|
703
|
-
const meta = candidate?.result?.meta ?? {};
|
|
704
|
-
const detection = meta.detection ?? {};
|
|
705
|
-
return {
|
|
706
|
-
...trial,
|
|
707
|
-
alphaGain: Number.isFinite(Number(meta.alphaGain))
|
|
708
|
-
? Number(meta.alphaGain)
|
|
709
|
-
: trial.alphaGain,
|
|
710
|
-
config: meta.config ?? trial.config,
|
|
711
|
-
position: meta.position ?? trial.position,
|
|
712
|
-
originalSpatialScore:
|
|
713
|
-
detection.originalSpatialScore ?? trial.originalSpatialScore,
|
|
714
|
-
originalGradientScore:
|
|
715
|
-
detection.originalGradientScore ?? trial.originalGradientScore,
|
|
716
|
-
processedGradientScore:
|
|
717
|
-
detection.processedGradientScore ?? trial.processedGradientScore
|
|
718
|
-
};
|
|
719
|
-
};
|
|
720
|
-
const incumbentTrial = getFinalTrialView(incumbent);
|
|
721
|
-
|
|
722
|
-
const promoted = baseRanked.slice(1).find((candidate) => (
|
|
723
|
-
!hasCatastrophicBlock(candidate.qualitySignals) &&
|
|
724
|
-
shouldPreferFullStrengthNewMarginVariant(
|
|
725
|
-
getFinalTrialView(candidate),
|
|
726
|
-
incumbentTrial
|
|
727
|
-
)
|
|
728
|
-
));
|
|
729
|
-
if (!promoted) return baseRanked;
|
|
730
|
-
|
|
731
|
-
return [promoted, ...baseRanked.filter((candidate) => candidate !== promoted)];
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
function strictlyDominatesQuality(leftSignals = {}, rightSignals = {}) {
|
|
735
|
-
const keys = ['evidenceLoss', 'residualLoss', 'damageLoss'];
|
|
736
|
-
const noWorse = keys.every((key) => (
|
|
737
|
-
(leftSignals[key] ?? Infinity) <= (rightSignals[key] ?? Infinity)
|
|
738
|
-
));
|
|
739
|
-
const strictlyBetter = keys.some((key) => (
|
|
740
|
-
(leftSignals[key] ?? Infinity) < (rightSignals[key] ?? Infinity)
|
|
741
|
-
));
|
|
742
|
-
return noWorse && strictlyBetter;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function shouldPreferSameAnchorCleanCandidate(left, right) {
|
|
746
|
-
return left.qualitySignals?.qualityStatus === 'clean' &&
|
|
747
|
-
right.qualitySignals?.qualityStatus !== 'clean' &&
|
|
748
|
-
hasSameCandidateAnchor(left, right) &&
|
|
749
|
-
strictlyDominatesQuality(left.qualitySignals, right.qualitySignals);
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
function compareRankedCandidates(left, right) {
|
|
753
|
-
const leftCatastrophic = hasCatastrophicBlock(left.qualitySignals);
|
|
754
|
-
const rightCatastrophic = hasCatastrophicBlock(right.qualitySignals);
|
|
755
|
-
if (leftCatastrophic !== rightCatastrophic) return leftCatastrophic ? 1 : -1;
|
|
756
|
-
const leftCleanDominates = shouldPreferSameAnchorCleanCandidate(left, right);
|
|
757
|
-
const rightCleanDominates = shouldPreferSameAnchorCleanCandidate(right, left);
|
|
758
|
-
if (leftCleanDominates !== rightCleanDominates) return leftCleanDominates ? -1 : 1;
|
|
759
|
-
if (left.dominated !== right.dominated) return left.dominated ? 1 : -1;
|
|
760
|
-
if (left.finalScore !== right.finalScore) return left.finalScore - right.finalScore;
|
|
761
|
-
const rankingComparison = compareRankingKey(
|
|
762
|
-
left.hypothesis?.rankingKey,
|
|
763
|
-
right.hypothesis?.rankingKey
|
|
764
|
-
);
|
|
765
|
-
if (rankingComparison !== 0) return rankingComparison;
|
|
766
|
-
return String(left.hypothesis?.id ?? '').localeCompare(String(right.hypothesis?.id ?? ''));
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
export function rankCompletedCandidates(completed = []) {
|
|
770
|
-
const scored = completed.map((item) => ({
|
|
771
|
-
...item,
|
|
772
|
-
discoveryPenalty: getDiscoveryPenalty(item.hypothesis),
|
|
773
|
-
finalScore: getFinalScore(item.qualitySignals) + getDiscoveryPenalty(item.hypothesis)
|
|
774
|
-
}));
|
|
775
|
-
for (const candidate of scored) {
|
|
776
|
-
candidate.dominated = scored.some((other) => (
|
|
777
|
-
other !== candidate && dominates(other, candidate)
|
|
778
|
-
));
|
|
779
|
-
}
|
|
780
|
-
scored.sort(compareRankedCandidates);
|
|
781
|
-
const imperfectionPreferred = applySameAnchor96ImperfectionPreference(scored);
|
|
782
|
-
const preferred = applyFullStrengthNewMarginPreference(imperfectionPreferred);
|
|
783
|
-
|
|
784
|
-
const first = preferred[0];
|
|
785
|
-
const second = preferred[1];
|
|
786
|
-
const selectionConfidence = !first
|
|
787
|
-
? 0
|
|
788
|
-
: !second
|
|
789
|
-
? 1
|
|
790
|
-
: clamp01((second.finalScore - first.finalScore) / Math.max(0.05, second.finalScore));
|
|
791
|
-
|
|
792
|
-
return preferred.map((item, index) => ({
|
|
793
|
-
...item,
|
|
794
|
-
rank: index + 1,
|
|
795
|
-
selectionConfidence: index === 0 ? selectionConfidence : 0
|
|
796
|
-
}));
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
export function createCandidateSummaries(ranked = [], failures = []) {
|
|
800
|
-
const valid = ranked.map((item) => ({
|
|
801
|
-
id: item.hypothesis?.id ?? null,
|
|
802
|
-
family: item.hypothesis?.family ?? null,
|
|
803
|
-
rank: item.rank,
|
|
804
|
-
valid: true,
|
|
805
|
-
finalScore: item.finalScore,
|
|
806
|
-
qualityStatus: item.qualitySignals?.qualityStatus ?? null,
|
|
807
|
-
qualitySignals: item.qualitySignals ?? null,
|
|
808
|
-
error: null
|
|
809
|
-
}));
|
|
810
|
-
const invalid = failures.map((item) => ({
|
|
811
|
-
id: item.hypothesis?.id ?? null,
|
|
812
|
-
family: item.hypothesis?.family ?? null,
|
|
813
|
-
rank: null,
|
|
814
|
-
valid: false,
|
|
815
|
-
finalScore: null,
|
|
816
|
-
qualityStatus: null,
|
|
817
|
-
qualitySignals: null,
|
|
818
|
-
error: item.error instanceof Error
|
|
819
|
-
? item.error.message
|
|
820
|
-
: String(item.error ?? 'Candidate execution failed')
|
|
821
|
-
}));
|
|
822
|
-
return [...valid, ...invalid];
|
|
823
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
assessCalibratedWatermarkResidualVisibility,
|
|
3
|
+
assessReferenceTextureAlignment,
|
|
4
|
+
assessRemovalDiffArtifacts,
|
|
5
|
+
calculateNearBlackRatio,
|
|
6
|
+
calculateNearWhiteRatio,
|
|
7
|
+
scoreRegion
|
|
8
|
+
} from './restorationMetrics.js';
|
|
9
|
+
import { getEmbeddedAlphaMap } from './embeddedAlphaMaps.js';
|
|
10
|
+
import {
|
|
11
|
+
buildPreviewNeighborhoodPrior,
|
|
12
|
+
measurePreviewBoundaryMetrics
|
|
13
|
+
} from './previewAlphaCalibration.js';
|
|
14
|
+
import { compareRankingKey } from './watermarkScoring.js';
|
|
15
|
+
import { shouldPreferFullStrengthNewMarginVariant } from './candidateEvaluation.js';
|
|
16
|
+
|
|
17
|
+
export const FINAL_EVIDENCE_WEIGHT = 0.35;
|
|
18
|
+
export const FINAL_RESIDUAL_WEIGHT = 0.40;
|
|
19
|
+
export const FINAL_DAMAGE_WEIGHT = 0.25;
|
|
20
|
+
export const FINAL_DAMAGE_WARNING_PENALTY = 0.08;
|
|
21
|
+
|
|
22
|
+
const DISCOVERY_ROLE_PENALTIES = {
|
|
23
|
+
'fixed-selected': 0,
|
|
24
|
+
'automatic-selected': 0.03,
|
|
25
|
+
'aggressive-fallback-alternative': 0.015,
|
|
26
|
+
'conservative-derived': 0.15,
|
|
27
|
+
'discovered-alternative': 0.25
|
|
28
|
+
};
|
|
29
|
+
// Keep these reporting thresholds aligned with assessWatermarkResidualVisibility.
|
|
30
|
+
const IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD = 6;
|
|
31
|
+
const IMPERFECTION_GRADIENT_THRESHOLD = 0.22;
|
|
32
|
+
const IMPERFECTION_SPATIAL_THRESHOLD = 0.18;
|
|
33
|
+
const IMPERFECTION_WARNING_RATIO = 0.5;
|
|
34
|
+
const DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL = 0.18;
|
|
35
|
+
const DARK_SUPPORT_RESOLUTION_MAX_GRADIENT = 0.18;
|
|
36
|
+
const DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO = 0.25;
|
|
37
|
+
const SEVERE_DARK_FLAT_TEXTURE_WARNING_THRESHOLD = 1.5;
|
|
38
|
+
const INDEPENDENT_DARK_SUPPORT_SIZE = 96;
|
|
39
|
+
const INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD = 12;
|
|
40
|
+
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_SPATIAL = 0.95;
|
|
41
|
+
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_GRADIENT = 0.9;
|
|
42
|
+
const INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_NEAR_BLACK = 0.1;
|
|
43
|
+
const INDEPENDENT_DARK_SUPPORT_MIN_CANDIDATE_NEAR_BLACK = 0.25;
|
|
44
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_OPPOSITE_HALO_LUMINANCE = 1;
|
|
45
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_OUTSIDE_ALPHA_CLIP_RATIO = 0.03;
|
|
46
|
+
const INDEPENDENT_DARK_SUPPORT_MIN_STRONG_ALPHA_CLIP_RATIO = 0.8;
|
|
47
|
+
const INDEPENDENT_DARK_SUPPORT_ALPHA_MIN = 0.05;
|
|
48
|
+
const INDEPENDENT_DARK_SUPPORT_ALPHA_STRONG_MIN = 0.2;
|
|
49
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_LUMINANCE = 8;
|
|
50
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_CANDIDATE_MEAN_LUMINANCE = 8;
|
|
51
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_DELTA = 1.5;
|
|
52
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MAE = 2.25;
|
|
53
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_WEIGHTED_MAE = 3.5;
|
|
54
|
+
const INDEPENDENT_DARK_SUPPORT_MAX_BOUNDARY_SCORE = 0.35;
|
|
55
|
+
const INDEPENDENT_DARK_SUPPORT_PRIOR_RADII = Object.freeze([4, 12]);
|
|
56
|
+
const INDEPENDENT_DARK_SUPPORT_RELAXATION_PASSES = 0;
|
|
57
|
+
|
|
58
|
+
function clamp01(value) {
|
|
59
|
+
if (!Number.isFinite(value)) return 0;
|
|
60
|
+
return Math.max(0, Math.min(1, value));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function haveEqualAlphaValues(left, right) {
|
|
64
|
+
if (!left || !right || left.length !== right.length) return false;
|
|
65
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
66
|
+
if (left[index] !== right[index]) return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveReferenceAlphaMap(
|
|
72
|
+
hypothesis,
|
|
73
|
+
explicitReferenceAlphaMap,
|
|
74
|
+
selectedAlphaMap
|
|
75
|
+
) {
|
|
76
|
+
const config = hypothesis?.config ?? hypothesis?.trial?.config;
|
|
77
|
+
const logoSize = config?.logoSize;
|
|
78
|
+
const alphaVariant = config?.alphaVariant;
|
|
79
|
+
const catalogKey = Number.isFinite(logoSize)
|
|
80
|
+
? typeof alphaVariant === 'string' && alphaVariant.length > 0
|
|
81
|
+
? `${logoSize}-${alphaVariant}`
|
|
82
|
+
: logoSize
|
|
83
|
+
: null;
|
|
84
|
+
// Keep one catalog-derived observer independent from any alpha profile
|
|
85
|
+
// mutated or injected by the candidate being evaluated.
|
|
86
|
+
const referenceAlphaMap = explicitReferenceAlphaMap ??
|
|
87
|
+
(catalogKey === null ? null : getEmbeddedAlphaMap(catalogKey));
|
|
88
|
+
return haveEqualAlphaValues(referenceAlphaMap, selectedAlphaMap)
|
|
89
|
+
? null
|
|
90
|
+
: referenceAlphaMap;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function extractCandidateQualityScratch(imageData, position) {
|
|
94
|
+
const x = Math.max(0, position.x - INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD);
|
|
95
|
+
const y = Math.max(0, position.y - INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD);
|
|
96
|
+
const right = Math.min(
|
|
97
|
+
imageData.width,
|
|
98
|
+
position.x + position.width + INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD
|
|
99
|
+
);
|
|
100
|
+
const bottom = Math.min(
|
|
101
|
+
imageData.height,
|
|
102
|
+
position.y + position.height + INDEPENDENT_DARK_SUPPORT_SCRATCH_PAD
|
|
103
|
+
);
|
|
104
|
+
const scratch = {
|
|
105
|
+
width: right - x,
|
|
106
|
+
height: bottom - y,
|
|
107
|
+
data: new Uint8ClampedArray((right - x) * (bottom - y) * 4)
|
|
108
|
+
};
|
|
109
|
+
for (let row = 0; row < scratch.height; row++) {
|
|
110
|
+
const sourceStart = ((y + row) * imageData.width + x) * 4;
|
|
111
|
+
const sourceEnd = sourceStart + scratch.width * 4;
|
|
112
|
+
scratch.data.set(
|
|
113
|
+
imageData.data.subarray(sourceStart, sourceEnd),
|
|
114
|
+
row * scratch.width * 4
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
imageData: scratch,
|
|
119
|
+
position: {
|
|
120
|
+
x: position.x - x,
|
|
121
|
+
y: position.y - y,
|
|
122
|
+
width: position.width,
|
|
123
|
+
height: position.height
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function measureIndependentDarkSupportClipping({
|
|
129
|
+
originalImageData,
|
|
130
|
+
candidateImageData,
|
|
131
|
+
alphaMap,
|
|
132
|
+
position
|
|
133
|
+
}) {
|
|
134
|
+
let newlyClippedCount = 0;
|
|
135
|
+
let outsideAlphaSupportCount = 0;
|
|
136
|
+
let strongAlphaSupportCount = 0;
|
|
137
|
+
for (let row = 0; row < position.height; row++) {
|
|
138
|
+
for (let col = 0; col < position.width; col++) {
|
|
139
|
+
const localIndex = row * position.width + col;
|
|
140
|
+
const pixelIndex = (
|
|
141
|
+
(position.y + row) * originalImageData.width +
|
|
142
|
+
position.x +
|
|
143
|
+
col
|
|
144
|
+
) * 4;
|
|
145
|
+
const newlyClipped = [0, 1, 2].some((channel) => (
|
|
146
|
+
candidateImageData.data[pixelIndex + channel] === 0 &&
|
|
147
|
+
originalImageData.data[pixelIndex + channel] > 5
|
|
148
|
+
));
|
|
149
|
+
if (!newlyClipped) continue;
|
|
150
|
+
|
|
151
|
+
newlyClippedCount++;
|
|
152
|
+
const alpha = Math.abs(alphaMap[localIndex]);
|
|
153
|
+
if (alpha < INDEPENDENT_DARK_SUPPORT_ALPHA_MIN) {
|
|
154
|
+
outsideAlphaSupportCount++;
|
|
155
|
+
}
|
|
156
|
+
if (alpha >= INDEPENDENT_DARK_SUPPORT_ALPHA_STRONG_MIN) {
|
|
157
|
+
strongAlphaSupportCount++;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
outsideAlphaSupportRatio: newlyClippedCount > 0
|
|
163
|
+
? outsideAlphaSupportCount / newlyClippedCount
|
|
164
|
+
: 0,
|
|
165
|
+
strongAlphaSupportRatio: newlyClippedCount > 0
|
|
166
|
+
? strongAlphaSupportCount / newlyClippedCount
|
|
167
|
+
: 1
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function measureIndependentDarkSupportPrior({
|
|
172
|
+
candidateImageData,
|
|
173
|
+
priorImageData,
|
|
174
|
+
alphaMap,
|
|
175
|
+
position
|
|
176
|
+
}) {
|
|
177
|
+
let absoluteDelta = 0;
|
|
178
|
+
let weightedAbsoluteDelta = 0;
|
|
179
|
+
let alphaWeight = 0;
|
|
180
|
+
let candidateLuminance = 0;
|
|
181
|
+
let priorLuminance = 0;
|
|
182
|
+
let channelCount = 0;
|
|
183
|
+
let pixelCount = 0;
|
|
184
|
+
for (let row = 0; row < position.height; row++) {
|
|
185
|
+
for (let col = 0; col < position.width; col++) {
|
|
186
|
+
const localIndex = row * position.width + col;
|
|
187
|
+
const alpha = Math.abs(alphaMap[localIndex]);
|
|
188
|
+
const pixelIndex = (
|
|
189
|
+
(position.y + row) * candidateImageData.width +
|
|
190
|
+
position.x +
|
|
191
|
+
col
|
|
192
|
+
) * 4;
|
|
193
|
+
for (let channel = 0; channel < 3; channel++) {
|
|
194
|
+
const delta = Math.abs(
|
|
195
|
+
candidateImageData.data[pixelIndex + channel] -
|
|
196
|
+
priorImageData.data[pixelIndex + channel]
|
|
197
|
+
);
|
|
198
|
+
absoluteDelta += delta;
|
|
199
|
+
weightedAbsoluteDelta += delta * alpha;
|
|
200
|
+
channelCount++;
|
|
201
|
+
}
|
|
202
|
+
candidateLuminance +=
|
|
203
|
+
0.2126 * candidateImageData.data[pixelIndex] +
|
|
204
|
+
0.7152 * candidateImageData.data[pixelIndex + 1] +
|
|
205
|
+
0.0722 * candidateImageData.data[pixelIndex + 2];
|
|
206
|
+
priorLuminance +=
|
|
207
|
+
0.2126 * priorImageData.data[pixelIndex] +
|
|
208
|
+
0.7152 * priorImageData.data[pixelIndex + 1] +
|
|
209
|
+
0.0722 * priorImageData.data[pixelIndex + 2];
|
|
210
|
+
alphaWeight += alpha;
|
|
211
|
+
pixelCount++;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const candidateMeanLuminance = candidateLuminance / Math.max(1, pixelCount);
|
|
215
|
+
const priorMeanLuminance = priorLuminance / Math.max(1, pixelCount);
|
|
216
|
+
return {
|
|
217
|
+
meanAbsoluteDelta: absoluteDelta / Math.max(1, channelCount),
|
|
218
|
+
weightedMeanAbsoluteDelta:
|
|
219
|
+
weightedAbsoluteDelta / Math.max(1e-8, alphaWeight * 3),
|
|
220
|
+
candidateMeanLuminance,
|
|
221
|
+
priorMeanLuminance,
|
|
222
|
+
meanLuminanceDelta: Math.abs(candidateMeanLuminance - priorMeanLuminance)
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function assessIndependentDarkBackgroundSupportConvergence({
|
|
227
|
+
originalImageData,
|
|
228
|
+
candidateImageData,
|
|
229
|
+
hypothesis,
|
|
230
|
+
position,
|
|
231
|
+
alphaMap,
|
|
232
|
+
alphaGain,
|
|
233
|
+
original,
|
|
234
|
+
final,
|
|
235
|
+
visibility,
|
|
236
|
+
artifacts,
|
|
237
|
+
originalNearBlackRatio,
|
|
238
|
+
candidateNearBlackRatio,
|
|
239
|
+
rawDamageWarning
|
|
240
|
+
}) {
|
|
241
|
+
const trial = hypothesis?.trial;
|
|
242
|
+
const source = trial?.source;
|
|
243
|
+
const catalogAlphaMap = getEmbeddedAlphaMap('96-20260520');
|
|
244
|
+
if (
|
|
245
|
+
rawDamageWarning !== true ||
|
|
246
|
+
source !== 'standard' ||
|
|
247
|
+
position.width !== INDEPENDENT_DARK_SUPPORT_SIZE ||
|
|
248
|
+
position.height !== INDEPENDENT_DARK_SUPPORT_SIZE ||
|
|
249
|
+
alphaGain !== 1 ||
|
|
250
|
+
!haveEqualAlphaValues(alphaMap, catalogAlphaMap) ||
|
|
251
|
+
original.spatialScore < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_SPATIAL ||
|
|
252
|
+
original.gradientScore < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_GRADIENT ||
|
|
253
|
+
visibility?.visible !== false ||
|
|
254
|
+
Math.abs(final.spatialScore) > DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL ||
|
|
255
|
+
final.gradientScore > DARK_SUPPORT_RESOLUTION_MAX_GRADIENT ||
|
|
256
|
+
originalNearBlackRatio < INDEPENDENT_DARK_SUPPORT_MIN_ORIGINAL_NEAR_BLACK ||
|
|
257
|
+
candidateNearBlackRatio < INDEPENDENT_DARK_SUPPORT_MIN_CANDIDATE_NEAR_BLACK ||
|
|
258
|
+
Number(artifacts?.newlyClippedRatio ?? Infinity) >
|
|
259
|
+
DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO ||
|
|
260
|
+
Number(artifacts?.oppositeDirectionHaloLum ?? Infinity) >
|
|
261
|
+
INDEPENDENT_DARK_SUPPORT_MAX_OPPOSITE_HALO_LUMINANCE
|
|
262
|
+
) {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const originalScratch = extractCandidateQualityScratch(
|
|
267
|
+
originalImageData,
|
|
268
|
+
position
|
|
269
|
+
);
|
|
270
|
+
const candidateScratch = extractCandidateQualityScratch(
|
|
271
|
+
candidateImageData,
|
|
272
|
+
position
|
|
273
|
+
);
|
|
274
|
+
const priorMetrics = INDEPENDENT_DARK_SUPPORT_PRIOR_RADII.map((radius) => {
|
|
275
|
+
const priorImageData = buildPreviewNeighborhoodPrior({
|
|
276
|
+
previewImageData: originalScratch.imageData,
|
|
277
|
+
position: originalScratch.position,
|
|
278
|
+
radius,
|
|
279
|
+
relaxationPasses: INDEPENDENT_DARK_SUPPORT_RELAXATION_PASSES
|
|
280
|
+
});
|
|
281
|
+
return {
|
|
282
|
+
radius,
|
|
283
|
+
...measureIndependentDarkSupportPrior({
|
|
284
|
+
candidateImageData: candidateScratch.imageData,
|
|
285
|
+
priorImageData,
|
|
286
|
+
alphaMap,
|
|
287
|
+
position: candidateScratch.position
|
|
288
|
+
})
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
const clipping = measureIndependentDarkSupportClipping({
|
|
292
|
+
originalImageData: originalScratch.imageData,
|
|
293
|
+
candidateImageData: candidateScratch.imageData,
|
|
294
|
+
alphaMap,
|
|
295
|
+
position: originalScratch.position
|
|
296
|
+
});
|
|
297
|
+
const boundary = measurePreviewBoundaryMetrics(
|
|
298
|
+
candidateScratch.imageData,
|
|
299
|
+
originalScratch.imageData,
|
|
300
|
+
originalScratch.position
|
|
301
|
+
);
|
|
302
|
+
const maximumPriorMeanLuminance = Math.max(
|
|
303
|
+
...priorMetrics.map((item) => item.priorMeanLuminance)
|
|
304
|
+
);
|
|
305
|
+
const maximumCandidateMeanLuminance = Math.max(
|
|
306
|
+
...priorMetrics.map((item) => item.candidateMeanLuminance)
|
|
307
|
+
);
|
|
308
|
+
const maximumPriorMeanDelta = Math.max(
|
|
309
|
+
...priorMetrics.map((item) => item.meanLuminanceDelta)
|
|
310
|
+
);
|
|
311
|
+
const maximumPriorMae = Math.max(
|
|
312
|
+
...priorMetrics.map((item) => item.meanAbsoluteDelta)
|
|
313
|
+
);
|
|
314
|
+
const maximumPriorWeightedMae = Math.max(
|
|
315
|
+
...priorMetrics.map((item) => item.weightedMeanAbsoluteDelta)
|
|
316
|
+
);
|
|
317
|
+
const accepted = Boolean(
|
|
318
|
+
clipping.outsideAlphaSupportRatio <=
|
|
319
|
+
INDEPENDENT_DARK_SUPPORT_MAX_OUTSIDE_ALPHA_CLIP_RATIO &&
|
|
320
|
+
clipping.strongAlphaSupportRatio >=
|
|
321
|
+
INDEPENDENT_DARK_SUPPORT_MIN_STRONG_ALPHA_CLIP_RATIO &&
|
|
322
|
+
maximumPriorMeanLuminance <=
|
|
323
|
+
INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_LUMINANCE &&
|
|
324
|
+
maximumCandidateMeanLuminance <=
|
|
325
|
+
INDEPENDENT_DARK_SUPPORT_MAX_CANDIDATE_MEAN_LUMINANCE &&
|
|
326
|
+
maximumPriorMeanDelta <= INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MEAN_DELTA &&
|
|
327
|
+
maximumPriorMae <= INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_MAE &&
|
|
328
|
+
maximumPriorWeightedMae <=
|
|
329
|
+
INDEPENDENT_DARK_SUPPORT_MAX_PRIOR_WEIGHTED_MAE &&
|
|
330
|
+
boundary.normalizedScore <= INDEPENDENT_DARK_SUPPORT_MAX_BOUNDARY_SCORE
|
|
331
|
+
);
|
|
332
|
+
return {
|
|
333
|
+
accepted,
|
|
334
|
+
source: 'independent-standard-96-neighborhood-prior',
|
|
335
|
+
textureHardRejectResolved: accepted,
|
|
336
|
+
priorRadii: [...INDEPENDENT_DARK_SUPPORT_PRIOR_RADII],
|
|
337
|
+
maximumPriorMeanLuminance,
|
|
338
|
+
maximumCandidateMeanLuminance,
|
|
339
|
+
maximumPriorMeanDelta,
|
|
340
|
+
maximumPriorMae,
|
|
341
|
+
maximumPriorWeightedMae,
|
|
342
|
+
boundaryScore: boundary.normalizedScore,
|
|
343
|
+
clipping
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function classifyCandidateQuality({
|
|
348
|
+
residualVisible = false,
|
|
349
|
+
damageWarning = false
|
|
350
|
+
} = {}) {
|
|
351
|
+
if (residualVisible && damageWarning) return 'mixed';
|
|
352
|
+
if (residualVisible) return 'visible-residual';
|
|
353
|
+
if (damageWarning) return 'possible-content-damage';
|
|
354
|
+
return 'clean';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function createCandidateImperfectionSignals(visibility = {}) {
|
|
358
|
+
const darkPolarityHaloThreshold = Number.isFinite(visibility.darkPolarityHaloThreshold)
|
|
359
|
+
? visibility.darkPolarityHaloThreshold
|
|
360
|
+
: IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD;
|
|
361
|
+
const definitions = [
|
|
362
|
+
['spatial-residual', visibility.spatialResidual, IMPERFECTION_SPATIAL_THRESHOLD],
|
|
363
|
+
['gradient-residual', visibility.gradientResidual, IMPERFECTION_GRADIENT_THRESHOLD],
|
|
364
|
+
['positive-halo', visibility.positiveHaloLum, IMPERFECTION_POSITIVE_HALO_LUM_THRESHOLD],
|
|
365
|
+
['dark-polarity-halo', visibility.darkPolarityHaloLum, darkPolarityHaloThreshold]
|
|
366
|
+
];
|
|
367
|
+
const components = Object.fromEntries(definitions.map(([type, rawValue, threshold]) => {
|
|
368
|
+
const value = Number.isFinite(rawValue) ? Math.max(0, rawValue) : 0;
|
|
369
|
+
return [type, {
|
|
370
|
+
value,
|
|
371
|
+
threshold,
|
|
372
|
+
ratio: value / threshold
|
|
373
|
+
}];
|
|
374
|
+
}));
|
|
375
|
+
const score = Math.max(0, ...Object.values(components).map((component) => component.ratio));
|
|
376
|
+
const severity = score >= 1
|
|
377
|
+
? 'high'
|
|
378
|
+
: score >= IMPERFECTION_WARNING_RATIO
|
|
379
|
+
? 'moderate'
|
|
380
|
+
: score > 0
|
|
381
|
+
? 'low'
|
|
382
|
+
: 'none';
|
|
383
|
+
const types = definitions
|
|
384
|
+
.map(([type]) => type)
|
|
385
|
+
.filter((type) => components[type].ratio >= IMPERFECTION_WARNING_RATIO);
|
|
386
|
+
|
|
387
|
+
return {
|
|
388
|
+
detected: score >= IMPERFECTION_WARNING_RATIO,
|
|
389
|
+
severity,
|
|
390
|
+
score,
|
|
391
|
+
types,
|
|
392
|
+
components
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function createCandidateQualitySignals({
|
|
397
|
+
originalImageData,
|
|
398
|
+
candidateImageData,
|
|
399
|
+
hypothesis,
|
|
400
|
+
finalCandidate = null,
|
|
401
|
+
referenceAlphaMap = null
|
|
402
|
+
} = {}) {
|
|
403
|
+
const trial = hypothesis?.trial;
|
|
404
|
+
const position = finalCandidate?.position ?? trial?.position ?? hypothesis?.position;
|
|
405
|
+
const alphaMap = finalCandidate?.alphaMap ?? trial?.alphaMap;
|
|
406
|
+
const alphaGain = finalCandidate?.alphaGain ?? trial?.alphaGain ?? 1;
|
|
407
|
+
if (!originalImageData || !candidateImageData || !position || !alphaMap) {
|
|
408
|
+
throw new Error('Candidate quality measurement requires original pixels, candidate pixels, position, and alpha map');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const original = scoreRegion(originalImageData, alphaMap, position);
|
|
412
|
+
const final = scoreRegion(candidateImageData, alphaMap, position);
|
|
413
|
+
const visibility = assessCalibratedWatermarkResidualVisibility({
|
|
414
|
+
imageData: candidateImageData,
|
|
415
|
+
originalImageData,
|
|
416
|
+
position,
|
|
417
|
+
alphaMap,
|
|
418
|
+
alphaGain
|
|
419
|
+
});
|
|
420
|
+
const resolvedReferenceAlphaMap = resolveReferenceAlphaMap(
|
|
421
|
+
hypothesis,
|
|
422
|
+
referenceAlphaMap,
|
|
423
|
+
alphaMap
|
|
424
|
+
);
|
|
425
|
+
const referenceVisibility = resolvedReferenceAlphaMap
|
|
426
|
+
? assessCalibratedWatermarkResidualVisibility({
|
|
427
|
+
imageData: candidateImageData,
|
|
428
|
+
originalImageData,
|
|
429
|
+
position,
|
|
430
|
+
alphaMap: resolvedReferenceAlphaMap,
|
|
431
|
+
alphaGain
|
|
432
|
+
})
|
|
433
|
+
: null;
|
|
434
|
+
const imperfections = createCandidateImperfectionSignals(visibility);
|
|
435
|
+
const artifacts = assessRemovalDiffArtifacts({
|
|
436
|
+
originalImageData,
|
|
437
|
+
candidateImageData,
|
|
438
|
+
alphaMap,
|
|
439
|
+
position,
|
|
440
|
+
alphaGain
|
|
441
|
+
});
|
|
442
|
+
const texture = assessReferenceTextureAlignment({
|
|
443
|
+
originalImageData,
|
|
444
|
+
referenceImageData: originalImageData,
|
|
445
|
+
candidateImageData,
|
|
446
|
+
position
|
|
447
|
+
});
|
|
448
|
+
const originalNearBlackRatio = calculateNearBlackRatio(originalImageData, position);
|
|
449
|
+
const candidateNearBlackRatio = calculateNearBlackRatio(candidateImageData, position);
|
|
450
|
+
const originalNearWhiteRatio = calculateNearWhiteRatio(originalImageData, position);
|
|
451
|
+
const candidateNearWhiteRatio = calculateNearWhiteRatio(candidateImageData, position);
|
|
452
|
+
const nearBlackIncrease = candidateNearBlackRatio - originalNearBlackRatio;
|
|
453
|
+
const nearWhiteIncrease = candidateNearWhiteRatio - originalNearWhiteRatio;
|
|
454
|
+
|
|
455
|
+
const evidence = clamp01((
|
|
456
|
+
Math.max(0, original.spatialScore) +
|
|
457
|
+
Math.max(0, original.gradientScore)
|
|
458
|
+
) / 2);
|
|
459
|
+
const residualComponents = {
|
|
460
|
+
spatial: clamp01(Math.abs(final.spatialScore) / 0.4),
|
|
461
|
+
gradient: clamp01(Math.max(0, final.gradientScore) / 0.35),
|
|
462
|
+
halo: clamp01(Math.max(
|
|
463
|
+
visibility?.positiveHaloLum ?? 0,
|
|
464
|
+
visibility?.darkPolarityHaloLum ?? 0
|
|
465
|
+
) / 8)
|
|
466
|
+
};
|
|
467
|
+
const residualLoss =
|
|
468
|
+
residualComponents.spatial * 0.45 +
|
|
469
|
+
residualComponents.gradient * 0.35 +
|
|
470
|
+
residualComponents.halo * 0.20;
|
|
471
|
+
const damageComponents = {
|
|
472
|
+
nearBlack: clamp01(Math.max(0, nearBlackIncrease) / 0.05),
|
|
473
|
+
nearWhite: clamp01(Math.max(0, nearWhiteIncrease) / 0.05),
|
|
474
|
+
texture: clamp01((texture?.texturePenalty ?? 0) / 1),
|
|
475
|
+
clipped: clamp01((artifacts?.newlyClippedRatio ?? 0) / 0.02)
|
|
476
|
+
};
|
|
477
|
+
const rawDamageLoss =
|
|
478
|
+
damageComponents.nearBlack * 0.25 +
|
|
479
|
+
damageComponents.nearWhite * 0.25 +
|
|
480
|
+
damageComponents.texture * 0.25 +
|
|
481
|
+
damageComponents.clipped * 0.25;
|
|
482
|
+
const residualVisible = visibility?.visible === true ||
|
|
483
|
+
referenceVisibility?.visible === true;
|
|
484
|
+
const textureWarningCorroborated = (
|
|
485
|
+
texture?.visibleDarkHole === true ||
|
|
486
|
+
texture?.hardReject === true
|
|
487
|
+
) && (
|
|
488
|
+
damageComponents.nearBlack >= 0.4 ||
|
|
489
|
+
damageComponents.nearWhite >= 0.4 ||
|
|
490
|
+
damageComponents.clipped >= 0.4
|
|
491
|
+
);
|
|
492
|
+
const severeDarkFlatTextureWarning =
|
|
493
|
+
texture?.tooDark === true &&
|
|
494
|
+
texture?.tooFlat === true &&
|
|
495
|
+
Number(texture?.texturePenalty ?? 0) >=
|
|
496
|
+
SEVERE_DARK_FLAT_TEXTURE_WARNING_THRESHOLD;
|
|
497
|
+
const rawDamageWarning = damageComponents.nearBlack >= 1 ||
|
|
498
|
+
damageComponents.nearWhite >= 1 ||
|
|
499
|
+
damageComponents.clipped >= 1 ||
|
|
500
|
+
textureWarningCorroborated ||
|
|
501
|
+
severeDarkFlatTextureWarning;
|
|
502
|
+
const explicitDarkBackgroundSupportConvergence =
|
|
503
|
+
finalCandidate?.darkBackgroundSupportConvergence?.accepted === true
|
|
504
|
+
? finalCandidate.darkBackgroundSupportConvergence
|
|
505
|
+
: null;
|
|
506
|
+
const independentDarkBackgroundSupportConvergence =
|
|
507
|
+
explicitDarkBackgroundSupportConvergence
|
|
508
|
+
? null
|
|
509
|
+
: assessIndependentDarkBackgroundSupportConvergence({
|
|
510
|
+
originalImageData,
|
|
511
|
+
candidateImageData,
|
|
512
|
+
hypothesis,
|
|
513
|
+
position,
|
|
514
|
+
alphaMap,
|
|
515
|
+
alphaGain,
|
|
516
|
+
original,
|
|
517
|
+
final,
|
|
518
|
+
visibility,
|
|
519
|
+
artifacts,
|
|
520
|
+
originalNearBlackRatio,
|
|
521
|
+
candidateNearBlackRatio,
|
|
522
|
+
rawDamageWarning
|
|
523
|
+
});
|
|
524
|
+
const darkBackgroundSupportConvergence =
|
|
525
|
+
explicitDarkBackgroundSupportConvergence ??
|
|
526
|
+
independentDarkBackgroundSupportConvergence;
|
|
527
|
+
const textureHardRejectResolved =
|
|
528
|
+
darkBackgroundSupportConvergence?.textureHardRejectResolved === true;
|
|
529
|
+
const darkBackgroundSupportResolved = Boolean(
|
|
530
|
+
rawDamageWarning &&
|
|
531
|
+
darkBackgroundSupportConvergence &&
|
|
532
|
+
!residualVisible &&
|
|
533
|
+
(
|
|
534
|
+
texture?.hardReject !== true ||
|
|
535
|
+
textureHardRejectResolved
|
|
536
|
+
) &&
|
|
537
|
+
Math.abs(final.spatialScore) <=
|
|
538
|
+
DARK_SUPPORT_RESOLUTION_MAX_ABS_SPATIAL &&
|
|
539
|
+
final.gradientScore <= DARK_SUPPORT_RESOLUTION_MAX_GRADIENT &&
|
|
540
|
+
Number(artifacts?.newlyClippedRatio ?? Infinity) <=
|
|
541
|
+
DARK_SUPPORT_RESOLUTION_MAX_NEWLY_CLIPPED_RATIO
|
|
542
|
+
);
|
|
543
|
+
const damageWarning = rawDamageWarning &&
|
|
544
|
+
!darkBackgroundSupportResolved;
|
|
545
|
+
const damageLoss = darkBackgroundSupportResolved ? 0 : rawDamageLoss;
|
|
546
|
+
const damageRiskResolution = darkBackgroundSupportResolved
|
|
547
|
+
? 'dark-background-support-converged'
|
|
548
|
+
: null;
|
|
549
|
+
const qualityStatus = classifyCandidateQuality({
|
|
550
|
+
residualVisible,
|
|
551
|
+
damageWarning
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
return {
|
|
555
|
+
evidenceLoss: 1 - evidence,
|
|
556
|
+
residualLoss,
|
|
557
|
+
damageLoss,
|
|
558
|
+
rawDamageLoss,
|
|
559
|
+
residualVisible,
|
|
560
|
+
damageWarning,
|
|
561
|
+
rawDamageWarning,
|
|
562
|
+
damageRiskResolution,
|
|
563
|
+
darkBackgroundSupportConvergence,
|
|
564
|
+
qualityStatus,
|
|
565
|
+
imperfections,
|
|
566
|
+
original,
|
|
567
|
+
final,
|
|
568
|
+
visibility,
|
|
569
|
+
referenceVisibility,
|
|
570
|
+
artifacts,
|
|
571
|
+
texture,
|
|
572
|
+
originalNearBlackRatio,
|
|
573
|
+
candidateNearBlackRatio,
|
|
574
|
+
nearBlackIncrease,
|
|
575
|
+
originalNearWhiteRatio,
|
|
576
|
+
candidateNearWhiteRatio,
|
|
577
|
+
nearWhiteIncrease,
|
|
578
|
+
residualComponents,
|
|
579
|
+
damageComponents
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function getFinalScore(signals = {}) {
|
|
584
|
+
const weightedLoss = (signals.evidenceLoss ?? 1) * FINAL_EVIDENCE_WEIGHT +
|
|
585
|
+
(signals.residualLoss ?? 1) * FINAL_RESIDUAL_WEIGHT +
|
|
586
|
+
(signals.damageLoss ?? 1) * FINAL_DAMAGE_WEIGHT;
|
|
587
|
+
return weightedLoss + (signals.damageWarning === true ? FINAL_DAMAGE_WARNING_PENALTY : 0);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function getDiscoveryPenalty(hypothesis = {}) {
|
|
591
|
+
return DISCOVERY_ROLE_PENALTIES[hypothesis.discoveryRole] ?? 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function dominates(left, right) {
|
|
595
|
+
const keys = ['evidenceLoss', 'residualLoss', 'damageLoss'];
|
|
596
|
+
const noWorse = keys.every((key) => (
|
|
597
|
+
(left.qualitySignals?.[key] ?? Infinity) <= (right.qualitySignals?.[key] ?? Infinity)
|
|
598
|
+
)) && left.discoveryPenalty <= right.discoveryPenalty;
|
|
599
|
+
const strictlyBetter = keys.some((key) => (
|
|
600
|
+
(left.qualitySignals?.[key] ?? Infinity) < (right.qualitySignals?.[key] ?? Infinity)
|
|
601
|
+
)) || left.discoveryPenalty < right.discoveryPenalty;
|
|
602
|
+
return noWorse && strictlyBetter;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function hasCatastrophicBlock(signals = {}) {
|
|
606
|
+
if (
|
|
607
|
+
signals.damageWarning === false &&
|
|
608
|
+
signals.darkBackgroundSupportConvergence?.accepted === true
|
|
609
|
+
) {
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
const damage = signals.damageComponents ?? {};
|
|
613
|
+
return signals.texture?.hardReject === true &&
|
|
614
|
+
damage.clipped >= 1 &&
|
|
615
|
+
(damage.nearBlack >= 1 || damage.nearWhite >= 1);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function getCandidatePosition(candidate = {}) {
|
|
619
|
+
return candidate.hypothesis?.trial?.position ?? candidate.hypothesis?.position ?? null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function hasSameCandidateAnchor(left, right) {
|
|
623
|
+
const leftPosition = getCandidatePosition(left);
|
|
624
|
+
const rightPosition = getCandidatePosition(right);
|
|
625
|
+
if (!leftPosition || !rightPosition) return false;
|
|
626
|
+
return leftPosition.x === rightPosition.x &&
|
|
627
|
+
leftPosition.y === rightPosition.y &&
|
|
628
|
+
leftPosition.width === rightPosition.width &&
|
|
629
|
+
leftPosition.height === rightPosition.height;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const SAME_ANCHOR_96_SIZE = 96;
|
|
633
|
+
const SAME_ANCHOR_96_IMPERFECTION_IMPROVEMENT = 0.15;
|
|
634
|
+
const SAME_ANCHOR_96_EVIDENCE_TOLERANCE = 0.05;
|
|
635
|
+
const SAME_ANCHOR_96_DAMAGE_TOLERANCE = 0.05;
|
|
636
|
+
|
|
637
|
+
function finiteOr(value, fallback = Infinity) {
|
|
638
|
+
return Number.isFinite(value) ? value : fallback;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function isEligibleSameAnchor96Alternative(incumbent, alternative) {
|
|
642
|
+
const incumbentSignals = incumbent?.qualitySignals ?? {};
|
|
643
|
+
const alternativeSignals = alternative?.qualitySignals ?? {};
|
|
644
|
+
const incumbentScore = incumbentSignals.imperfections?.score;
|
|
645
|
+
const alternativeScore = alternativeSignals.imperfections?.score;
|
|
646
|
+
return hasSameCandidateAnchor(incumbent, alternative) &&
|
|
647
|
+
!hasCatastrophicBlock(alternativeSignals) &&
|
|
648
|
+
Number.isFinite(alternativeScore) &&
|
|
649
|
+
alternativeScore <=
|
|
650
|
+
incumbentScore - SAME_ANCHOR_96_IMPERFECTION_IMPROVEMENT &&
|
|
651
|
+
Number.isFinite(alternativeSignals.evidenceLoss) &&
|
|
652
|
+
alternativeSignals.evidenceLoss <=
|
|
653
|
+
incumbentSignals.evidenceLoss + SAME_ANCHOR_96_EVIDENCE_TOLERANCE &&
|
|
654
|
+
Number.isFinite(alternativeSignals.damageLoss) &&
|
|
655
|
+
alternativeSignals.damageLoss <=
|
|
656
|
+
incumbentSignals.damageLoss + SAME_ANCHOR_96_DAMAGE_TOLERANCE;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
export function applySameAnchor96ImperfectionPreference(baseRanked = []) {
|
|
660
|
+
const incumbent = baseRanked[0];
|
|
661
|
+
const position = getCandidatePosition(incumbent);
|
|
662
|
+
const signals = incumbent?.qualitySignals ?? {};
|
|
663
|
+
if (
|
|
664
|
+
!position ||
|
|
665
|
+
position.width !== SAME_ANCHOR_96_SIZE ||
|
|
666
|
+
position.height !== SAME_ANCHOR_96_SIZE ||
|
|
667
|
+
signals.imperfections?.severity !== 'high' ||
|
|
668
|
+
!Number.isFinite(signals.imperfections?.score) ||
|
|
669
|
+
!Number.isFinite(signals.evidenceLoss) ||
|
|
670
|
+
!Number.isFinite(signals.damageLoss)
|
|
671
|
+
) {
|
|
672
|
+
return baseRanked;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const eligible = baseRanked
|
|
676
|
+
.slice(1)
|
|
677
|
+
.map((candidate, offset) => ({ candidate, baseIndex: offset + 1 }))
|
|
678
|
+
.filter(({ candidate }) =>
|
|
679
|
+
isEligibleSameAnchor96Alternative(incumbent, candidate)
|
|
680
|
+
)
|
|
681
|
+
.sort((left, right) =>
|
|
682
|
+
left.candidate.qualitySignals.imperfections.score -
|
|
683
|
+
right.candidate.qualitySignals.imperfections.score ||
|
|
684
|
+
finiteOr(left.candidate.qualitySignals.residualLoss) -
|
|
685
|
+
finiteOr(right.candidate.qualitySignals.residualLoss) ||
|
|
686
|
+
left.baseIndex - right.baseIndex ||
|
|
687
|
+
String(left.candidate.hypothesis?.id ?? '').localeCompare(
|
|
688
|
+
String(right.candidate.hypothesis?.id ?? '')
|
|
689
|
+
)
|
|
690
|
+
);
|
|
691
|
+
if (eligible.length === 0) return baseRanked;
|
|
692
|
+
|
|
693
|
+
const promoted = eligible[0].candidate;
|
|
694
|
+
return [promoted, ...baseRanked.filter((candidate) => candidate !== promoted)];
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function applyFullStrengthNewMarginPreference(baseRanked = []) {
|
|
698
|
+
const incumbent = baseRanked[0];
|
|
699
|
+
if (!incumbent) return baseRanked;
|
|
700
|
+
|
|
701
|
+
const getFinalTrialView = (candidate) => {
|
|
702
|
+
const trial = candidate?.hypothesis?.trial ?? {};
|
|
703
|
+
const meta = candidate?.result?.meta ?? {};
|
|
704
|
+
const detection = meta.detection ?? {};
|
|
705
|
+
return {
|
|
706
|
+
...trial,
|
|
707
|
+
alphaGain: Number.isFinite(Number(meta.alphaGain))
|
|
708
|
+
? Number(meta.alphaGain)
|
|
709
|
+
: trial.alphaGain,
|
|
710
|
+
config: meta.config ?? trial.config,
|
|
711
|
+
position: meta.position ?? trial.position,
|
|
712
|
+
originalSpatialScore:
|
|
713
|
+
detection.originalSpatialScore ?? trial.originalSpatialScore,
|
|
714
|
+
originalGradientScore:
|
|
715
|
+
detection.originalGradientScore ?? trial.originalGradientScore,
|
|
716
|
+
processedGradientScore:
|
|
717
|
+
detection.processedGradientScore ?? trial.processedGradientScore
|
|
718
|
+
};
|
|
719
|
+
};
|
|
720
|
+
const incumbentTrial = getFinalTrialView(incumbent);
|
|
721
|
+
|
|
722
|
+
const promoted = baseRanked.slice(1).find((candidate) => (
|
|
723
|
+
!hasCatastrophicBlock(candidate.qualitySignals) &&
|
|
724
|
+
shouldPreferFullStrengthNewMarginVariant(
|
|
725
|
+
getFinalTrialView(candidate),
|
|
726
|
+
incumbentTrial
|
|
727
|
+
)
|
|
728
|
+
));
|
|
729
|
+
if (!promoted) return baseRanked;
|
|
730
|
+
|
|
731
|
+
return [promoted, ...baseRanked.filter((candidate) => candidate !== promoted)];
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function strictlyDominatesQuality(leftSignals = {}, rightSignals = {}) {
|
|
735
|
+
const keys = ['evidenceLoss', 'residualLoss', 'damageLoss'];
|
|
736
|
+
const noWorse = keys.every((key) => (
|
|
737
|
+
(leftSignals[key] ?? Infinity) <= (rightSignals[key] ?? Infinity)
|
|
738
|
+
));
|
|
739
|
+
const strictlyBetter = keys.some((key) => (
|
|
740
|
+
(leftSignals[key] ?? Infinity) < (rightSignals[key] ?? Infinity)
|
|
741
|
+
));
|
|
742
|
+
return noWorse && strictlyBetter;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function shouldPreferSameAnchorCleanCandidate(left, right) {
|
|
746
|
+
return left.qualitySignals?.qualityStatus === 'clean' &&
|
|
747
|
+
right.qualitySignals?.qualityStatus !== 'clean' &&
|
|
748
|
+
hasSameCandidateAnchor(left, right) &&
|
|
749
|
+
strictlyDominatesQuality(left.qualitySignals, right.qualitySignals);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function compareRankedCandidates(left, right) {
|
|
753
|
+
const leftCatastrophic = hasCatastrophicBlock(left.qualitySignals);
|
|
754
|
+
const rightCatastrophic = hasCatastrophicBlock(right.qualitySignals);
|
|
755
|
+
if (leftCatastrophic !== rightCatastrophic) return leftCatastrophic ? 1 : -1;
|
|
756
|
+
const leftCleanDominates = shouldPreferSameAnchorCleanCandidate(left, right);
|
|
757
|
+
const rightCleanDominates = shouldPreferSameAnchorCleanCandidate(right, left);
|
|
758
|
+
if (leftCleanDominates !== rightCleanDominates) return leftCleanDominates ? -1 : 1;
|
|
759
|
+
if (left.dominated !== right.dominated) return left.dominated ? 1 : -1;
|
|
760
|
+
if (left.finalScore !== right.finalScore) return left.finalScore - right.finalScore;
|
|
761
|
+
const rankingComparison = compareRankingKey(
|
|
762
|
+
left.hypothesis?.rankingKey,
|
|
763
|
+
right.hypothesis?.rankingKey
|
|
764
|
+
);
|
|
765
|
+
if (rankingComparison !== 0) return rankingComparison;
|
|
766
|
+
return String(left.hypothesis?.id ?? '').localeCompare(String(right.hypothesis?.id ?? ''));
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export function rankCompletedCandidates(completed = []) {
|
|
770
|
+
const scored = completed.map((item) => ({
|
|
771
|
+
...item,
|
|
772
|
+
discoveryPenalty: getDiscoveryPenalty(item.hypothesis),
|
|
773
|
+
finalScore: getFinalScore(item.qualitySignals) + getDiscoveryPenalty(item.hypothesis)
|
|
774
|
+
}));
|
|
775
|
+
for (const candidate of scored) {
|
|
776
|
+
candidate.dominated = scored.some((other) => (
|
|
777
|
+
other !== candidate && dominates(other, candidate)
|
|
778
|
+
));
|
|
779
|
+
}
|
|
780
|
+
scored.sort(compareRankedCandidates);
|
|
781
|
+
const imperfectionPreferred = applySameAnchor96ImperfectionPreference(scored);
|
|
782
|
+
const preferred = applyFullStrengthNewMarginPreference(imperfectionPreferred);
|
|
783
|
+
|
|
784
|
+
const first = preferred[0];
|
|
785
|
+
const second = preferred[1];
|
|
786
|
+
const selectionConfidence = !first
|
|
787
|
+
? 0
|
|
788
|
+
: !second
|
|
789
|
+
? 1
|
|
790
|
+
: clamp01((second.finalScore - first.finalScore) / Math.max(0.05, second.finalScore));
|
|
791
|
+
|
|
792
|
+
return preferred.map((item, index) => ({
|
|
793
|
+
...item,
|
|
794
|
+
rank: index + 1,
|
|
795
|
+
selectionConfidence: index === 0 ? selectionConfidence : 0
|
|
796
|
+
}));
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
export function createCandidateSummaries(ranked = [], failures = []) {
|
|
800
|
+
const valid = ranked.map((item) => ({
|
|
801
|
+
id: item.hypothesis?.id ?? null,
|
|
802
|
+
family: item.hypothesis?.family ?? null,
|
|
803
|
+
rank: item.rank,
|
|
804
|
+
valid: true,
|
|
805
|
+
finalScore: item.finalScore,
|
|
806
|
+
qualityStatus: item.qualitySignals?.qualityStatus ?? null,
|
|
807
|
+
qualitySignals: item.qualitySignals ?? null,
|
|
808
|
+
error: null
|
|
809
|
+
}));
|
|
810
|
+
const invalid = failures.map((item) => ({
|
|
811
|
+
id: item.hypothesis?.id ?? null,
|
|
812
|
+
family: item.hypothesis?.family ?? null,
|
|
813
|
+
rank: null,
|
|
814
|
+
valid: false,
|
|
815
|
+
finalScore: null,
|
|
816
|
+
qualityStatus: null,
|
|
817
|
+
qualitySignals: null,
|
|
818
|
+
error: item.error instanceof Error
|
|
819
|
+
? item.error.message
|
|
820
|
+
: String(item.error ?? 'Candidate execution failed')
|
|
821
|
+
}));
|
|
822
|
+
return [...valid, ...invalid];
|
|
823
|
+
}
|