Can now create blank identities
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
df764ad342
commit
b86c12fc92
@ -121,7 +121,15 @@ div {
|
|||||||
|
|
||||||
.IdentityForm {
|
.IdentityForm {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: 0.25rem;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.IdentityForm input {
|
||||||
|
min-height: 1rem;
|
||||||
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Face.Active,
|
.Face.Active,
|
||||||
@ -145,11 +153,18 @@ div {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.Info .Face .Image {
|
||||||
|
width: 10rem;
|
||||||
|
height: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
.Face .Image {
|
.Face .Image {
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
min-width: 8rem;
|
||||||
|
min-height: 8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Cluster {
|
.Cluster {
|
||||||
@ -175,8 +190,6 @@ div {
|
|||||||
object-fit: cover; /* contain */
|
object-fit: cover; /* contain */
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 8rem;
|
|
||||||
min-height: 8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.Cluster .Faces {
|
.Cluster .Faces {
|
||||||
|
@ -209,7 +209,7 @@ const Face = ({ face, onFaceClick, title, isSelected }: any) => {
|
|||||||
|
|
||||||
type ClusterProps = {
|
type ClusterProps = {
|
||||||
identity: IdentityData,
|
identity: IdentityData,
|
||||||
setIdentity(identity: IdentityData | undefined): void,
|
setIdentity(identity: IdentityData): void,
|
||||||
identities: IdentityData[],
|
identities: IdentityData[],
|
||||||
setIdentities(identiteis: IdentityData[]): void,
|
setIdentities(identiteis: IdentityData[]): void,
|
||||||
setImage(image: number): void,
|
setImage(image: number): void,
|
||||||
@ -275,7 +275,7 @@ const Cluster = ({
|
|||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
identities.splice(index, 1);
|
identities.splice(index, 1);
|
||||||
}
|
}
|
||||||
setIdentity(undefined);
|
setIdentity(EmptyIdentity);
|
||||||
setIdentities([...identities]);
|
setIdentities([...identities]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -338,27 +338,37 @@ const Cluster = ({
|
|||||||
return (
|
return (
|
||||||
<div className='Cluster'>
|
<div className='Cluster'>
|
||||||
<div className="Info">
|
<div className="Info">
|
||||||
<form className="IdentityForm">
|
<div style={{ display: "flex", flexDirection: "row", gap: "0.25rem" }}>
|
||||||
<div>Last name:</div>
|
<form className="IdentityForm">
|
||||||
<input type="text"
|
<div>Last name:</div>
|
||||||
value={identity.lastName}
|
<input type="text"
|
||||||
onChange={lastNameChanged}/>
|
value={identity.lastName}
|
||||||
<div>First name:</div>
|
onChange={lastNameChanged}/>
|
||||||
<input type="text"
|
<div>First name:</div>
|
||||||
value={identity.firstName}
|
<input type="text"
|
||||||
onChange={firstNameChanged} />
|
value={identity.firstName}
|
||||||
<div>Middle name:</div><input type="text"
|
onChange={firstNameChanged} />
|
||||||
value={identity.middleName}
|
<div>Middle name:</div><input type="text"
|
||||||
onChange={middleNameChanged} />
|
value={identity.middleName}
|
||||||
<div>Display name:</div>
|
onChange={middleNameChanged} />
|
||||||
<input type="text"
|
<div>Display name:</div>
|
||||||
value={identity.displayName}
|
<input type="text"
|
||||||
onChange={displayNameChanged} />
|
value={identity.displayName}
|
||||||
</form>
|
onChange={displayNameChanged} />
|
||||||
|
</form>
|
||||||
|
<Face
|
||||||
|
face={identity.relatedFaces.length
|
||||||
|
? identity.relatedFaces[0]
|
||||||
|
: UnknownFace}
|
||||||
|
onFaceClick={() => {}}
|
||||||
|
title={`${identity.displayName} (${identity.facesCount})`} />
|
||||||
|
</div>
|
||||||
<div className="Actions">
|
<div className="Actions">
|
||||||
<Button onClick={createIdentity}>Create</Button>
|
<Button onClick={createIdentity}>Create</Button>
|
||||||
<Button onClick={updateIdentity}>Update</Button>
|
{ identity.identityId !== -1 && <>
|
||||||
<Button onClick={deleteIdentity}>Delete</Button>
|
<Button onClick={updateIdentity}>Update</Button>
|
||||||
|
<Button onClick={deleteIdentity}>Delete</Button>
|
||||||
|
</> }
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>Faces: {identity.relatedFaces.length}</div>
|
<div>Faces: {identity.relatedFaces.length}</div>
|
||||||
@ -405,6 +415,32 @@ type IdentityData = {
|
|||||||
faceId: number
|
faceId: number
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const EmptyIdentity: IdentityData = {
|
||||||
|
lastName: '',
|
||||||
|
middleName: '',
|
||||||
|
firstName: '',
|
||||||
|
descriptors: [],
|
||||||
|
identityId: -1,
|
||||||
|
displayName: '',
|
||||||
|
relatedFaces: [],
|
||||||
|
facesCount: 0,
|
||||||
|
faceId: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
const UnknownFace = {
|
||||||
|
faceId: -1,
|
||||||
|
photoId: -1,
|
||||||
|
identityId: -1,
|
||||||
|
distance: 0,
|
||||||
|
descriptors: [],
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
identity: EmptyIdentity
|
||||||
|
};
|
||||||
|
|
||||||
interface IdentitiesProps {
|
interface IdentitiesProps {
|
||||||
identities: IdentityData[],
|
identities: IdentityData[],
|
||||||
onFaceClick(e: any, face: FaceData): void
|
onFaceClick(e: any, face: FaceData): void
|
||||||
@ -448,7 +484,7 @@ const App = () => {
|
|||||||
const [identities, setIdentities] = useState<IdentityData[]>([]);
|
const [identities, setIdentities] = useState<IdentityData[]>([]);
|
||||||
const { identityId, faceId } = useParams();
|
const { identityId, faceId } = useParams();
|
||||||
const [selectedIdentities, setSelectedIdentities] = useState<number[]>([]);
|
const [selectedIdentities, setSelectedIdentities] = useState<number[]>([]);
|
||||||
const [identity, setIdentity] = useState<IdentityData | undefined>(undefined);
|
const [identity, setIdentity] = useState<IdentityData>(EmptyIdentity);
|
||||||
const [image, setImage] = useState<number>(0);
|
const [image, setImage] = useState<number>(0);
|
||||||
const [guess, setGuess] = useState<FaceData|undefined>(undefined);
|
const [guess, setGuess] = useState<FaceData|undefined>(undefined);
|
||||||
const { loading, data } = useApi( /* TODO: Switch away from using useApi */
|
const { loading, data } = useApi( /* TODO: Switch away from using useApi */
|
||||||
@ -575,7 +611,7 @@ const App = () => {
|
|||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
identities.splice(index, 1);
|
identities.splice(index, 1);
|
||||||
}
|
}
|
||||||
setIdentity(undefined);
|
setIdentity(EmptyIdentity);
|
||||||
setIdentities([...identities]);
|
setIdentities([...identities]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -721,7 +757,6 @@ const App = () => {
|
|||||||
autoSaveId="persistence" direction="horizontal">
|
autoSaveId="persistence" direction="horizontal">
|
||||||
<Panel defaultSize={50} className="ClusterEditor">
|
<Panel defaultSize={50} className="ClusterEditor">
|
||||||
{loading && <div style={{ margin: '1rem' }}>Loading...</div>}
|
{loading && <div style={{ margin: '1rem' }}>Loading...</div>}
|
||||||
{ !loading && identity !== undefined &&
|
|
||||||
<Cluster {...{
|
<Cluster {...{
|
||||||
identity,
|
identity,
|
||||||
setIdentity,
|
setIdentity,
|
||||||
@ -730,7 +765,7 @@ const App = () => {
|
|||||||
setImage,
|
setImage,
|
||||||
selected,
|
selected,
|
||||||
setSelected,
|
setSelected,
|
||||||
}} /> }
|
}} />
|
||||||
{!loading && identity === undefined && <div className="Cluster">
|
{!loading && identity === undefined && <div className="Cluster">
|
||||||
Select identity to edit
|
Select identity to edit
|
||||||
</div>}
|
</div>}
|
||||||
|
@ -44,6 +44,7 @@ const upsertIdentity = async(id, {
|
|||||||
replacements: identity
|
replacements: identity
|
||||||
});
|
});
|
||||||
identity.identityId = lastId;
|
identity.identityId = lastId;
|
||||||
|
console.log('Created identity: ', identity)
|
||||||
} else {
|
} else {
|
||||||
await photoDB.sequelize.query(
|
await photoDB.sequelize.query(
|
||||||
`UPDATE identities ` +
|
`UPDATE identities ` +
|
||||||
@ -55,6 +56,7 @@ const upsertIdentity = async(id, {
|
|||||||
'WHERE id=:identityId', {
|
'WHERE id=:identityId', {
|
||||||
replacements: identity
|
replacements: identity
|
||||||
});
|
});
|
||||||
|
console.log('Updated identity: ', identity)
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity;
|
return identity;
|
||||||
@ -502,82 +504,92 @@ const updateIdentityFaces = async (identity) => {
|
|||||||
.parseFloat(euclideanDistanceArray(identity.descriptors, average))
|
.parseFloat(euclideanDistanceArray(identity.descriptors, average))
|
||||||
.toFixed(4);
|
.toFixed(4);
|
||||||
|
|
||||||
/* If the average position has not changed, then face distances should
|
const t = await photoDB.sequelize.transaction();
|
||||||
* not change either! */
|
try {
|
||||||
await Promise.map(faces, async (face) => {
|
/* If the average position has not changed, then face distances should
|
||||||
/* All the buffer are already arrays, so use the short-cut version */
|
* not change either! */
|
||||||
const distance = Number
|
await Promise.map(faces, async (face) => {
|
||||||
.parseFloat(euclideanDistanceArray(face.descriptors, average))
|
/* All the buffer are already arrays, so use the short-cut version */
|
||||||
.toFixed(4);
|
const distance = Number
|
||||||
|
.parseFloat(euclideanDistanceArray(face.descriptors, average))
|
||||||
|
.toFixed(4);
|
||||||
|
|
||||||
|
if (Math.abs(distance - face.distance) > MIN_DISTANCE_COMMIT) {
|
||||||
|
console.log(
|
||||||
|
`Updating face ${face.id} to ${round(distance, 2)} ` +
|
||||||
|
`(${distance - face.distance}) ` +
|
||||||
|
`from identity ${identity.identityId} (${identity.displayName})`);
|
||||||
|
face.distance = distance;
|
||||||
|
await photoDB.sequelize.query(
|
||||||
|
'UPDATE faces SET distance=:distance WHERE id=:id', {
|
||||||
|
replacements: face,
|
||||||
|
transaction: t
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
concurrency: 5
|
||||||
|
});
|
||||||
|
|
||||||
if (Math.abs(distance - face.distance) > MIN_DISTANCE_COMMIT) {
|
let sql = '';
|
||||||
|
/* If there is a new closestId, then set the faceId field */
|
||||||
|
if (closestId !== -1 && closestId !== identity.faceId) {
|
||||||
console.log(
|
console.log(
|
||||||
`Updating face ${face.id} to ${round(distance, 2)} ` +
|
`Updating identity ${identity.identityId} closest face to ${closestId}`);
|
||||||
`(${distance - face.distance}) ` +
|
sql = `${sql} faceId=:faceId`;
|
||||||
`from identity ${identity.identityId} (${identity.displayName})`);
|
identity.faceId = closestId;
|
||||||
face.distance = distance;
|
}
|
||||||
|
|
||||||
|
/* If the centroid changed, update the identity descriptors to
|
||||||
|
* the new average */
|
||||||
|
if (Math.abs(moved) > MIN_DISTANCE_COMMIT) {
|
||||||
|
console.log(
|
||||||
|
`Updating identity ${identity.identityId} centroid ` +
|
||||||
|
`(moved ${Number.parseFloat(moved).toFixed(4)}).`);
|
||||||
|
if (sql !== '') {
|
||||||
|
sql = `${sql}, `;
|
||||||
|
}
|
||||||
|
sql = `${sql} descriptors=:descriptors`;
|
||||||
|
// this: identity.descriptors = average;
|
||||||
|
// gives: Invalid value Float64Array(2622)
|
||||||
|
//
|
||||||
|
// this: identity.descriptors = new Blob(average);
|
||||||
|
// gives: Invalid value Blob { size: 54008, type: '' }
|
||||||
|
//
|
||||||
|
// this: identity.descriptors = Buffer.from(average);
|
||||||
|
// gives: all zeroes
|
||||||
|
//
|
||||||
|
// this: identity.descriptors = Buffer.from(average.buffer);
|
||||||
|
// gives: IT WORKS!!!
|
||||||
|
identity.descriptors = Buffer.from(average.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the number of faces changed, update the facesCount */
|
||||||
|
if (identity.facesCount !== faces.length) {
|
||||||
|
if (sql !== '') {
|
||||||
|
sql = `${sql}, `;
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
`Updating identity ${identity.identityId} face count to ${faces.length}`);
|
||||||
|
identity.facesCount = faces.length;
|
||||||
|
sql = `${sql} facesCount=${faces.length}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If any of the above required changes, actually commit to the DB */
|
||||||
|
if (sql !== '') {
|
||||||
await photoDB.sequelize.query(
|
await photoDB.sequelize.query(
|
||||||
'UPDATE faces SET distance=:distance WHERE id=:id', {
|
`UPDATE identities SET ${sql} ` +
|
||||||
replacements: face
|
`WHERE id=:identityId`, {
|
||||||
|
replacements: identity,
|
||||||
|
transaction: t
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, {
|
t.commit();
|
||||||
concurrency: 5
|
} catch (error) {
|
||||||
});
|
console.error(error);
|
||||||
|
t.rollback();
|
||||||
let sql = '';
|
return;
|
||||||
/* If there is a new closestId, then set the faceId field */
|
|
||||||
if (closestId !== -1 && closestId !== identity.faceId) {
|
|
||||||
console.log(
|
|
||||||
`Updating identity ${identity.identityId} closest face to ${closestId}`);
|
|
||||||
sql = `${sql} faceId=:faceId`;
|
|
||||||
identity.faceId = closestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the centroid changed, update the identity descriptors to
|
|
||||||
* the new average */
|
|
||||||
if (Math.abs(moved) > MIN_DISTANCE_COMMIT) {
|
|
||||||
console.log(
|
|
||||||
`Updating identity ${identity.identityId} centroid ` +
|
|
||||||
`(moved ${Number.parseFloat(moved).toFixed(4)}).`);
|
|
||||||
if (sql !== '') {
|
|
||||||
sql = `${sql}, `;
|
|
||||||
}
|
|
||||||
sql = `${sql} descriptors=:descriptors`;
|
|
||||||
// this: identity.descriptors = average;
|
|
||||||
// gives: Invalid value Float64Array(2622)
|
|
||||||
//
|
|
||||||
// this: identity.descriptors = new Blob(average);
|
|
||||||
// gives: Invalid value Blob { size: 54008, type: '' }
|
|
||||||
//
|
|
||||||
// this: identity.descriptors = Buffer.from(average);
|
|
||||||
// gives: all zeroes
|
|
||||||
//
|
|
||||||
// this: identity.descriptors = Buffer.from(average.buffer);
|
|
||||||
// gives: IT WORKS!!!
|
|
||||||
identity.descriptors = Buffer.from(average.buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If the number of faces changed, update the facesCount */
|
|
||||||
if (identity.facesCount !== faces.length) {
|
|
||||||
if (sql !== '') {
|
|
||||||
sql = `${sql}, `;
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
`Updating identity ${identity.identityId} face count to ${faces.length}`);
|
|
||||||
identity.facesCount = faces.length;
|
|
||||||
sql = `${sql} facesCount=${faces.length}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If any of the above required changes, actually commit to the DB */
|
|
||||||
if (sql !== '') {
|
|
||||||
await photoDB.sequelize.query(
|
|
||||||
`UPDATE identities SET ${sql} ` +
|
|
||||||
`WHERE id=:identityId`, {
|
|
||||||
replacements: identity
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user