diff --git a/docker-compose.yml b/docker-compose.yml index 9a284bc..c29e695 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,5 +18,6 @@ services: - ${PWD}/config/local.json:/website/config/local.json - /opt/ketrface/models:/root/.deepface # - ${PWD}:/website + - ${PWD}/ketrface:/website/ketrface - ${PWD}/frontend:/website/frontend - ${PWD}/server:/website/server diff --git a/frontend/clusters.html b/frontend/clusters.html deleted file mode 100644 index bb72a25..0000000 --- a/frontend/clusters.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - diff --git a/frontend/identities.html b/frontend/identities.html index 1d0e3e2..9c3bfe7 100755 --- a/frontend/identities.html +++ b/frontend/identities.html @@ -165,7 +165,7 @@ function getIdentities(faceId) { const identitiesBlock = document.getElementById("identities"); identitiesBlock.innerHTML = ""; - const search = faceId ? "?withScore=" + faceId : ""; + const search = ''; //faceId ? "?withScore=" + faceId : ""; window.fetch("api/v1/identities" + search).then(res => res.json()).then((identities) => { identities.sort((a, b) => { if (a.lastName == b.lastName) { diff --git a/ketrface/cluster.py b/ketrface/cluster.py index 57926c0..95d8c00 100644 --- a/ketrface/cluster.py +++ b/ketrface/cluster.py @@ -197,18 +197,19 @@ straglers = build_straglers(faces) reduced = reduced + DBSCAN(straglers) # Build a final cluster with all remaining uncategorized faces -remaining_cluster = { - 'id': len(reduced) + 1, - 'distance': 0, - 'descriptors': [], - 'cluster': Undefined, - 'faces': [] -} -straglers = build_straglers(faces) -for face in straglers: - face['cluster'] = remaining_cluster - remaining_cluster['faces'].append(face) -reduced.append(remaining_cluster) +if False: + remaining_cluster = { + 'id': len(reduced) + 1, + 'distance': 0, + 'descriptors': [], + 'cluster': Undefined, + 'faces': [] + } + straglers = build_straglers(faces) + for face in straglers: + face['cluster'] = remaining_cluster + remaining_cluster['faces'].append(face) + reduced.append(remaining_cluster) # Give all merged identity lists a unique ID for id, identity in enumerate(reduced): @@ -241,3 +242,46 @@ print('Writing to "auto-clusters.html"') redirect_on(os.path.join(html_path, 'auto-clusters.html')) gen_html(reduced) redirect_off() + +def create_identity(conn, identity): + """ + Create a new identity in the identities table + :param conn: + :param identity: + :return: identity id + """ + sql = ''' + INSERT INTO identities(descriptors,displayName) + VALUES(?,?) + ''' + cur = conn.cursor() + cur.execute(sql, ( + np.array(identity['descriptors']), + f'cluster-{identity["id"]}' + )) + conn.commit() + return cur.lastrowid + +def update_face_identity(conn, faceId, identityId = None): + """ + Update the identity associated with this face + :param conn: + :param faceId: + :param identityId: + :return: None + """ + sql = ''' + UPDATE faces SET identityId=? WHERE id=? + ''' + cur = conn.cursor() + cur.execute(sql, (identityId, faceId)) + conn.commit() + return None + +print(f'Connecting to database: {db_path}') +conn = create_connection(db_path) +with conn: + for identity in reduced: + id = create_identity(conn, identity) + for face in identity['faces']: + update_face_identity(conn, face['id'], id) diff --git a/ketrface/ketrface/.gitignore b/ketrface/ketrface/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/ketrface/ketrface/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/ketrface/ketrface/__pycache__/__init__.cpython-310.pyc b/ketrface/ketrface/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 1faa59e..0000000 Binary files a/ketrface/ketrface/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/ketrface/ketrface/__pycache__/db.cpython-310.pyc b/ketrface/ketrface/__pycache__/db.cpython-310.pyc deleted file mode 100644 index 057e651..0000000 Binary files a/ketrface/ketrface/__pycache__/db.cpython-310.pyc and /dev/null differ diff --git a/ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc b/ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc deleted file mode 100644 index 8d11d0b..0000000 Binary files a/ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc and /dev/null differ diff --git a/ketrface/ketrface/__pycache__/util.cpython-310.pyc b/ketrface/ketrface/__pycache__/util.cpython-310.pyc deleted file mode 100644 index a196fda..0000000 Binary files a/ketrface/ketrface/__pycache__/util.cpython-310.pyc and /dev/null differ diff --git a/server/routes/identities.js b/server/routes/identities.js index ebde173..b264847 100755 --- a/server/routes/identities.js +++ b/server/routes/identities.js @@ -26,7 +26,8 @@ router.put("/faces/add/:id", (req, res) => { return res.status(400).send("No faces supplied."); } - return photoDB.sequelize.query("UPDATE faces SET identityId=:identityId,identityDistance=0 " + + return photoDB.sequelize.query( + "UPDATE faces SET identityId=:identityId " + "WHERE id IN (:faceIds)", { replacements: { identityId: id, @@ -71,7 +72,8 @@ router.post("/", (req, res) => { replacements: identity, }).then(([ results, metadata ]) => { identity.id = metadata.lastID; - return photoDB.sequelize.query("UPDATE faces SET identityId=:identityId,identityDistance=0 " + + return photoDB.sequelize.query( + "UPDATE faces SET identityId=:identityId " + "WHERE id IN (:faceIds)", { replacements: { identityId: identity.id, @@ -102,7 +104,7 @@ function euclideanDistance(a, b) { return Math.sqrt(sum); } -router.get("/:id?", (req, res) => { +router.get("/:id?", async (req, res) => { let id; if (req.params.id) { @@ -114,7 +116,7 @@ router.get("/:id?", (req, res) => { const filter = id ? "WHERE identities.id=:id " : ""; - return photoDB.sequelize.query("SELECT " + + const identities = await photoDB.sequelize.query("SELECT " + "identities.*," + "GROUP_CONCAT(faces.id) AS relatedFaceIds," + "GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds," + @@ -127,79 +129,88 @@ router.get("/:id?", (req, res) => { replacements: { id }, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true - }).then((identities) => { - identities.forEach((identity) => { - const relatedFaces = identity.relatedFaceIds.split(","), - relatedFacePhotos = identity.relatedFacePhotoIds.split(","), - relatedIdentityDistances = identity.relatedIdentityDistances.split(","); - if (relatedFaces.length != relatedFacePhotos.length) { - console.warn("Face ID to Photo ID mapping doesn't match!"); + }); + + identities.forEach((identity) => { + [ 'firstName', 'middleName', 'lastName' ].forEach(key => { + if (!identity[key]) { + identity[key] = ''; } - delete identity.relatedFaceIds; - delete identity.relatedFacePhotoIds; - identity.relatedFaces = relatedFaces.map((faceId, index) => { - const distance = euclideanDistance( - relatedIdentityDistances[index], - identity.descriptors - ); - return { - faceId: faceId, - photoId: relatedFacePhotos[index], - distance - }; - }); }); - if (!req.query.withScore) { - console.log("No score request."); - return identities; - } - console.log("Looking up score against: " + req.query.withScore); + const relatedFaces = identity.relatedFaceIds.split(","), + relatedFacePhotos = identity.relatedFacePhotoIds.split(","), + relatedIdentityDescriptors = + identity.relatedIdentityDescriptors.split(","); - return Promise.map(identities, (identity) => { - return photoDB.sequelize.query("SELECT * FROM facedescriptors WHERE faceId IN (:id,:faceIds)", { - replacements: { - id: parseInt(req.query.withScore), - faceIds: identity.relatedFaces.map(face => face.faceId) - }, - type: photoDB.Sequelize.QueryTypes.SELECT, - raw: true - }).then((descriptors) => { - let target; - for (let i = 0; i < descriptors.length; i++) { - if (descriptors[i].faceId == req.query.withScore) { - target = descriptors[i].descriptors; - break; - } - } - if (!target) { - console.warn("Could not find descriptor for requested face: " + req.query.withScore); + 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; } - - /* 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 - }).then(() => { - return identities; + } }); - }).then((identities) => { - return res.status(200).json(identities); - }).catch((error) => { - console.error(error); - return res.status(500).send("Error processing request."); + }, { + concurrency: 5 }); + + return res.status(200).json(identities); }); module.exports = router;