Files
juzi/h5/lib/wrinkle-views.js

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};