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>
<meta charset="utf-8">
<link rel="icon" href="./frontend/icons-192.png" sizes="192x192">
<base href="/chalk/">
<base href="/photos/">
<style>
body {
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(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) {
@ -99,6 +107,9 @@ router.get("/:id?", (req, res) => {
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then((results) => {
if (!results[0].count) {
return [];
}
const random = Math.floor(Math.random() * results[0].count);
return photoDB.sequelize.query(
"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) => {
if (!faces.length) {
return [];
}
console.log("Looking up " + faces.map(face => face.id).join(","));
return photoDB.sequelize.query(
"SELECT relatedFaces.photoId AS photoId,fd.face1Id,fd.face2Id,fd.distance,relatedFaces.faceConfidence " +

View File

@ -486,49 +486,71 @@ router.delete("/:id?", function(req, res/*, next*/) {
return res.status(404).send("Unable to find photo " + replacements.id);
}
if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
replacements: replacements
}).then(function() {
sent = true;
return res.status(200).send({
id: req.params.id,
deleted: true,
permanent: (req.query && req.query.permanent) || false
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
}
});
});
}
/**
* Delete the asset from disk and the DB
*/
return Task().then(function(task) {
task.remaining = photos.length;
task.eta = -1;
}).then(() => {
if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
replacements: replacements
}).then(function() {
sent = true;
return res.status(200).send({
id: req.params.id,
deleted: true,
permanent: (req.query && req.query.permanent) || false
});
});
}
/**
* Delete the asset from disk and the DB
*/
return Task().then(function(task) {
task.remaining = photos.length;
task.eta = -1;
sent = true;
res.status(200).send({
id: req.params.id,
task: task,
permanent: (req.query && req.query.permanent) || false
});
sent = true;
res.status(200).send({
id: req.params.id,
task: task,
permanent: (req.query && req.query.permanent) || false
});
return Promise.mapSeries(photos, function(photo) {
let lastStamp = Date.now();
return deletePhoto(photo).then(function() {
let now = Date.now(), elapsed = now - lastStamp;
lastStamp= now;
task.processed++;
task.remaining--;
task.lastUpdate = Date.now();
task.eta = elapsed * task.remaining;
})
}).then(function() {
console.log("Processing task " + task.token + " finished: " + (task.lastUpdate - task.started));
removeTask(task);
}).catch(function(error) {
console.log("Processing task " + task.token + " failed: " + error);
removeTask(task);
return Promise.mapSeries(photos, function(photo) {
let lastStamp = Date.now();
return deletePhoto(photo).then(function() {
let now = Date.now(), elapsed = now - lastStamp;
lastStamp= now;
task.processed++;
task.remaining--;
task.lastUpdate = Date.now();
task.eta = elapsed * task.remaining;
})
}).then(function() {
console.log("Processing task " + task.token + " finished: " + (task.lastUpdate - task.started));
removeTask(task);
}).catch(function(error) {
console.log("Processing task " + task.token + " failed: " + error);
removeTask(task);
});
});
});
}).catch(function(error) {
@ -875,7 +897,7 @@ router.get("/random/:id?", (req, res) => {
});
}).then(function(photos) {
if (!photos.length) {
return res.status(404).send(id + " not found.");
return res.status(404).send({ message: id + " not found." });
}
const photo = photos[0];
for (var key in photo) {

View File

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