import Globals from "./Globals";
import { MountFileWithProgress } from "./Utility/MountFileWithProgress";
/* type Estimates = {
Heart: number;
Oblong: number;
Oval: number;
Round: number;
Square: number;
}; */
let resource_loading = null;
export class FaceShapeAnalyzer {
static async Create(modulesManager, onProgress) {
const bsc = modulesManager.modules.visage.module;
const bscAnalyzer = bsc.GetFaceShapeAnalysis();
// file loads wrapped so they load lazily, but once-only
if (!resource_loading) {
// NOTE(Straza) byteLen added because if the server uses compression (gzip),
// content-length will not be correct
// there is no standard header for uncompressed size
const resources = [
{
name: "fs_c.bin",
path: Globals.pathToAssets + "Assets/Plugins/fs_c.bin",
byteLen: 1236,
},
{
name: "fs_m.js.json",
path: Globals.pathToAssets + "Assets/Plugins/fs_m.js.json",
byteLen: 80496,
},
{
name: "fs_m.js.bin",
path: Globals.pathToAssets + "Assets/Plugins/fs_m.js.bin",
byteLen: 3751840,
},
];
const curentprogresses = resources.reduce((acc, x) => {
acc[x.name] = {
contentLength: x.byteLen,
receivedLength: 0,
progress: 0,
done: false,
};
return acc;
}, {});
const total = resources
.map((k) => k.byteLen)
.reduce((partialSum, a) => partialSum + a, 0);
const start = () => {};
const progress = (name) => (x) => {
if (!onProgress) return;
curentprogresses[name] = x;
const keys = Object.keys(curentprogresses);
const done = keys.every((k) => curentprogresses[k].done);
let received = 0;
if (done) {
received = total;
} else {
received = keys
.map((k) => curentprogresses[k].receivedLength)
.reduce((partialSum, a) => partialSum + a, 0);
}
onProgress({
contentLength: total,
receivedLength: received,
progress: received / total,
done,
});
};
const resource_promises = resources.map((r) =>
MountFileWithProgress(
bsc.FS,
r.name,
r.path,
progress(r.name),
start,
r.byteLen,
),
);
resource_loading = Promise.all(resource_promises).catch(
() => (resource_loading = null),
);
}
await resource_loading;
return new FaceShapeAnalyzer(bscAnalyzer, bsc);
}
constructor(bscAnalyzer, bsc) {
this.bscAnalyzer = bscAnalyzer;
this.bsc = bsc;
}
promise = null;
/**
*@callback FaceShapeCallback
*@param {Object} FaceShapeAnalyzerResult - Analyzed face shape
*@param {FaceShapeEstimates} FaceShapeAnalyzerResult.estimates - Object containing model estimates for each of the 5 possible faceshape classes (Heart, Oblong, Oval, Round, Square).
*@param {number} FaceShapeAnalyzerResult.frameIndex - Index of the frame that has been analyzed.
*@param { "OFF"|"OK"|"RECOVERING"|"INIT" } FaceShapeAnalyzerResult.status - Indicates tracking status.
*/
/**
* Initiates the face shape analysis. When a frame has been analyzed a call to the {@link FaceShapeCallback}
* function is issued, with the analysis results passed as the argument.
* <br/>
* @param {FaceShapeCallback} FaceShapeCallback - callback which will be called every time a new frame is analyzed
*/
StartAnalysis(FaceShapeCallback) {
const convert = (result) => {
const status_mapping = new Map([
[this.bsc.TrackStatus.off, "OFF"],
[this.bsc.TrackStatus.init, "INIT"],
[this.bsc.TrackStatus.recovering, "RECOVERING"],
[this.bsc.TrackStatus.ok, "OK"],
]);
let res = {
frameIndex: result.frameIndex,
estimates: result.estimates,
status: status_mapping.get(result.status),
};
result.delete();
return res;
};
const convertThenCall = (result) => {
FaceShapeCallback(convert(result));
};
this.bscAnalyzer.SetCallback(convertThenCall);
this.bscAnalyzer.StartAnalysis();
}
/**
* Stops the face shape analysis and stops issuing calls to the callback given as a argument to the
* {@link StartAnalysis} function.
* <br/>
*/
StopAnalysis() {
this.bscAnalyzer.StopAnalysis();
}
/**
* Resets the internal state of the face shape analyzer. This is useful when switching between frames of different people.
*/
Reset() {
this.bscAnalyzer.Reset();
}
}