Identity editor

Signed-off-by: James Ketrenos <james.p.ketrenos@intel.com>
This commit is contained in:
James Ketrenos 2020-01-20 15:28:26 -08:00
parent b7ed9f05f1
commit 33bb8d04e2
2 changed files with 257 additions and 121 deletions

246
frontend/identities.html Normal file → Executable file
View File

@ -12,28 +12,7 @@
6 Create new Identity
*/
function loadMore(index) {
var clusterBlock = document.body.querySelector("[cluster-index='" + index + "']");
if (!clusterBlock) {
return;
}
var faces = clusterBlock.querySelectorAll("div.face").length, i
for (i = faces; i < clusters[index].length; i++) {
if (i - faces > 10) {
return;
}
var tuple = clusters[index][i],
face = createFace(tuple[0], tuple[1]);
clusterBlock.appendChild(face);
}
if (i == clusters[index].length) {
var span = clusterBlock.querySelector("span.more");
if (span) {
span.parentElement.removeChild(span);
}
}
}
function createFace(faceId, photoId, selectable) {
var div = document.createElement("div");
@ -75,88 +54,115 @@ function shuffle(array) {
}
document.addEventListener("DOMContentLoaded", (event) => {
window.fetch("api/v1/faces").then(res => res.json()).then((faces) => {
const block = document.createElement("div");
block.id = "face-editor";
block.classList.add("block");
faces.forEach((face) => {
const editor = document.createElement("div");
editor.classList.add("editor");
editor.appendChild(createFace(face.id, face.photoId));
for (let key in face) {
const row = document.createElement("div"),
left = document.createElement("div");
row.classList.add("editor-row");
left.textContent = key;
let right;
if (key == "relatedFaces") {
right = document.createElement("div");
right.classList.add("related-faces");
face.relatedFaces.forEach((face) => {
right.appendChild(createFace(face.faceId, face.photoId, true));
});
} else {
right = document.createElement("input");
right.value = face[key];
}
row.appendChild(left);
row.appendChild(right);
editor.appendChild(row);
}
block.appendChild(editor);
});
document.body.appendChild(block);
getIdentities();
});
loadFace();
});
function getIdentities() {
const el = document.getElementById("identities");
if (el) {
el.parentElement.removeChild(el);
}
function loadFace(id) {
const faceEditorBlock = document.getElementById("face-editor");
faceEditorBlock.innerHTML = "";
window.fetch("api/v1/identities").then(res => res.json()).then((identities) => {
const block = document.createElement("div");
block.id = "identities";
block.classList.add("block");
window.fetch("api/v1/faces" + (id ? "/" + id : "")).then(res => res.json()).then((faces) => {
if (faces.length == 0) {
return;
}
identities.forEach((identity) => {
const editor = document.createElement("div");
editor.classList.add("editor");
// editor.appendChild(createFace(face.id, face.photoId));
for (let key in face) {
const row = document.createElement("div"),
left = document.createElement("div");
row.classList.add("editor-row");
left.textContent = key;
let right;
if (key == "relatedFaces") {
right = document.createElement("div");
face.relatedFaces.forEach((face) => {
right.appendChild(createFace(face.faceId, face.photoId));
});
} else {
right = document.createElement("input");
right.value = face[key];
}
row.appendChild(left);
row.appendChild(right);
editor.appendChild(row);
const face = faces[0];
getIdentities(face.id);
const editor = document.createElement("div");
editor.classList.add("editor");
editor.appendChild(createFace(face.id, face.photoId));
for (let key in face) {
const row = document.createElement("div"),
left = document.createElement("div");
row.classList.add("editor-row");
left.textContent = key;
let right;
if (key == "relatedFaces") {
right = document.createElement("div");
right.classList.add("related-faces");
face.relatedFaces.forEach((face) => {
right.appendChild(createFace(face.faceId, face.photoId, true));
});
} else {
right = document.createElement("input");
right.value = face[key];
}
block.appendChild(editor);
row.appendChild(left);
row.appendChild(right);
editor.appendChild(row);
}
faceEditorBlock.appendChild(editor);
});
}
function getIdentities(id) {
const identitiesBlock = document.getElementById("identities");
identitiesBlock.innerHTML = "";
const search = id ? "?withScore=" + id : "";
window.fetch("api/v1/identities" + search).then(res => res.json()).then((identities) => {
identitiesBlock.appendChild(createNewIdenityEditor());
identities.sort((a, b) => {
if (a.lastName == b.lastName) {
return a.firstName.localeCompare(b.firstName);
}
return a.lastName.localeCompare(b.lastName);
});
block.appendChild(createNewIdenityEditor());
identities.forEach((identity) => {
const block = document.createElement("div");
block.classList.add("block");
document.body.appendChild(block);
const button = document.createElement("button");
button.textContent = "Use this identity";
block.appendChild(button);
button.addEventListener("click", (event) => {
const object = {
faces: []
};
Array.prototype.forEach.call(document.body.querySelectorAll("#face-editor .face"), (face) => {
if (!face.hasAttribute("disabled")) {
object.faces.push(face.getAttribute("face-id"));
}
});
window.fetch("api/v1/identities/faces/add/" + identity.id, {
method: "PUT",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(object)
}).then(res => res.json()).then((identities) => {
console.log("Updated identity: ", identities[0]);
const face = document.body.querySelector("#face-editor .face");
loadFace(parseInt(face.getAttribute("face-id")));
});
});
let div = document.createElement("div");
div.textContent = identity.name;
block.appendChild(div);
div = document.createElement("div");
div.textContent = "Related faces " + identity.relatedFaces.length;
block.appendChild(div);
const random = Math.floor(Math.random() * identity.relatedFaces.length);
const facePhoto = createFace(identity.relatedFaces[random].faceId, identity.relatedFaces[random].photoId);
const distance = document.createElement("div");
distance.classList.add("distance");
distance.textContent = Math.round(100 * identity.relatedFaces[random].distance) / 100;
facePhoto.appendChild(distance);
block.appendChild(facePhoto);
identitiesBlock.appendChild(block);
});
});
}
@ -198,8 +204,9 @@ function createNewIdenityEditor() {
},
body: JSON.stringify(object)
}).then(res => res.json()).then((identities) => {
console.log("Identities: ", identities);
getIdentities();
console.log("New identity: ", identities[0]);
const face = document.body.querySelector("#face-editor .face");
loadFace(parseInt(face.getAttribute("face-id")));
});
});
editor.appendChild(button);
@ -219,6 +226,21 @@ body {
cursor: pointer;
}
#identities {
display: flex;
flex-wrap: wrap;
border: 1px solid black;
padding: 0.25em;
margin: 0.25em;
}
#identities > div {
border: 1px solid black;
margin: 0.25em;
padding: 0.25em;
box-sizing: border-box;
}
.more:hover {
text-decoration: underline;
}
@ -228,6 +250,7 @@ body {
}
.face {
position: relative;
max-width: 128px;
max-height: 128px;
width: 128px;
@ -244,6 +267,16 @@ body {
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.5);
}
.face .distance {
position: absolute;
background: black;
opacity: 0.8;
text-align: center;
left: 0;
right: 0;
bottom: 0;
}
.editor-row {
display: flex;
flex-direction: row;
@ -255,24 +288,7 @@ body {
}
</style>
<script>
var clusters = [
/* 1. */ [[738,229],[739,230],[740,231],[741,233],[742,237],[743,238],[749,242],[751,244],[752,245],[754,247],[755,248],[756,249],[758,256],[760,258],[762,261],[764,263],[765,265],[766,267],[767,268],[770,270],[772,272],[774,274],[775,277],[776,282],[777,285],[779,287],[780,288],[781,290],[783,292],[784,294],[786,298],[787,299],[788,300]],
/* 2. */ [[791,309],[792,315],[793,316],[797,333],[798,335],[800,350],[802,353]],
/* 3. */ [[805,360],[806,362],[807,363],[808,374]],
/* 4. */ [[848,511],[849,511],[851,512],[852,512]],
/* 5. */ [[915,651],[916,654],[918,656],[919,659]],
/* 6. */ [[954,757],[955,758],[957,770],[959,780],[960,781],[961,796],[962,799],[963,800],[964,805],[982,891],[986,908],[988,916],[990,922],[993,936],[995,941],[998,951],[1000,957],[1003,961],[1004,962],[1005,965],[1009,976],[1013,988],[1015,991],[1018,996],[1023,1021],[1027,1038],[1028,1039],[1029,1044],[1033,1050],[1034,1052],[1039,1066],[1040,1068],[1041,1072],[1045,1078],[1046,1084],[1051,1089],[1052,1091],[1054,1097],[1059,1106],[1060,1107],[1102,1200],[1103,1204],[1104,1205],[1105,1206],[1106,1206],[1107,1208],[1108,1212],[1110,1222],[1113,1231],[1115,1236],[1116,1242],[1117,1242],[1121,1248],[1125,1255],[1126,1256],[1127,1258],[1128,1261],[1131,1267],[1135,1279],[1136,1282],[1138,1284],[1139,1292],[1140,1295],[1141,1296],[1142,1297],[1145,1299],[1146,1300],[1148,1302],[1150,1306],[1152,1308],[1153,1314],[1155,1329],[1156,1330],[1157,1336],[1158,1337],[1159,1348],[1162,1352],[1163,1353],[1164,1356],[1165,1361],[1166,1364],[1167,1367],[1168,1369],[1171,1372],[1172,1373],[1173,1376],[1174,1377],[1175,1378],[1176,1379],[1177,1379],[1178,1380],[1179,1382],[1180,1383],[1181,1385],[1182,1390],[1185,1418],[1186,1424],[1187,1425],[1188,1426],[1189,1434],[1190,1437],[1191,1438],[1194,1440],[1195,1441],[1196,1443],[1197,1444],[1200,1452],[1201,1457],[1325,1826],[1326,1833],[1327,1834],[1328,1836],[1329,1837],[1359,1918],[1360,1919],[1362,1921],[1363,1922],[1364,1923]],
/* 7. */ [[985,904],[989,918],[999,957],[1010,984],[1012,988],[1014,989],[1024,1024],[1037,1056],[1038,1058],[1044,1078],[1047,1084],[1050,1089],[1053,1096],[1055,1097],[1056,1098]],
/* 8. */ [[1035,182],[1380,112],[1381,120],[1382,121],[1384,124],[1385,125],[1386,128],[1387,129],[1392,177],[1393,178],[1394,179],[1395,180]],
/* 9. */ [[785,295],[908,629],[1065,1135],[1068,1145],[1070,1152],[1071,1154],[1072,1155],[1075,1165],[1076,1166],[1077,1167],[1078,1168]],
/* 10. */ [[1091,1471],[1219,1757],[1222,1762],[1223,1764],[1224,1766],[1226,1774],[1227,1775],[1228,1776]],
/* 11. */ [[1208,1506],[1210,1530],[1211,1533],[1213,1541]],
/* 12. */ [[1233,1556],[1234,1557],[1236,1559],[1237,1563],[1240,1570],[1241,1571],[1243,1583],[1245,1590],[1247,1593],[1248,1594],[1249,1596],[1251,1600],[1253,1602],[1254,1604],[1255,1605],[1256,1607],[1257,1608],[1259,1610],[1260,1611],[1261,1615],[1263,1619],[1266,1638],[1267,1639],[1269,1646],[1271,1661],[1276,1665],[1277,1666],[1278,1667],[1280,1670],[1281,1672],[1284,1678],[1285,1679],[1287,1684],[1288,1685],[1289,1686],[1291,1688],[1292,1690],[1293,1693],[1295,1712],[1296,1715],[1297,1716],[1298,1717],[1300,1722],[1302,1725],[1303,1726],[1304,1728],[1306,1730],[1308,1733]],
/* 13. */ [[1143,1298],[1160,1351],[1192,1439],[1310,1789]],
/* 14. */ [[1332,1860],[1333,1860],[1335,1861],[1336,1861]],
/* 15. */ [[935,705],[1342,1887],[1343,1888],[1347,1902],[1350,1906],[1352,1908],[1353,1909],[1354,1910],[1356,1912],[1358,1915],[1367,1931],[1368,10],[1369,9]]
];
</script>
<body>
<div id="face-editor" class="block"></div>
<div id="identities" class="block"></div>
</body>

132
server/routes/identities.js Normal file → Executable file
View File

@ -10,13 +10,84 @@ require("../db/photos").then(function(db) {
const router = express.Router();
router.put("/faces/add/:id", (req, res) => {
if (!req.user.maintainer) {
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,identityDistance=0 " +
"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) => {
console.log(req.body);
return res.status(200).json({});
if (!req.user.maintainer) {
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,
}).spread(function(results, metadata) {
identity.id = metadata.lastID;
return photoDB.sequelize.query("UPDATE faces SET identityId=:identityId,identityDistance=0 " +
"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.");
});
});
router.get("/:id?", (req, res) => {
let id;
console.log("here");
if (req.params.id) {
id = parseInt(req.params.id);
if (id != req.params.id) {
@ -24,16 +95,65 @@ router.get("/:id?", (req, res) => {
}
}
const filter = id ? "WHERE id=:id" : "";
const filter = id ? "WHERE id=:id " : "";
return photoDB.sequelize.query(`SELECT * FROM identities ${filter}`, {
return photoDB.sequelize.query("SELECT " +
"identities.*," +
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds " +
"FROM identities " +
"INNER JOIN faces ON identities.id=faces.identityId " +
filter +
"GROUP BY identities.id", {
replacements: {
id: id
},
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then((results) => {
return res.status(200).json(results);
}).then((identities) => {
console.log("asdf");
identities.forEach((identity) => {
const relatedFaces = identity.relatedFaceIds.split(","),
relatedFacePhotos = identity.relatedFacePhotoIds.split(",");
if (relatedFaces.length != relatedFacePhotos.length) {
console.warn("Face ID to Photo ID mapping doesn't match!");
}
delete identity.relatedFaceIds;
delete identity.relatedFacePhotoIds;
identity.relatedFaces = relatedFaces.map((faceId, index) => {
return {
faceId: faceId,
photoId: relatedFacePhotos[index]
};
});
});
if (!req.query.withScore) {
console.log("No score request.");
return identities;
}
console.log("Looking up scores.");
return photoDB.sequelize.query("SELECT * FROM facedescriptor WHERE faceId IN (:faceIds)", {
replacements: {
faceIds: relatedFaces
},
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then((descriptors) => {
descriptors.forEach((descriptor) => {
for (let i = 0; i < identity.relatedFaces.length; i++) {
if (identity.relatedFaces[i].faceId == descriptor.faceId) {
identity.relatedFaces[i].descriptors = descriptor.descriptors;
return;
}
}
});
return identities;
});
}).then((identities) => {
return res.status(200).json(identities);
}).catch((error) => {
console.error(error);
return res.status(500).send("Error processing request.");