Identity classification is editable

Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
James Ketrenos 2020-02-15 21:10:22 -08:00
parent d562beef8e
commit a0bdecf481
4 changed files with 158 additions and 70 deletions

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<link rel="icon" href="./frontend/icons-192.png" sizes="192x192"> <link rel="icon" href="./frontend/icons-192.png" sizes="192x192">
<base href="/chalk/"> <base href="/photos/">
<style> <style>
body { body {
background-image: linear-gradient(#090B1A, #131524); background-image: linear-gradient(#090B1A, #131524);

View File

@ -29,7 +29,15 @@ router.delete("/:id", function(req, res/*, next*/) {
return res.status(401).send("Unauthorized to delete photos."); return res.status(401).send("Unauthorized to delete photos.");
} }
return res.status(400).send("Invalid request"); return photoDB.sequelize.query("UPDATE faces SET faceConfidence=0 WHERE id=:id", {
replacements: {
id: req.params.id
}
}).then(() => {
return res.status(200).send({ message: `${req.params.id} deleted` });
}).catch((error) => {
return res.status(500).send({ error: error });
})
}); });
function getFacesForPhoto(id) { function getFacesForPhoto(id) {
@ -99,6 +107,9 @@ router.get("/:id?", (req, res) => {
type: photoDB.Sequelize.QueryTypes.SELECT, type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true raw: true
}).then((results) => { }).then((results) => {
if (!results[0].count) {
return [];
}
const random = Math.floor(Math.random() * results[0].count); const random = Math.floor(Math.random() * results[0].count);
return photoDB.sequelize.query( return photoDB.sequelize.query(
"SELECT * FROM faces WHERE faceConfidence>=0.9 AND identityId IS NULL ORDER BY id LIMIT :index,1", { "SELECT * FROM faces WHERE faceConfidence>=0.9 AND identityId IS NULL ORDER BY id LIMIT :index,1", {
@ -112,6 +123,9 @@ router.get("/:id?", (req, res) => {
} }
return promise.then((faces) => { return promise.then((faces) => {
if (!faces.length) {
return [];
}
console.log("Looking up " + faces.map(face => face.id).join(",")); console.log("Looking up " + faces.map(face => face.id).join(","));
return photoDB.sequelize.query( return photoDB.sequelize.query(
"SELECT relatedFaces.photoId AS photoId,fd.face1Id,fd.face2Id,fd.distance,relatedFaces.faceConfidence " + "SELECT relatedFaces.photoId AS photoId,fd.face1Id,fd.face2Id,fd.distance,relatedFaces.faceConfidence " +

View File

@ -486,6 +486,27 @@ router.delete("/:id?", function(req, res/*, next*/) {
return res.status(404).send("Unable to find photo " + replacements.id); return res.status(404).send("Unable to find photo " + replacements.id);
} }
return photoDB.sequelize.query(
"SELECT id FROM faces WHERE photoId IN (:photos)", {
replacements: photos.map(photo => photo.id),
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then((faces) => {
const faceIds = faces.map(face => face.id);
return photoDB.sequelize.query(
"DELETE FROM facedistances WHERE face1Id IN (:ids) OR face2Id IN (:ids)", {
replacements: {
ids: faceIds
}
}).then(() => {
return photoDB.sequelize.query(
"DELETE FROM faces WHERE id IN (:ids)", {
replacements: {
ids: faceIds
}
});
});
}).then(() => {
if (!req.query.permanent) { if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", { return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
replacements: replacements replacements: replacements
@ -531,6 +552,7 @@ router.delete("/:id?", function(req, res/*, next*/) {
removeTask(task); removeTask(task);
}); });
}); });
});
}).catch(function(error) { }).catch(function(error) {
console.log(error); console.log(error);
if (!sent) { if (!sent) {
@ -875,7 +897,7 @@ router.get("/random/:id?", (req, res) => {
}); });
}).then(function(photos) { }).then(function(photos) {
if (!photos.length) { if (!photos.length) {
return res.status(404).send(id + " not found."); return res.status(404).send({ message: id + " not found." });
} }
const photo = photos[0]; const photo = photos[0];
for (var key in photo) { for (var key in photo) {

View File

@ -64,22 +64,28 @@ class Identities extends React.Component {
componentDidMount() { componentDidMount() {
let params = window.location.search ? window.location.search.replace(/^\?/, "").split("&") : [], let params = window.location.search ? window.location.search.replace(/^\?/, "").split("&") : [],
face; face, identity;
for (let i in params) { for (let i in params) {
let parts = params[i].split("="); let parts = params[i].split("=");
switch (parts[0]) { switch (parts[0]) {
case "face": case "face":
face = parseInt(parts[1]); face = parseInt(parts[1]);
if (face != parts[1]) { if (face == parts[1]) {
face = undefined; loadFace(face);
}
break;
case "identity":
identity = parseInt(parts[1]);
if (identity == parts[1]) {
getIdentity(identity);
} }
break; break;
} }
} }
loadFace(face); if (!face && !identity) {
const newIdentity = document.getElementById("new-identity"); loadFace();
newIdentity.appendChild(createNewIdentityEditor()); }
} }
render() { render() {
@ -123,9 +129,11 @@ class App extends React.Component {
<div className="Body"> <div className="Body">
<div className="Main"> <div className="Main">
<Switch> <Switch>
<Route path="/identities" render={ (props) => <Identities {...props}/> }/>
<Route path={ base + "identities" } render={ (props) => <Identities {...props}/> }/> <Route path={ base + "identities" } render={ (props) => <Identities {...props}/> }/>
<Route path="/faces" render={ props => <div {...props}/> }/> <Route path="/faces" render={ props => <div {...props}/> }/>
<Route path="/photos" render={ props => <div {...props}/> }/> <Route path="/photos" render={ props => <div {...props}/> }/>
<Route>{base + "identities"}</Route>
</Switch> </Switch>
</div> </div>
</div> </div>
@ -283,20 +291,21 @@ function loadFace(id) {
window.fetch("api/v1/faces" + (id ? "/" + id : "")).then(res => res.json()).then((faces) => { window.fetch("api/v1/faces" + (id ? "/" + id : "")).then(res => res.json()).then((faces) => {
if (faces.length == 0) { if (faces.length == 0) {
getIdentities();
return; return;
} }
const newIdentity = document.getElementById("new-identity");
netIdentity.innerHTML = "";
newIdentity.appendChild(createNewIdentityEditor());
const face = faces[0]; const face = faces[0];
window.fetch("api/v1/photos/random/" + face.photoId).then(res => res.json()).then((photo) => { window.fetch("api/v1/photos/random/" + face.photoId).then(res => res.json()).then((photo) => {
loadPhoto(photo); loadPhoto(photo);
}); });
if (face.identityId) {
getIdentity(face.identityId);
} else {
getIdentities(face.id); getIdentities(face.id);
}
const editor = document.createElement("div"); const editor = document.createElement("div");
editor.classList.add("editor"); editor.classList.add("editor");
@ -366,11 +375,17 @@ function getIdentity(identityId) {
const identitiesBlock = document.getElementById("identities"); const identitiesBlock = document.getElementById("identities");
identitiesBlock.innerHTML = ""; identitiesBlock.innerHTML = "";
/* For each identity, create the identityBlock */
window.fetch("api/v1/identities/" + identityId).then(res => res.json()).then((identities) => { window.fetch("api/v1/identities/" + identityId).then(res => res.json()).then((identities) => {
identities.forEach((identity) => { identities.forEach((identity) => {
const block = createIdentityBlock(identity, true), const block = createIdentityBlock(identity, true);
button = createUseThisIdentityButton(identity.id);
/* If face is being edited, show the 'use this identity' button */
if (!document.body.querySelector("#face-editor .face")) {
const button = createUseThisIdentityButton(identity.id);
block.insertBefore(button, block.firstChild); block.insertBefore(button, block.firstChild);
}
identitiesBlock.appendChild(block); identitiesBlock.appendChild(block);
}) })
}); });
@ -403,12 +418,21 @@ function getIdentities(faceId) {
} }
}); });
const block = createIdentityBlock(identity), const block = createIdentityBlock(identity);
button = createUseThisIdentityButton(identity.id);
/* If face is being edited, show the 'use this identity' */
if (document.body.querySelector("#face-editor .face")) {
const button = createUseThisIdentityButton(identity.id);
block.insertBefore(button, block.firstChild); block.insertBefore(button, block.firstChild);
}
identitiesBlock.appendChild(block); identitiesBlock.appendChild(block);
}); });
/* If no face is being edited, don't show the 'best match' */
if (!document.body.querySelector("#face-editor .face")) {
return;
}
const bestMatch = document.getElementById("best-match"); const bestMatch = document.getElementById("best-match");
const buttonBlock = createActionButtons(); const buttonBlock = createActionButtons();
@ -422,7 +446,10 @@ function getIdentities(faceId) {
distance.textContent = minDistance.distance.toFixed(2); distance.textContent = minDistance.distance.toFixed(2);
facePhoto.appendChild(distance); facePhoto.appendChild(distance);
bestMatch.appendChild(facePhoto); bestMatch.appendChild(facePhoto);
/* If no face is being edited, don't show the 'use this identity' button */
if (document.body.querySelector("#face-editor .face")) {
buttonBlock.insertBefore(createUseThisIdentityButton(minDistance.identity.id), buttonBlock.firstChild); buttonBlock.insertBefore(createUseThisIdentityButton(minDistance.identity.id), buttonBlock.firstChild);
}
} else { } else {
bestMatch.innerHTML = "No best guess for match."; bestMatch.innerHTML = "No best guess for match.";
} }
@ -454,8 +481,9 @@ function createUseThisIdentityButton(identityId) {
body: JSON.stringify(object) body: JSON.stringify(object)
}).then(res => res.json()).then((identities) => { }).then(res => res.json()).then((identities) => {
console.log("Updated identity: ", identities[0]); console.log("Updated identity: ", identities[0]);
const face = document.body.querySelector("#face-editor .face"); const identitiesBlock = document.getElementById("identities");
loadFace(parseInt(face.getAttribute("face-id"))); identitiesBlock.innerHTML = "";
loadFace();
}); });
}); });
return button; return button;
@ -463,7 +491,11 @@ function createUseThisIdentityButton(identityId) {
function createActionButtons() { function createActionButtons() {
const buttonBlock = document.createElement("div"); const buttonBlock = document.createElement("div"),
face = document.body.querySelector("#face-editor .face"),
faceId = parseInt(face.getAttribute("face-id")),
photoId = parseInt(face.getAttribute("photo-id"));
let button = document.createElement("button"); let button = document.createElement("button");
button = document.createElement("button"); button = document.createElement("button");
@ -478,13 +510,29 @@ function createActionButtons() {
button = document.createElement("button"); button = document.createElement("button");
button.textContent = "Not a face"; button.textContent = "Not a face";
button.addEventListener("click", (event) => { button.addEventListener("click", (event) => {
window.fetch("api/v1/face", { window.fetch("api/v1/faces/" + faceId, {
method: "DELETE", method: "DELETE",
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, }
body: JSON.stringify({ faceId: faceId }) }).then(res => res.json()).then(() => {
const identitiesBlock = document.getElementById("identities");
identitiesBlock.innerHTML = "";
loadFace();
});
});
buttonBlock.appendChild(button);
button = document.createElement("button");
button.textContent = "Delete photo";
button.addEventListener("click", (event) => {
window.fetch("api/v1/photos/" + photoId, {
method: "DELETE",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(res => res.json()).then(() => { }).then(res => res.json()).then(() => {
const identitiesBlock = document.getElementById("identities"); const identitiesBlock = document.getElementById("identities");
identitiesBlock.innerHTML = ""; identitiesBlock.innerHTML = "";
@ -500,13 +548,17 @@ function createIdentityBlock(identity, nolimit) {
const block = document.createElement("div"); const block = document.createElement("div");
block.classList.add("block"); block.classList.add("block");
let div = document.createElement("div"); let div = document.createElement("div"),
a = document.createElement("a");
div.appendChild(a);
a.target = "identities?identity-" + identity.id;
a.href = "identities?identity=" + identity.id;
if (identity.lastName && identity.firstName) { if (identity.lastName && identity.firstName) {
div.textContent = `${identity.lastName}, ${identity.firstName}`; a.textContent = `${identity.lastName}, ${identity.firstName}`;
} else if (identity.lastName) { } else if (identity.lastName) {
div.textContent = identity.lastName; a.textContent = identity.lastName;
} else { } else {
div.textContent = identity.name; a.textContent = identity.name;
} }
block.appendChild(div); block.appendChild(div);