galleria_photo_face_rknn_op.../tools/runFaceKnnShadow.js
2026-10-03 12:16:58 +02:00

543 lines
9.8 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const db = require('../db/knex');
const {
runFaceKnn,
} = require('./faceKnnRuntime');
function parseArguments(argv) {
const experimentIndex =
argv.indexOf('--experiment');
const outputIndex =
argv.indexOf('--output');
const backendIndex =
argv.indexOf('--backend');
return {
experimentPath:
experimentIndex >= 0
? path.resolve(
String(
argv[experimentIndex + 1]
|| '',
),
)
: '',
outputPath:
outputIndex >= 0
? path.resolve(
String(
argv[outputIndex + 1]
|| '',
),
)
: '',
backend:
backendIndex >= 0
? String(
argv[backendIndex + 1]
|| '',
)
: '',
keepTemporaryFiles:
argv.includes(
'--keep-temporary-files',
),
};
}
function mean(values) {
if (values.length === 0) {
return null;
}
return (
values.reduce(
(sum, value) =>
sum + value,
0,
)
/ values.length
);
}
function compareResults({
experiment,
runtime,
}) {
const pythonQueries =
experiment
?.evaluations
?.greedy_gain
?.queries;
if (
!Array.isArray(pythonQueries)
|| pythonQueries.length === 0
) {
throw new Error(
'Query greedy_gain mancanti',
);
}
const openclResults =
runtime.output.results;
if (
!Array.isArray(openclResults)
|| openclResults.length
!== pythonQueries.length
) {
throw new Error(
'Numero risultati face_knn incoerente',
);
}
const threshold =
Number(
runtime.config.recognitionThreshold,
);
const tolerance =
Number(
runtime.config.scoreTolerance,
);
let top1Matches = 0;
let decisionMatches = 0;
let bestScoreMatches = 0;
let top3MeanMatches = 0;
let maximumScoreError = 0;
let maximumTop3MeanError = 0;
const comparisons = [];
const mismatches = [];
for (
let queryIndex = 0;
queryIndex < pythonQueries.length;
queryIndex += 1
) {
const pythonQuery =
pythonQueries[queryIndex];
const nativeQuery =
openclResults[queryIndex];
const queryMetadata =
runtime.queryRows[queryIndex];
if (
pythonQuery.face_id
!== queryMetadata.face_id
) {
throw new Error(
'Ordine query incoerente '
+ `all'indice ${queryIndex}`,
);
}
const nativeBestIndex =
Number(nativeQuery.best_index);
const nativePrototype =
runtime.prototypeRows[
nativeBestIndex
];
if (!nativePrototype) {
throw new Error(
'Indice prototype OpenCL '
+ `non valido: ${nativeBestIndex}`,
);
}
const pythonBestScore =
pythonQuery.best_similarity == null
? null
: Number(
pythonQuery.best_similarity,
);
const nativeBestScore =
Number(
nativeQuery.best_score,
);
const pythonTop3Mean =
pythonQuery.top3_mean == null
? null
: Number(
pythonQuery.top3_mean,
);
const nativeTop3Mean =
mean(
(nativeQuery.neighbors || [])
.slice(0, 3)
.map((neighbor) =>
Number(neighbor.score)),
);
const pythonRecognized =
pythonBestScore != null
&& Number.isFinite(
pythonBestScore,
)
&& pythonBestScore >= threshold;
const nativeRecognized =
Boolean(nativeQuery.recognized);
const prototypeMatch =
String(
pythonQuery
.best_prototype_face_id
|| '',
)
=== nativePrototype.face_id;
const decisionMatch =
pythonRecognized
=== nativeRecognized;
const scoreError =
pythonBestScore == null
? null
: Math.abs(
pythonBestScore
- nativeBestScore,
);
const top3MeanError =
(
pythonTop3Mean == null
|| nativeTop3Mean == null
)
? null
: Math.abs(
pythonTop3Mean
- nativeTop3Mean,
);
const scoreMatch =
scoreError != null
&& scoreError <= tolerance;
const top3MeanMatch =
top3MeanError != null
&& top3MeanError <= tolerance;
if (prototypeMatch) {
top1Matches += 1;
}
if (decisionMatch) {
decisionMatches += 1;
}
if (scoreMatch) {
bestScoreMatches += 1;
}
if (top3MeanMatch) {
top3MeanMatches += 1;
}
if (scoreError != null) {
maximumScoreError =
Math.max(
maximumScoreError,
scoreError,
);
}
if (top3MeanError != null) {
maximumTop3MeanError =
Math.max(
maximumTop3MeanError,
top3MeanError,
);
}
const comparison = {
query_index:
queryIndex,
face_id:
pythonQuery.face_id,
photo_name:
pythonQuery.photo_name,
is_prototype:
Boolean(
pythonQuery.is_prototype,
),
excluded_index:
nativeQuery.excluded_index,
python_best_prototype_face_id:
pythonQuery
.best_prototype_face_id
|| null,
native_best_prototype_face_id:
nativePrototype.face_id,
native_best_prototype_index:
nativeBestIndex,
python_best_similarity:
pythonBestScore,
native_best_similarity:
nativeBestScore,
absolute_score_error:
scoreError,
python_top3_mean:
pythonTop3Mean,
native_top3_mean:
nativeTop3Mean,
absolute_top3_mean_error:
top3MeanError,
python_recognized:
pythonRecognized,
native_recognized:
nativeRecognized,
prototype_match:
prototypeMatch,
decision_match:
decisionMatch,
score_match:
scoreMatch,
top3_mean_match:
top3MeanMatch,
};
comparisons.push(comparison);
if (
!prototypeMatch
|| !decisionMatch
|| !scoreMatch
|| !top3MeanMatch
) {
mismatches.push(comparison);
}
}
return {
threshold,
tolerance,
query_count:
pythonQueries.length,
prototype_count:
runtime.prototypeRows.length,
top1_matches:
top1Matches,
decision_matches:
decisionMatches,
best_score_matches:
bestScoreMatches,
top3_mean_matches:
top3MeanMatches,
maximum_score_error:
maximumScoreError,
maximum_top3_mean_error:
maximumTop3MeanError,
mismatch_count:
mismatches.length,
mismatches,
comparisons,
};
}
async function main() {
const options =
parseArguments(
process.argv.slice(2),
);
if (!options.experimentPath) {
throw new Error(
'Uso: node tools/runFaceKnnShadow.js '
+ '--experiment FILE '
+ '[--output FILE] '
+ '[--backend opencl|cpu]',
);
}
const experiment =
JSON.parse(
fs.readFileSync(
options.experimentPath,
'utf8',
),
);
const person =
experiment.person || {};
const pythonQueries =
experiment
?.evaluations
?.greedy_gain
?.queries;
if (
!person.id
|| !person.user
|| !Array.isArray(pythonQueries)
) {
throw new Error(
'Esperimento incompleto',
);
}
const runtime =
await runFaceKnn({
connection: db,
personId: person.id,
user: person.user,
queryFaceIds:
pythonQueries.map(
(query) => query.face_id,
),
backend:
options.backend || undefined,
keepTemporaryFiles:
options.keepTemporaryFiles,
});
const comparison =
compareResults({
experiment,
runtime,
});
const report = {
mode: 'shadow',
generated_at:
new Date().toISOString(),
experiment_path:
options.experimentPath,
database:
experiment.database || null,
person: {
id: person.id,
user: person.user,
name: person.name || null,
},
backend:
runtime.output.backend,
device_name:
runtime.output.device_name,
elapsed_ms:
runtime.output.elapsed_ms,
query_count:
comparison.query_count,
prototype_count:
comparison.prototype_count,
threshold:
comparison.threshold,
tolerance:
comparison.tolerance,
top1_matches:
comparison.top1_matches,
decision_matches:
comparison.decision_matches,
best_score_matches:
comparison.best_score_matches,
top3_mean_matches:
comparison.top3_mean_matches,
maximum_score_error:
comparison.maximum_score_error,
maximum_top3_mean_error:
comparison.maximum_top3_mean_error,
mismatch_count:
comparison.mismatch_count,
mismatches:
comparison.mismatches,
comparisons:
comparison.comparisons,
prototypes:
runtime.prototypeRows,
native_stderr:
runtime.stderr,
temporary_directory:
runtime.temporaryDirectory,
};
const reportText =
`${JSON.stringify(
report,
null,
2,
)}\n`;
if (options.outputPath) {
fs.mkdirSync(
path.dirname(
options.outputPath,
),
{
recursive: true,
},
);
const temporaryOutput =
`${options.outputPath}.tmp-`
+ process.pid;
fs.writeFileSync(
temporaryOutput,
reportText,
'utf8',
);
fs.renameSync(
temporaryOutput,
options.outputPath,
);
}
process.stdout.write(reportText);
if (comparison.mismatch_count > 0) {
process.exitCode = 1;
}
}
main()
.catch((error) => {
console.error(
'[faceKnnShadow] errore:',
error?.stack
|| error?.message
|| error,
);
process.exitCode = 1;
})
.finally(async () => {
await db.destroy();
});