217 lines
6.2 KiB
JavaScript
Executable File
217 lines
6.2 KiB
JavaScript
Executable File
"use strict";
|
|
|
|
const express = require("express"),
|
|
Promise = require("bluebird");
|
|
|
|
let photoDB;
|
|
|
|
require("../db/photos").then(function(db) {
|
|
photoDB = db;
|
|
});
|
|
|
|
const router = express.Router();
|
|
|
|
router.put("/faces/add/:id", (req, res) => {
|
|
if (!req.user.maintainer) {
|
|
console.warn(`${req.user.name} attempted to modify photos.`);
|
|
return res.status(401).send("Unauthorized to modify photos.");
|
|
}
|
|
|
|
const id = parseInt(req.params.id);
|
|
if (id != req.params.id) {
|
|
return res.status(400).send("Invalid identity id.");
|
|
}
|
|
|
|
if (!Array.isArray(req.body.faces) || req.body.faces.length == 0) {
|
|
return res.status(400).send("No faces supplied.");
|
|
}
|
|
|
|
return photoDB.sequelize.query(
|
|
"UPDATE faces SET identityId=:identityId " +
|
|
"WHERE id IN (:faceIds)", {
|
|
replacements: {
|
|
identityId: id,
|
|
faceIds: req.body.faces
|
|
}
|
|
}).then(() => {
|
|
const identity = {
|
|
id: id,
|
|
faces: req.body.faces
|
|
};
|
|
return res.status(200).json([identity]);
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
return res.status(500).send("Error processing request.");
|
|
});
|
|
});
|
|
|
|
router.post("/", (req, res) => {
|
|
if (!req.user.maintainer) {
|
|
console.warn(`${req.user.name} attempted to modify photos.`);
|
|
return res.status(401).send("Unauthorized to modify photos.");
|
|
}
|
|
const identity = {
|
|
lastName: req.body.lastName || "",
|
|
firstName: req.body.firstName || "",
|
|
middleName: req.body.middleName || ""
|
|
};
|
|
identity.name = req.body.name || (identity.firstName + " " + identity.lastName);
|
|
|
|
let fields = [];
|
|
for (let key in identity) {
|
|
fields.push(key);
|
|
}
|
|
|
|
if (!Array.isArray(req.body.faces) || req.body.faces.length == 0) {
|
|
return res.status(400).send("No faces supplied.");
|
|
}
|
|
|
|
return photoDB.sequelize.query("INSERT INTO identities " +
|
|
"(" + fields.join(",") + ") " +
|
|
"VALUES(:" + fields.join(",:") + ")", {
|
|
replacements: identity,
|
|
}).then(([ results, metadata ]) => {
|
|
identity.id = metadata.lastID;
|
|
return photoDB.sequelize.query(
|
|
"UPDATE faces SET identityId=:identityId " +
|
|
"WHERE id IN (:faceIds)", {
|
|
replacements: {
|
|
identityId: identity.id,
|
|
faceIds: req.body.faces
|
|
}
|
|
}).then(() => {
|
|
identity.faces = req.body.faces;
|
|
return res.status(200).json([identity]);
|
|
});
|
|
}).catch((error) => {
|
|
console.error(error);
|
|
return res.status(500).send("Error processing request.");
|
|
});
|
|
});
|
|
|
|
function bufferToFloat32Array(buffer) {
|
|
return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
|
}
|
|
|
|
function euclideanDistance(a, b) {
|
|
let A = bufferToFloat32Array(a);
|
|
let B = bufferToFloat32Array(b);
|
|
let sum = 0;
|
|
for (let i = 0; i < A.length; i++) {
|
|
let delta = A[i] - B[i];
|
|
sum += delta * delta;
|
|
}
|
|
return Math.sqrt(sum);
|
|
}
|
|
|
|
router.get("/:id?", async (req, res) => {
|
|
let id;
|
|
|
|
if (req.params.id) {
|
|
id = parseInt(req.params.id);
|
|
if (id != req.params.id) {
|
|
return res.status(400).send({ message: "Usage /[id]"});
|
|
}
|
|
}
|
|
|
|
const filter = id ? "WHERE identities.id=:id " : "";
|
|
|
|
const identities = await photoDB.sequelize.query("SELECT " +
|
|
"identities.*," +
|
|
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
|
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds," +
|
|
"GROUP_CONCAT(facedescriptors.descriptors) AS relatedIdentityDescriptors " +
|
|
"FROM identities " +
|
|
"INNER JOIN facedescriptors ON facedescriptors.id=faces.descriptorId " +
|
|
"INNER JOIN faces ON identities.id=faces.identityId " +
|
|
filter +
|
|
"GROUP BY identities.id", {
|
|
replacements: { id },
|
|
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
raw: true
|
|
});
|
|
|
|
identities.forEach((identity) => {
|
|
[ 'firstName', 'middleName', 'lastName' ].forEach(key => {
|
|
if (!identity[key]) {
|
|
identity[key] = '';
|
|
}
|
|
});
|
|
|
|
const relatedFaces = identity.relatedFaceIds.split(","),
|
|
relatedFacePhotos = identity.relatedFacePhotoIds.split(","),
|
|
relatedIdentityDescriptors =
|
|
identity.relatedIdentityDescriptors.split(",");
|
|
|
|
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
|
const distance = euclideanDistance(
|
|
relatedIdentityDescriptors[index],
|
|
identity.descriptors
|
|
);
|
|
return {
|
|
faceId,
|
|
photoId: relatedFacePhotos[index],
|
|
distance
|
|
};
|
|
});
|
|
|
|
delete identity.relatedFaceIds;
|
|
delete identity.relatedFacePhotoIds;
|
|
delete identity.relatedIdentityDescriptors;
|
|
});
|
|
|
|
//if (!req.query.withScore) {
|
|
console.log("No score request.");
|
|
return res.status(200).json(identities);
|
|
//}
|
|
|
|
// THe rest of this routine needs to be reworked -- I don't
|
|
// recall what it was doing; maybe getting a list of all identities
|
|
// sorted with distance to this faceId?
|
|
console.log("Looking up score against: " + req.query.withScore);
|
|
|
|
await Promise.map(identities, async (identity) => {
|
|
const descriptors = photoDB.sequelize.query(
|
|
"SELECT id FROM facedescriptors " +
|
|
"WHERE descriptorId " +
|
|
"IN (:id,:descriptorIds)", {
|
|
replacements: {
|
|
id: parseInt(req.query.withScore),
|
|
descriptorIds: identity.relatedFaces.map(
|
|
face => parseInt(face.faceId))
|
|
},
|
|
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
raw: true
|
|
});
|
|
let target;
|
|
for (let i = 0; i < descriptors.length; i++) {
|
|
if (descriptors[i].descriptorId == req.query.withScore) {
|
|
target = descriptors[i].descriptors;
|
|
break;
|
|
}
|
|
}
|
|
if (!target) {
|
|
console.warn("Could not find descriptor for requested face: " + req.query.withScore);
|
|
return;
|
|
}
|
|
|
|
/* For each face's descriptor returned for this identity, compute the distance between the
|
|
* requested photo and that face descriptor */
|
|
descriptors.forEach((descriptor) => {
|
|
for (let i = 0; i < identity.relatedFaces.length; i++) {
|
|
if (identity.relatedFaces[i].faceId == descriptor.faceId) {
|
|
identity.relatedFaces[i].distance = euclideanDistance(target, descriptor.descriptors);
|
|
identity.relatedFaces[i].descriptors = descriptor.descriptors;
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
}, {
|
|
concurrency: 5
|
|
});
|
|
|
|
return res.status(200).json(identities);
|
|
});
|
|
|
|
module.exports = router;
|