Compare commits
No commits in common. "e8de846ed0842d8a56ab3ab3f155c93eff40791b" and "743d7cc5ea442e952e0ab6df909a612bf60b4739" have entirely different histories.
e8de846ed0
...
743d7cc5ea
@ -21,74 +21,12 @@ div {
|
|||||||
|
|
||||||
.Identities {
|
.Identities {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-y: scroll;
|
flex-grow: 1;
|
||||||
flex-direction: column;
|
|
||||||
border: 1px solid green;
|
border: 1px solid green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Face {
|
|
||||||
display: flex;
|
|
||||||
box-sizing: border-box;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face .Image {
|
|
||||||
border: 0.25rem solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face:hover .Image {
|
|
||||||
border: 0.25rem solid yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face.Selected .Image {
|
|
||||||
border: 0.25rem solid blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face .Title {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
padding: 0.125rem;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Face .Image {
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 8rem;
|
|
||||||
height: 8rem;
|
|
||||||
background-size: contain !important;
|
|
||||||
background-repeat: no-repeat no-repeat !important;;
|
|
||||||
background-position: 50% 50% !important;;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Cluster {
|
.Cluster {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
overflow-y: scroll;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 0.5rem;
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Cluster .Face.Selected {
|
|
||||||
/* filter: grayscale(100%); */
|
|
||||||
}
|
|
||||||
|
|
||||||
.Cluster .Info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Cluster .Faces {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
|
||||||
}
|
|
@ -3,150 +3,12 @@ import React, { useState, useMemo, useEffect } from 'react';
|
|||||||
import { useApi } from './useApi';
|
import { useApi } from './useApi';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
const Face = ({ faceId, onClick, title }: any) => {
|
const Cluster = () => {
|
||||||
const idPath = String(faceId % 100).padStart(2, '0');
|
|
||||||
return (
|
return (
|
||||||
<div onClick={(e) => { onClick(e, faceId) }}
|
<div className='Cluster'>cluster</div>
|
||||||
className='Face'>
|
|
||||||
<div className='Image'
|
|
||||||
style={{
|
|
||||||
background: `url("/faces/${idPath}/${faceId}.jpg")`,
|
|
||||||
}}>
|
|
||||||
<div className='Title'>{title}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClusterProps = {
|
|
||||||
id: number
|
|
||||||
};
|
|
||||||
|
|
||||||
const Cluster = ({ id }: ClusterProps) => {
|
|
||||||
const [identity, setIdentity] = useState<Identity | undefined>(undefined);
|
|
||||||
const { loading, data } = useApi(
|
|
||||||
`../api/v1/identities/${id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data) {
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
|
||||||
setIdentity(data[0] as Identity);
|
|
||||||
} else {
|
|
||||||
setIdentity(data as Identity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const relatedFacesJSX = useMemo(() => {
|
|
||||||
const faceClicked = (e: any, id: any) => {
|
|
||||||
if (!identity) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const el = e.currentTarget;
|
|
||||||
const face = identity.relatedFaces.find(item => item.faceId === id);
|
|
||||||
el.classList.toggle('Selected');
|
|
||||||
console.log(face);
|
|
||||||
}
|
|
||||||
if (identity === undefined) {
|
|
||||||
return <></>;
|
|
||||||
}
|
|
||||||
return identity.relatedFaces.map(face =>
|
|
||||||
<Face key={face.faceId}
|
|
||||||
faceId={face.faceId}
|
|
||||||
onClick={faceClicked}
|
|
||||||
title={face.distance}/>
|
|
||||||
);
|
|
||||||
}, [identity]);
|
|
||||||
|
|
||||||
const lastNameChanged = (e: any) => {
|
|
||||||
setIdentity(Object.assign(
|
|
||||||
{},
|
|
||||||
identity, {
|
|
||||||
lastName: e.currentTarget.value
|
|
||||||
}
|
|
||||||
));
|
|
||||||
};
|
|
||||||
const firstNameChanged = (e: any) => {
|
|
||||||
setIdentity(Object.assign(
|
|
||||||
{},
|
|
||||||
identity, {
|
|
||||||
firstName: e.currentTarget.value
|
|
||||||
}
|
|
||||||
));
|
|
||||||
};
|
|
||||||
const middleNameChanged = (e: any) => {
|
|
||||||
setIdentity(Object.assign(
|
|
||||||
{},
|
|
||||||
identity, {
|
|
||||||
middleName: e.currentTarget.value
|
|
||||||
}
|
|
||||||
));
|
|
||||||
};
|
|
||||||
const displayNameChanged = (e: any) => {
|
|
||||||
setIdentity(Object.assign(
|
|
||||||
{},
|
|
||||||
identity, {
|
|
||||||
displayName: e.currentTarget.value
|
|
||||||
}
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (<div className='Cluster'>
|
|
||||||
{loading && `Loading ${id}...`}
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identity === undefined) {
|
|
||||||
return (<div className='Cluster'>
|
|
||||||
Select identity to load.
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='Cluster'>
|
|
||||||
<div className="Info">
|
|
||||||
<form>
|
|
||||||
<div><label>Last name:<input type="text"
|
|
||||||
value={identity.lastName}
|
|
||||||
onChange={lastNameChanged}/>
|
|
||||||
</label></div>
|
|
||||||
<div><label>First name:<input type="text"
|
|
||||||
value={identity.firstName}
|
|
||||||
onChange={firstNameChanged} />
|
|
||||||
</label></div>
|
|
||||||
<div><label>Middle name:<input type="text"
|
|
||||||
value={identity.middleName}
|
|
||||||
onChange={middleNameChanged} />
|
|
||||||
</label></div>
|
|
||||||
<div><label>Display name:<input type="text"
|
|
||||||
value={identity.displayName}
|
|
||||||
onChange={displayNameChanged} />
|
|
||||||
</label></div>
|
|
||||||
<div>Faces: {identity.relatedFaces.length}</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div className="Faces">
|
|
||||||
{ relatedFacesJSX }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
type FaceData = {
|
|
||||||
faceId: number,
|
|
||||||
lastName: string,
|
|
||||||
firstName: string,
|
|
||||||
middleName: string,
|
|
||||||
displayName: string,
|
|
||||||
identityId: number,
|
|
||||||
distance: number,
|
|
||||||
descriptors: any[]
|
|
||||||
};
|
|
||||||
|
|
||||||
type Identity = {
|
type Identity = {
|
||||||
lastName: string,
|
lastName: string,
|
||||||
middleName: string,
|
middleName: string,
|
||||||
@ -154,31 +16,22 @@ type Identity = {
|
|||||||
descriptors: number[],
|
descriptors: number[],
|
||||||
id: number
|
id: number
|
||||||
displayName: string,
|
displayName: string,
|
||||||
relatedFaces: FaceData[]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
interface IdentitiesProps {
|
interface IdentitiesProps {
|
||||||
setIdentity?(id: number): void,
|
|
||||||
identities: Identity[]
|
identities: Identity[]
|
||||||
};
|
};
|
||||||
|
|
||||||
const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
const Identities = ({ identities } : IdentitiesProps) => {
|
||||||
const identitiesJSX = useMemo(() => {
|
const identitiesJSX = useMemo(() =>
|
||||||
const loadIdentity = (id: number): void => {
|
identities.map((identity) => {
|
||||||
if (setIdentity) {
|
const idPath = String(identity.id % 100).padStart(2, '0');
|
||||||
setIdentity(id)
|
return (<img
|
||||||
}
|
key={identity.id}
|
||||||
};
|
alt={identity.id.toString()}
|
||||||
return identities.map((identity) => {
|
src={`/faces/${idPath}/${identity.id}.jpg`}/>);
|
||||||
const face = identity.relatedFaces[0];
|
}
|
||||||
return (
|
), [ identities ]);
|
||||||
<Face key={face.faceId}
|
|
||||||
faceId={face.faceId}
|
|
||||||
onClick={() => loadIdentity(identity.id)}
|
|
||||||
title={identity.displayName}/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [ setIdentity, identities ]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='Identities'>
|
<div className='Identities'>
|
||||||
@ -189,9 +42,8 @@ const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [identities, setIdentities] = useState<Identity[]>([]);
|
const [identities, setIdentities] = useState<Identity[]>([]);
|
||||||
const [identity, setIdentity] = useState<number>(0);
|
|
||||||
const { loading, data } = useApi(
|
const { loading, data } = useApi(
|
||||||
'../api/v1/identities'
|
'../api/v1/faces'
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -203,13 +55,10 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div className="Worksheet">
|
<div className="Worksheet">
|
||||||
{ loading && <div style={{margin:'1rem'}}>Loading...</div> }
|
{ loading && <div>Loading...</div> }
|
||||||
{ !loading && identity !== 0 && <Cluster id={identity} />}
|
|
||||||
{ !loading && identity === 0 && <div className="Cluster">
|
|
||||||
Select identity to edit
|
|
||||||
</div> }
|
|
||||||
{ !loading && <>
|
{ !loading && <>
|
||||||
<Identities {... {identities, setIdentity }}/>
|
<Cluster/>
|
||||||
|
<Identities identities={identities}/>
|
||||||
</> }
|
</> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,19 +13,15 @@ const useApi = (_url: string, _options?: {}) : UseApi => {
|
|||||||
const [error, setError] = useState<any>(undefined);
|
const [error, setError] = useState<any>(undefined);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (_url === '') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const fetchApi = async () => {
|
const fetchApi = async () => {
|
||||||
console.log(`Fetching ${_url}...`);
|
|
||||||
try {
|
try {
|
||||||
const res = await window.fetch(_url, _options);
|
const res = await window.fetch(_url, _options);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setData(data);
|
setData(data);
|
||||||
setLoading(false);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
setError(e)
|
setError(e)
|
||||||
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from ketrface.util import *
|
|
||||||
from ketrface.dbscan import *
|
|
||||||
from ketrface.db import *
|
|
||||||
from ketrface.config import *
|
|
||||||
|
|
||||||
config = read_config()
|
|
||||||
|
|
||||||
html_path = merge_config_path(config['path'], 'frontend')
|
|
||||||
pictures_path = merge_config_path(config['path'], config['picturesPath'])
|
|
||||||
faces_path = merge_config_path(config['path'], config['facesPath'])
|
|
||||||
db_path = merge_config_path(config['path'], config["db"]["photos"]["host"])
|
|
||||||
html_base = config['basePath']
|
|
||||||
if html_base == "/":
|
|
||||||
html_base = "."
|
|
||||||
|
|
||||||
print(f'Connecting to database: {db_path}')
|
|
||||||
conn = create_connection(db_path)
|
|
||||||
with conn:
|
|
||||||
cur = conn.cursor()
|
|
||||||
res = cur.execute('''
|
|
||||||
SELECT identities.descriptors,
|
|
||||||
GROUP_CONCAT(faces.id) AS relatedFaceIds,
|
|
||||||
GROUP_CONCAT(faces.descriptorId) AS relatedFaceDescriptorIds,
|
|
||||||
GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds
|
|
||||||
FROM identities
|
|
||||||
INNER JOIN faces ON identities.id=faces.identityId
|
|
||||||
WHERE identities.id=7
|
|
||||||
GROUP BY identities.id
|
|
||||||
''')
|
|
||||||
for identity in res.fetchall():
|
|
||||||
relatedFaceDescriptorIds = identity[2].split(',')
|
|
||||||
|
|
||||||
res2 = cur.execute(
|
|
||||||
'SELECT descriptors FROM facedescriptors WHERE id IN (%s)' %
|
|
||||||
','.join('?'*len(relatedFaceDescriptorIds)), relatedFaceDescriptorIds)
|
|
||||||
|
|
||||||
descriptors = []
|
|
||||||
for row2 in res2.fetchall():
|
|
||||||
descriptors.append(np.frombuffer(row2[0]))
|
|
||||||
|
|
||||||
distances = []
|
|
||||||
|
|
||||||
relatedFaceIds = identity[2].split(',')
|
|
||||||
for i, face in enumerate(relatedFaceIds):
|
|
||||||
distance = findEuclideanDistance(
|
|
||||||
descriptors[i],
|
|
||||||
np.frombuffer(identity[0])
|
|
||||||
)
|
|
||||||
distances.append(distance)
|
|
||||||
|
|
||||||
distances.sort()
|
|
||||||
print(distances)
|
|
@ -1,7 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
. .env
|
|
||||||
cat << EOF | ./query.sh
|
|
||||||
update faces set identityId=NULL where identityId is not null;
|
|
||||||
delete from identities where id>0;
|
|
||||||
delete from sqlite_sequence where name='identities';
|
|
||||||
EOF
|
|
@ -1,13 +1,10 @@
|
|||||||
|
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
pid=$(ps aux |
|
pid=$(ps aux |
|
||||||
grep -E '[0-9] (/usr/bin/)?node .*server/app.js' |
|
grep '[0-9] node app.js' |
|
||||||
while read user pid rest; do
|
while read user pid rest; do
|
||||||
echo $pid;
|
echo $pid;
|
||||||
done)
|
done)
|
||||||
if [[ "$pid" != "" ]]; then
|
if [[ "$pid" != "" ]]; then
|
||||||
echo "Killing ${pid}"
|
|
||||||
kill $pid
|
kill $pid
|
||||||
else
|
|
||||||
echo "No node server found"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const Promise = require("bluebird");
|
|
||||||
|
|
||||||
let photoDB;
|
|
||||||
|
|
||||||
function bufferToFloat32Array(buffer) {
|
|
||||||
return new Float64Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Float64Array.BYTES_PER_ELEMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
function euclideanDistance(a, b) {
|
|
||||||
let A = bufferToFloat32Array(a);
|
|
||||||
let B = bufferToFloat32Array(b);
|
|
||||||
console.log(A.length, B.length);
|
|
||||||
let sum = 0;
|
|
||||||
for (let i = 0; i < A.length; i++) {
|
|
||||||
let delta = A[i] - B[i];
|
|
||||||
sum += delta * delta;
|
|
||||||
}
|
|
||||||
return Math.sqrt(sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
require("./db/photos").then(function(db) {
|
|
||||||
photoDB = db;
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
const id = 7;
|
|
||||||
const filter = ` WHERE identities.id=:id `;
|
|
||||||
const identities = await photoDB.sequelize.query("SELECT " +
|
|
||||||
"identities.*," +
|
|
||||||
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
|
||||||
"GROUP_CONCAT(faces.descriptorId) AS relatedFaceDescriptorIds," +
|
|
||||||
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds " +
|
|
||||||
"FROM identities " +
|
|
||||||
"INNER JOIN faces ON identities.id=faces.identityId " +
|
|
||||||
filter +
|
|
||||||
"GROUP BY identities.id", {
|
|
||||||
replacements: { id },
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.map(identities, async (identity) => {
|
|
||||||
[ 'firstName', 'middleName', 'lastName' ].forEach(key => {
|
|
||||||
if (!identity[key]) {
|
|
||||||
identity[key] = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const relatedFaces = identity.relatedFaceIds.split(","),
|
|
||||||
relatedFacePhotos = identity.relatedFacePhotoIds.split(",");
|
|
||||||
|
|
||||||
let descriptors = await photoDB.sequelize.query(
|
|
||||||
`SELECT descriptors FROM facedescriptors WHERE id in (:ids)`, {
|
|
||||||
replacements: {
|
|
||||||
ids: identity.relatedFaceDescriptorIds.split(',')
|
|
||||||
},
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
descriptors = descriptors.map(entry => entry.descriptors);
|
|
||||||
|
|
||||||
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
|
||||||
const distance = euclideanDistance(
|
|
||||||
descriptors[index],
|
|
||||||
identity.descriptors
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(index, distance);
|
|
||||||
return {
|
|
||||||
faceId,
|
|
||||||
photoId: relatedFacePhotos[index],
|
|
||||||
distance
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
identity
|
|
||||||
.relatedFaces
|
|
||||||
.sort((A, B) => {
|
|
||||||
return A.distance - B.distance;
|
|
||||||
});
|
|
||||||
|
|
||||||
/* If no filter was specified, only return the best face for
|
|
||||||
* the identity */
|
|
||||||
if (!filter) {
|
|
||||||
identity.relatedFaces = [ identity.relatedFaces[0] ];
|
|
||||||
}
|
|
||||||
|
|
||||||
delete identity.descriptors;
|
|
||||||
delete identity.relatedFaceIds;
|
|
||||||
delete identity.relatedFacePhotoIds;
|
|
||||||
delete identity.relatedIdentityDescriptors;
|
|
||||||
}, {
|
|
||||||
concurrency: 10
|
|
||||||
});
|
|
||||||
});
|
|
@ -90,7 +90,7 @@ router.post("/", (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function bufferToFloat32Array(buffer) {
|
function bufferToFloat32Array(buffer) {
|
||||||
return new Float64Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Float64Array.BYTES_PER_ELEMENT);
|
return new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Float32Array.BYTES_PER_ELEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
function euclideanDistance(a, b) {
|
function euclideanDistance(a, b) {
|
||||||
@ -105,8 +105,6 @@ function euclideanDistance(a, b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
router.get("/:id?", async (req, res) => {
|
router.get("/:id?", async (req, res) => {
|
||||||
console.log(`GET ${req.url}`);
|
|
||||||
|
|
||||||
let id;
|
let id;
|
||||||
|
|
||||||
if (req.params.id) {
|
if (req.params.id) {
|
||||||
@ -121,9 +119,10 @@ router.get("/:id?", async (req, res) => {
|
|||||||
const identities = await photoDB.sequelize.query("SELECT " +
|
const identities = await photoDB.sequelize.query("SELECT " +
|
||||||
"identities.*," +
|
"identities.*," +
|
||||||
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
"GROUP_CONCAT(faces.id) AS relatedFaceIds," +
|
||||||
"GROUP_CONCAT(faces.descriptorId) AS relatedFaceDescriptorIds," +
|
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds," +
|
||||||
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds " +
|
"GROUP_CONCAT(facedescriptors.descriptors) AS relatedIdentityDescriptors " +
|
||||||
"FROM identities " +
|
"FROM identities " +
|
||||||
|
"INNER JOIN facedescriptors ON facedescriptors.id=faces.descriptorId " +
|
||||||
"INNER JOIN faces ON identities.id=faces.identityId " +
|
"INNER JOIN faces ON identities.id=faces.identityId " +
|
||||||
filter +
|
filter +
|
||||||
"GROUP BY identities.id", {
|
"GROUP BY identities.id", {
|
||||||
@ -132,7 +131,7 @@ router.get("/:id?", async (req, res) => {
|
|||||||
raw: true
|
raw: true
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.map(identities, async (identity) => {
|
identities.forEach((identity) => {
|
||||||
[ 'firstName', 'middleName', 'lastName' ].forEach(key => {
|
[ 'firstName', 'middleName', 'lastName' ].forEach(key => {
|
||||||
if (!identity[key]) {
|
if (!identity[key]) {
|
||||||
identity[key] = '';
|
identity[key] = '';
|
||||||
@ -140,26 +139,15 @@ router.get("/:id?", async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const relatedFaces = identity.relatedFaceIds.split(","),
|
const relatedFaces = identity.relatedFaceIds.split(","),
|
||||||
relatedFacePhotos = identity.relatedFacePhotoIds.split(",");
|
relatedFacePhotos = identity.relatedFacePhotoIds.split(","),
|
||||||
|
relatedIdentityDescriptors =
|
||||||
let descriptors = await photoDB.sequelize.query(
|
identity.relatedIdentityDescriptors.split(",");
|
||||||
`SELECT descriptors FROM facedescriptors WHERE id in (:ids)`, {
|
|
||||||
replacements: {
|
|
||||||
ids: identity.relatedFaceDescriptorIds.split(',')
|
|
||||||
},
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
descriptors = descriptors.map(entry => entry.descriptors);
|
|
||||||
|
|
||||||
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
identity.relatedFaces = relatedFaces.map((faceId, index) => {
|
||||||
const distance = euclideanDistance(
|
const distance = euclideanDistance(
|
||||||
descriptors[index],
|
relatedIdentityDescriptors[index],
|
||||||
identity.descriptors
|
identity.descriptors
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
faceId,
|
faceId,
|
||||||
photoId: relatedFacePhotos[index],
|
photoId: relatedFacePhotos[index],
|
||||||
@ -167,24 +155,61 @@ router.get("/:id?", async (req, res) => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
identity
|
|
||||||
.relatedFaces
|
|
||||||
.sort((A, B) => {
|
|
||||||
return A.distance - B.distance;
|
|
||||||
});
|
|
||||||
|
|
||||||
/* If no filter was specified, only return the best face for
|
|
||||||
* the identity */
|
|
||||||
if (!filter) {
|
|
||||||
identity.relatedFaces = [ identity.relatedFaces[0] ];
|
|
||||||
}
|
|
||||||
|
|
||||||
delete identity.descriptors;
|
|
||||||
delete identity.relatedFaceIds;
|
delete identity.relatedFaceIds;
|
||||||
delete identity.relatedFacePhotoIds;
|
delete identity.relatedFacePhotoIds;
|
||||||
delete identity.relatedIdentityDescriptors;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
concurrency: 5
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).json(identities);
|
return res.status(200).json(identities);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user