Identity classification is editable
Signed-off-by: James Ketrenos <james_gitlab@ketrenos.com>
This commit is contained in:
parent
d562beef8e
commit
a0bdecf481
@ -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);
|
||||
|
@ -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 " +
|
||||
|
@ -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) {
|
||||
|
108
src/App.js
108
src/App.js
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user