From 0965cd5d854651c37c8fd3a82fe98a216f95801b Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Fri, 13 Jan 2023 15:23:57 -0800 Subject: [PATCH] IDentity editing in work Signed-off-by: James Ketrenos --- docker-compose.yml | 1 + frontend/clusters.html | 199 ------------------ frontend/identities.html | 2 +- ketrface/cluster.py | 68 ++++-- ketrface/ketrface/.gitignore | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 131 -> 0 bytes .../ketrface/__pycache__/db.cpython-310.pyc | Bin 2087 -> 0 bytes .../__pycache__/dbscan.cpython-310.pyc | Bin 1439 -> 0 bytes .../ketrface/__pycache__/util.cpython-310.pyc | Bin 2431 -> 0 bytes server/routes/identities.js | 149 +++++++------ 10 files changed, 139 insertions(+), 281 deletions(-) delete mode 100644 frontend/clusters.html create mode 100644 ketrface/ketrface/.gitignore delete mode 100644 ketrface/ketrface/__pycache__/__init__.cpython-310.pyc delete mode 100644 ketrface/ketrface/__pycache__/db.cpython-310.pyc delete mode 100644 ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc delete mode 100644 ketrface/ketrface/__pycache__/util.cpython-310.pyc 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 1faa59ef14c9680685482d74b151c0108ca8df7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131 zcmd1j<>g`k0+oe(lR@-j5P=LBfgA@QE@lA|DGb33nv8xc8Hzx{2;!HTetBwAab`)X res*d}QCea$5+go7GcU6wK3=b&@)n0pZhlH>PO2S9e=!q~U||3Np@$nm diff --git a/ketrface/ketrface/__pycache__/db.cpython-310.pyc b/ketrface/ketrface/__pycache__/db.cpython-310.pyc deleted file mode 100644 index 057e65178252d347c52909dee8f2b0132e405118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2087 zcmb_d&2Aev5GHq5((1>q<3mt4{bA6eunM6;+nbTZH5>so5V&pZ#Hd@q-Cc5{b)}_B zQdT9%MS$cTdMaQa^ALRzUVG{j1Sk-oGhE4%l^jc9heLA6A;0-%Bv)2y1X|yYT4{#DrXs zO9I;{?x+|UkBrcu`jJvEiNd@8%kB(CtTQ9-{347>qhvwh@#p=K4exfNUd*HWXs^*j zNK^$nTY__<(X~tXFpr42OFRID({nlw4Y{BbatZnonZQc|V!Y99O3vZs`fVtSew?s~@STH=EeH+Qnw;N|=Glx7hC7kBm>jBa@2l%N!5cZw9ua zf9-LhBJhV z12|ZWpMluhKWINXgzoU$;=Ij%M;f`uTRMsICqik6c?&n~$~?`u$P>{@Wirq$Bm1qc zNKGr2#>ievWluY%#f45();Ch^@g_KUvh(n9`(Sgswe-U^TbVku^#yNs^}0J-a{jCj z4sC!QZMkr1OU|clTyq-Oaxzd_Dq9i1iDX~|Ovqj@Gq&1|v{7=H@t1+ZINAVxSs!R+ zeb}{3m->`0Xmu0p^@kvWN*I*sTYj&?dYP)*VDbiOFo_*$z69k>(irc;FNkwVQ#z$W zFr`8XsNjzy0PO#z!W=c0IYJ;&mI*O8(whtVVrX3`%V&_SQZ>tJ174`!17W?quQ0vT zEfnvg_y7e~w}qU%xd8IX+{YJSrLn*v-tW>75LchTF!4dm8Xkq8!*&xCz}BB8WH1RJ zA)(++XzDGd7xx^Z&*8a``%vSAOQ(F$i-lq`oh4Q$GOQDw2^I^HvtDG94iq*hfe~}O zh3(VAwv=>JRxhQYzLtm6qBi~vFY(=jox^rEhIx!D1$0^u<9*8t&ws!w6A*-%wH zps1~YOMj$3h6%f-#M4Y0p-v(m^`Up+wd+^Y0K}P4uwZMclD&A4reK6+)TM=spL4Nr lId@H0oWr|4$p>9=AOBxnO%6!|uC?y1($EW6!&{BD{{TSi4hjGO diff --git a/ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc b/ketrface/ketrface/__pycache__/dbscan.cpython-310.pyc deleted file mode 100644 index 8d11d0be0d1af5b0beb312f19f9936b7fe142639..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1439 zcmZuxPiq`E6xWk9vzpymlemGzX#;ymOST33R$q)0XB}t?**diYWoa<$k-V9BJ+mvx zHZd9~*k7Tiw%{K7A^IJ9=tLC?_aau z1tEWg#r45(@d(8PkVL|m><~!@AlZOQGGLMqxQqsojAi4T3}QtUNU_gkBAe$hXvmgK zabL*xHt8-hUB-&E#l%2gKpvy7acIY>JqePGIG%m zM8>Xx*l;nTK(G7^aF+a(oZ;U)A)8CYa-Zm`YRg;y)}Xw zwY?J~uw1r&WL3-Yi-v23O1jou%QdUir5D19dN;+~Kd;)ZT}O&B_X;&}wT2clT_R}T zt~KWxA(!*%>;KoOGi<+?VWcl?;!@0)R_XNhG<6(LwB6UC&^t4UIK#@4)5YHS$XKQG zV$_+Gd0|cGlzrTJ@D^51?{+4-%#<-ob~{;lI2kLeIw!e()j3T&xm1PCZLZ9+Z)9^X zUFK;neH4gb^i2$)KS1aTXy*viaCIo;~gFZtZOQ^x4KYyPHq9cQ*RHCpx(D{D~YXeHW+oQP;Jq kqg~4&+zq-uB(Zi_%A>Jb4^%db2!e1tfLcJX8zTMeKgG9CnE(I) diff --git a/ketrface/ketrface/__pycache__/util.cpython-310.pyc b/ketrface/ketrface/__pycache__/util.cpython-310.pyc deleted file mode 100644 index a196fda974bda8e1a20a6477ab2bbac63a066ad4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2431 zcmb7F&5zqe6!(lBXW~t^*^gF*B8V!42uLkdd=#maLMaFi+e10!5_vt7t+$Syj>j!o zrCic07yg3e*fVGTk-2i(f1v`w?~St`S&0L-=K0ODXXd@%`@QF)(}_G>503vxeixqi zCr!>@0Vek_%HJTQCt2nV>L(j8H|GP6C(rzGFbG)vt}$r1cj2Jvo?;**i(7J6`Z9RQ z29a#Y@TE7{ke8*9Em+zzk{hsezV~EXc3yh2qZ)^sbAND2Zpuq|)0JJhh3A&smOFTE zOZLd?Upa;C<~LKL${!vkj4Z2*bRXkBM)?thfrC%DzGB!RkG)wv+cf@R@WL|< z%-Nh9Hbh8|@D2y?Jnmj!?6 z4h#v#+Ql(*k-ZDWMds@+MYiyl9)qQhBzBQdG_TlRKhTs@-5?P{*hZ2SrE<=m6Xbzy zpZPT!?Z8qJTLh2bmSDl(x{K-S@Q#d4d^aI?PA)owj&SbXfI`kJI03xmbM}H8Z|=SD z3HTYG`73;9MUR9|)DWvL| z$Et_#s?79`TR4g`kjG=y4>h@D16p83vf9q_sabw zH5%C_5>3iQbUL)}@WhR4SW=>dye{(A1w8sKOxD=w!$80|P~L}Pz(9b(92npo0Ae$~ zpqppFDfqbKeed!}OAip2Bet-o0TnqZ5I`M-WJ04_KYo>L@DCG-oz$-VWYH6 z^U}n5qAbrRwwdNe9Vl&mC{~QJA~tD$U>i=e&0NM>$5R`cB1=ot=Qb!+HnM)Pe|Qlj zbc0b0E`ETcN;-RLmLf;<3d&$>Fwe0X%%Pn8~q_R zJ9ip23Qc9Idk}q$lE@`+ptGz&NwrAT?x6eyN=-e(&V5QA4e$ zU8vk3c|~q_&@jwJBa1EDw=6k_qw93rqcoTI3(!J+3r~m{x8O`k4X({1Brsyz?tY8u zFJW9mmq>bw?oXKfA9R5syXrYiYm_;)03hg4h7N7UPsrQU^c&M3lOVV>xv4)T@fnHF zAyCM-N&f=EZmTNEQmNv6C|8kP4E{0&|KQ}allbpo^;-M_@kcCR`YXIyYiyKC>b#C! z)b?gTV1c%_K%XaW)ht=EYo|pR)ZUZ3XWpZ8v!5?|*UoMY^Foi~EIm@+;ro*M0@Cm` zt#uX6^cM8~PW@MFlHIjCo))WLz+nkSH1cXZnbrqWH>&@H^lNoLGS{$O6SEsXK6<#f xYDun`T+UtK^|7w_&V=KTxKkJIU4o { 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;