feat(report): add annotated wrinkle views and restore capture countdown
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
'use strict';
|
||||
// Display-only side analyses. Never merge these scores into the primary
|
||||
// three-view result or re-run detection when reading a historical report.
|
||||
const VERSION = 'independent-side-wrinkles-v1';
|
||||
function hasAnnotation(result) {
|
||||
if (typeof result?.face_maps?.texture_enhanced_lines === 'string' && result.face_maps.texture_enhanced_lines.trim()) return true;
|
||||
return Object.values(result?.wrinkle_mark || {}).some(points => Array.isArray(points) && points.filter(p => Number.isFinite(p?.x) && Number.isFinite(p?.y)).length >= 3);
|
||||
}
|
||||
async function collect(primary, photos, options) {
|
||||
const {key, secret, url} = options;
|
||||
const fetchImpl = options.fetch || fetch;
|
||||
const sleep = options.sleep || (ms => new Promise(resolve => setTimeout(resolve, ms)));
|
||||
const now = options.now || Date.now;
|
||||
const deadline = options.deadline || now() + 90000;
|
||||
const gap = options.gapMs ?? 4000;
|
||||
const views = {version: VERSION};
|
||||
for (const view of ['left','right']) {
|
||||
const existing = primary[view + '_side_result'] || primary.side_result?.[view + '_side_result'];
|
||||
const existingResult = existing?.result || existing;
|
||||
if (hasAnnotation(existingResult)) {
|
||||
views[view] = {status:'ready',source:'primary-side-result',result:existingResult};
|
||||
continue;
|
||||
}
|
||||
const photo = photos[view];
|
||||
if (!photo) { views[view] = {status:'unavailable',error_code:'MISSING_SIDE_PHOTO'}; continue; }
|
||||
if (deadline - now() <= gap + 1000) { views[view] = {status:'unavailable',error_code:'WRINKLE_TIME_BUDGET'}; continue; }
|
||||
await sleep(gap);
|
||||
const timeout = Math.min(options.timeoutMs || 20000, deadline - now());
|
||||
if (timeout <= 0) { views[view] = {status:'unavailable',error_code:'WRINKLE_TIME_BUDGET'}; continue; }
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append('api_key',key); form.append('api_secret',secret);
|
||||
// A side photo must be the main image of ITS OWN request to obtain
|
||||
// wrinkle maps in the corresponding coordinate space.
|
||||
form.append('image_file',new Blob([Buffer.from(photo,'base64')]),view+'.jpg');
|
||||
form.append('return_maps','texture_enhanced_lines');
|
||||
form.append('return_marks','wrinkle_mark');
|
||||
const response = await fetchImpl(url,{method:'POST',body:form,signal:AbortSignal.timeout(timeout)});
|
||||
const raw = await response.json();
|
||||
const code = raw.error_message ? String(raw.error_message).split(':')[0] : !response.ok ? 'WRINKLE_HTTP_'+response.status : !hasAnnotation(raw.result) ? 'WRINKLE_ANNOTATION_NOT_RETURNED' : null;
|
||||
views[view] = Object.assign({},raw,{status:code?'unavailable':'ready',source:'independent-side-request',error_code:code});
|
||||
} catch (e) {
|
||||
views[view] = {status:'unavailable',source:'independent-side-request',error_code:e.name==='TimeoutError'||e.name==='AbortError'?'WRINKLE_TIMEOUT':'WRINKLE_REQUEST_FAILED'};
|
||||
}
|
||||
}
|
||||
return views;
|
||||
}
|
||||
function forReport(views) {
|
||||
if (!views) return null;
|
||||
const out = {version:views.version};
|
||||
for (const view of ['left','right']) {
|
||||
const item=views[view];
|
||||
if (!item) continue;
|
||||
out[view]={status:item.status,error_code:item.error_code||null,face_rectangle:item.face_rectangle||null};
|
||||
if (item.status==='ready'&&item.result) out[view].result={
|
||||
face_maps:{texture_enhanced_lines:item.result.face_maps?.texture_enhanced_lines||null},
|
||||
wrinkle_mark:item.result.wrinkle_mark||null
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
module.exports={VERSION,hasAnnotation,collect,forReport};
|
||||
Reference in New Issue
Block a user