Compare commits

...

2 Commits

Author SHA1 Message Date
58a2baddde Working again
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
2023-01-23 08:49:43 -08:00
3e9438bb27 Switched back to port 80 (http) instead of https in the container
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
2023-01-23 07:54:15 -08:00
7 changed files with 205 additions and 80 deletions

View File

@ -30,6 +30,9 @@ if [[ -z "${DEVELOPMENT}" ]]; then
{ while true; do npm start ; sleep 3 ; done ; } { while true; do npm start ; sleep 3 ; done ; }
else else
echo "Running in DEVELOPMENT mode." echo "Running in DEVELOPMENT mode."
if [[ ! -d /website/frontend ]]; then
fail "/website/frontend not found. Is the volume mounted? Did you run via './launch.sh'?"
fi
if [[ ! -d /website/frontend/bower_components ]]; then if [[ ! -d /website/frontend/bower_components ]]; then
echo "...installing bower_components for frontend" echo "...installing bower_components for frontend"
cd /website/frontend cd /website/frontend

View File

@ -226,5 +226,7 @@ with conn:
for identity in reduced: for identity in reduced:
print(f'Writing identity {identity["id"]} to DB') print(f'Writing identity {identity["id"]} to DB')
id = create_identity(conn, identity) id = create_identity(conn, identity)
first = True
for face in identity['faces']: for face in identity['faces']:
update_face_identity(conn, face['id'], id) update_face_identity(conn, face['id'], id, first)
first = False

View File

@ -142,7 +142,7 @@ def create_identity(conn, identity):
conn.commit() conn.commit()
return cur.lastrowid return cur.lastrowid
def update_face_identity(conn, faceId, identityId = None): def update_face_identity(conn, faceId, identityId = None, first = False):
""" """
Update the identity associated with this face Update the identity associated with this face
:param conn: :param conn:
@ -152,8 +152,13 @@ def update_face_identity(conn, faceId, identityId = None):
""" """
sql = ''' sql = '''
UPDATE faces SET identityId=? WHERE id=? UPDATE faces SET identityId=? WHERE id=?
''' '''
cur = conn.cursor() cur = conn.cursor()
cur.execute(sql, (identityId, faceId)) cur.execute(sql, (identityId, faceId))
if first:
sql = '''
UPDATE identities SET faceId=? WHERE id=?
'''
cur.execute(sql, (faceId, identityId))
conn.commit() conn.commit()
return None return None

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
pid=$(ps aux | pid=$(ps aux |
grep -E '[0-9] (/usr/bin/)?node .*client.*react-scripts/scripts/start.js' | grep -E '[0-9] (/usr/bin/)?node .*client.*craco.*scripts/start.js' |
while read user pid rest; do while read user pid rest; do
echo $pid; echo $pid;
done) done)

View File

