63 lines
3.4 KiB
JavaScript
63 lines
3.4 KiB
JavaScript
'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};
|