@ -111,6 +111,14 @@ function init() {
type: Sequelize.STRING, type: Sequelize.STRING,
allowNull: false allowNull: false
}, },
faceId: {
type: Sequelize.INTEGER,
sallowNull: true,
references: {
model: Photo,
key: 'id',
}
},
descriptors: Sequelize.BLOB /* average of all faces mapped to this */ descriptors: Sequelize.BLOB /* average of all faces mapped to this */
}, { }, {
timestamps: false timestamps: false
@ -180,6 +188,10 @@ function init() {
key: 'id', key: 'id',
} }
}, },
distance: {
type: Sequelize.FLOAT,
defaultValue: -1
},
classifiedBy: { classifiedBy: {
type: Sequelize.DataTypes.ENUM( type: Sequelize.DataTypes.ENUM(
'machine', 'machine',

View File

@ -1,9 +1,10 @@
# DEVELOPMENT -- use npm development server on port 3000 (entrypoint.sh) # DEVELOPMENT -- use npm development server on port 3000 (entrypoint.sh)
location /identities/api/v1/ { location /identities/api/v1/ {
rewrite ^/identities/api/v1/(.*)$ /api/v1/$1 break; rewrite ^/identities/api/v1/(.*)$ /api/v1/$1 break;
proxy_pass https://localhost/; proxy_pass http://localhost:8123;
proxy_redirect off; proxy_redirect off;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_ssl_verify off;
} }
location /identities { location /identities {
@ -17,5 +18,6 @@ location /identities {
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade"; proxy_set_header Connection "Upgrade";
proxy_ssl_verify off;
proxy_pass https://localhost:3000; proxy_pass https://localhost:3000;
} }

View File

@ -11,7 +11,7 @@ require("../db/photos").then(function(db) {
const router = express.Router(); const router = express.Router();
const addOrUpdateIdentity = async(id, { const upsertIdentity = async(id, {
displayName, displayName,
firstName, firstName,
lastName, lastName,
@ -106,7 +106,7 @@ router.post('/', async (req, res) => {
return res.status(401).send({ message: "Unauthorized to modify photos." }); return res.status(401).send({ message: "Unauthorized to modify photos." });
} }
const identity = await addOrUpdateIdentity(-1, req.body, res); const identity = await upsertIdentity(-1, req.body, res);
if (!identity) { if (!identity) {
return; return;
} }
@ -126,7 +126,7 @@ router.put('/:id', async (req, res) => {
return res.status(400).send({ message: `Invalid identity id ${id}` }); return res.status(400).send({ message: `Invalid identity id ${id}` });
} }
const identity = await addOrUpdateIdentity(id, req.body, res); const identity = await upsertIdentity(id, req.body, res);
if (!identity) { if (!identity) {
return; return;
} }
@ -135,6 +135,35 @@ router.put('/:id', async (req, res) => {
}); });
router.delete('/:id', async (req, res) => {
console.log(`DELETE ${req.url}`)
if (!req.user.maintainer) {
console.warn(`${req.user.name} attempted to modify photos.`);
return res.status(401).send({ message: "Unauthorized to modify photos." });
}
const { id } = req.params;
if (!id || isNaN(+id)) {
return res.status(400).send({ message: `Invalid identity id ${id}` });
}
await photoDB.sequelize.query(
'UPDATE faces SET distance=0,identityId=NULL ' +
'WHERE identityId=:id', {
replacements: { id }
}
);
await photoDB.sequelize.query(
'DELETE FROM identities ' +
'WHERE identityId=:id', {
replacements: { id }
});
return res.status(200).send({});
});
const addFaceToIdentityDescriptors = (identity, face) => { const addFaceToIdentityDescriptors = (identity, face) => {
}; };
@ -331,6 +360,105 @@ const getUnknownIdentity = async (faceCount) => {
return unknownIdentity; return unknownIdentity;
} }
/* Compute the identity's centroid descriptor from all faces
* and determine closest face to that centroid. If either of
* those values have changed, update the identity.
*
* Also updates the 'distance' on each face to the identity
* centroid.
*/
const updateIdentityFaces = async (identity) => {
if (!identity.identityId) {
identity.identityId = identity.id;
}
const faces = await photoDB.sequelize.query(
"SELECT " +
"faces.*,faceDescriptors.* " +
"FROM faces,faceDescriptors " +
"WHERE " +
"faces.identityId=:identityId " +
"AND faceDescriptors.id=faces.descriptorId", {
replacements: identity,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
});
let average = undefined,
closestId = -1,
closestDistance = -1,
count = 0;
faces.forEach((face) => {
if (!identity.descriptors) {
return;
}
if (!face.descriptors) {
return;
}
face.distance = euclideanDistance(
face.descriptors,
identity.descriptors
);
face.descriptors = bufferToFloat32Array(face.descriptors).map(x => x * x);
if (closestId === -1) {
closestId = face.id;
closestDistance = face.distance;
average = descriptors;
count = 1;
return;
}
descriptors.forEach((x, i) => {
average[i] += x;
});
count++;
if (face.distance < closestDistance) {
closestDistance = face.distance;
closestId = face.id;
}
});
let same = true;
if (average) {
average = average.map(x => x / count);
same = bufferToFloat32Array(identity.descriptors)
.find((x, i) => average[i] === x) === undefined;
await Promise(faces, async (face) => {
const distance = euclideanDistanceArray(face.descriptors, average);
if (distance !== face.distance) {
await photoDB.sequelize.query(
'UPDATE faces SET distance=:distance WHERE id=:faceId', {
replacements: face
}
);
}
});
}
let sql = '';
if (closestId !== -1 && closestId !== identity.faceId) {
sql = `${sql} faceId=:faceId`;
}
if (!same) {
if (sql !== '') {
sql = `${sql}, `;
}
sql = `${sql} descriptors=:descriptors`;
identity.descriptors = average;
}
if (sql !== '') {
identity.faceId = closestId;
await photoDB.sequelize.query(
`UPDATE identities SET ${sql} ` +
`WHERE id=:identityId`, {
replacements: identity
}
);
}
};
router.get("/:id?", async (req, res) => { router.get("/:id?", async (req, res) => {
console.log(`GET ${req.url}`); console.log(`GET ${req.url}`);
@ -352,100 +480,73 @@ router.get("/:id?", async (req, res) => {
const filter = id ? "WHERE identities.id=:id " : ""; const filter = id ? "WHERE identities.id=:id " : "";
const identities = await photoDB.sequelize.query("SELECT " + const identities = await photoDB.sequelize.query(
"identities.*," + "SELECT " +
"GROUP_CONCAT(faces.id) AS relatedFaceIds," + "identities.id AS identityId," +
"GROUP_CONCAT(faces.descriptorId) AS relatedFaceDescriptorIds," + "identities.firstName," +
"GROUP_CONCAT(faces.photoId) AS relatedFacePhotoIds " + "identities.lastName," +
"identities.middleName," +
"identities.displayName," +
"identities.faceId " +
"FROM identities " + "FROM identities " +
"LEFT JOIN faces ON identities.id=faces.identityId " + filter, {
filter +
"GROUP BY identities.id", {
replacements: { id }, replacements: { id },
type: photoDB.Sequelize.QueryTypes.SELECT, type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true raw: true
}); });
await Promise.map(identities, async (identity) => { await Promise.map(identities, async (identity) => {
[ 'firstName', 'middleName', 'lastName' ].forEach(key => { for (let field in identity) {
if (!identity[key]) { if (field.match(/.*Name/) && identity[field] === null) {
identity[key] = ''; identity[field] = '';
} }
});
identity.identityId = identity.id;
if (!identity.relatedFaceIds) {
identity.relatedFaces = [];
} else {
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) => {
let distance = 0;
if (descriptors[index] && identity.descriptors) {
distance = euclideanDistance(
descriptors[index],
identity.descriptors
);
} else {
distance = -1;
}
return {
identityId: identity.id,
faceId,
photoId: relatedFacePhotos[index],
distance
};
});
} }
let where, limit = '';
/* If id was not set, only return a single face */
if (id === undefined) {
if (identity.faceId !== -1 && identity.faceId !== null) {
where = 'faceId=:faceId';
} else {
where = 'identityId=:identityId';
limit = 'LIMIT 1';
}
} else {
where = 'identityId=:identityId'
}
identity.relatedFaces = await photoDB.sequelize.query(
'SELECT id as faceId,identityId,photoId,distance ' +
'FROM faces ' +
`WHERE ${where} ` +
'ORDER BY distance ASC ' +
limit, {
replacements: identity,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}
);
if (identity.relatedFaces.length !== 0
&& (!identity.faceId || identity.faceId === -1)) {
await updateIdentityFaces(identity);
}
/* If there were no faces, then add a 'Unknown' face */
if (identity.relatedFaces.length === 0) { if (identity.relatedFaces.length === 0) {
identity.relatedFaces.push({ identity.relatedFaces.push({
faceId: -1, faceId: -1,
photoId: -1, photoId: -1,
identityId: identity.id, identityId: identity.identityId,
distance: 0, distance: 0,
faceConfidence: 0 faceConfidence: 0
}); });
} }
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.id;
delete identity.descriptors;
delete identity.relatedFaceIds;
delete identity.relatedFacePhotoIds;
delete identity.relatedFaceDescriptorIds;
delete identity.relatedIdentityDescriptors;
}); });
/* If no ID was provided (so no 'filter') then this call is returning /* If no ID was provided then this call is returning
* a list of all identities -- we create a fake identity for all * a list of all identities -- we create a fake identity for all
* unlabeled faces */ * unlabeled faces */
if (!filter) { if (!id) {
const unknownIdentity = await getUnknownIdentity(1) const unknownIdentity = await getUnknownIdentity(1)
identities.push(unknownIdentity); identities.push(unknownIdentity);
} }