Show face boxes

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2023-01-17 15:48:24 -08:00
parent a9549d29a9
commit 5d38cb4787
3 changed files with 157 additions and 15 deletions

View File

@ -56,6 +56,27 @@ div {
border: 0.25rem solid transparent;
}
.Image .FaceBox {
border: 1px solid red;
border-radius: 0.25rem;
position: absolute;
}
.Image .FaceBox:hover {
background-color: rgba(255, 255, 255, 0.2);
box-shadow: 0px 0px 5px black;
}
.Image {
display: flex;
width: 100%;
height: 100%;
position: relative;
background-size: contain !important;
background-repeat: no-repeat no-repeat !important;
background-position: 50% 50% !important;
}
.Face:hover .Image {
border: 0.25rem solid yellow;
}
@ -80,9 +101,6 @@ div {
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 {

View File

@ -1,11 +1,91 @@
import React, { useState, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect, useRef } from 'react';
import { useApi } from './useApi';
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
import './App.css';
const makeFaceBoxes = (photo: any, dimensions: any): any => {
const faces: FaceData[] = photo.faces;
let width: number, height: number, offsetLeft = 0, offsetTop = 0;
/* If photo is wider than viewport, it will be 100% width and < 100% height */
if (photo.width / photo.height > dimensions.width / dimensions.height) {
width = dimensions.width;
height = dimensions.height * photo.height / photo.width *
dimensions.width / dimensions.height;
offsetLeft = 0;
offsetTop = (dimensions.height - height) * 0.5;
} else {
width = dimensions.width * photo.width / photo.height *
dimensions.height / dimensions.width;
height = dimensions.height;
offsetLeft = (dimensions.width - width) * 0.5;
offsetTop = 0;
}
return faces.map((face: FaceData) => (
<div className="FaceBox"
key={face.faceId}
style={{
left: offsetLeft + Math.floor(face.left * width) + "px",
top: offsetTop + Math.floor(face.top * height) + "px",
width: Math.floor((face.right - face.left) * width) + "px",
height: Math.floor((face.bottom - face.top) * height) + "px"
}}
/>
));
};
/*
function debounce(fn: any, ms: number) {
let timer: any;
return () => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
timer = null
fn.apply(this as typeof Photo, arguments)
}, ms)
};
};
*/
const Photo = ({ photoId }: any) => {
const [image, setImage] = useState<any>(undefined);
const ref = useRef(null);
const [dimensions, setDimensions] = React.useState({
height: window.innerHeight,
width: window.innerWidth
})
const faces = useMemo(() => {
if (image === undefined) {
return <></>;
}
return makeFaceBoxes(image, dimensions);
}, [image, dimensions]);
useEffect(() : any => {
if (!ref || !ref.current) {
return;
}
const el: Element = ref.current as Element;
const handleResize = () => {
setDimensions({
height: el.clientHeight,
width: el.clientWidth
})
};
const debouncedHandleResize = handleResize;//debounce(handleResize, 250);
debouncedHandleResize();
window.addEventListener('resize', debouncedHandleResize);
return () => {
window.removeEventListener('resize', debouncedHandleResize)
};
});
useEffect(() => {
if (photoId === 0) {
@ -14,8 +94,8 @@ const Photo = ({ photoId }: any) => {
const fetchImageData = async (image: number) => {
console.log(`Loading photo ${image}`);
const res = await window.fetch(`../api/v1/photos/${image}`);
const data = await res.json();
setImage(data[0]);
const photo = await res.json();
setImage(photo);
};
fetchImageData(photoId);
@ -26,9 +106,11 @@ const Photo = ({ photoId }: any) => {
}
return (<div className="Image"
ref={ref}
style={{
background: `url(${image.path}thumbs/scaled/${image.filename})`
}}/>);
background: `url(../${image.path}thumbs/scaled/${image.filename})`
}}>{ faces }</div>
);
};
const Face = ({ faceId, onClick, title }: any) => {
@ -181,7 +263,11 @@ type FaceData = {
displayName: string,
identityId: number,
distance: number,
descriptors: any[]
descriptors: any[],
top: number
right: number,
bottom: number,
left: number,
};
type Identity = {

View File

@ -1084,24 +1084,62 @@ console.log("Trying path as: " + path);
});
router.get("/:id", async (req, res) => {
console.log(`GET ${req.url}`);
const id = parseInt(req.params.id);
try {
const results = await photoDB.sequelize.query(
let results;
results = await photoDB.sequelize.query(
`
SELECT photos.*,albums.path AS path,
faces.identityId,faces.top,faces.left,faces.right,faces.bottom
SELECT photos.*,albums.path AS path
FROM photos
INNER JOIN albums ON albums.id=photos.albumId
INNER JOIN faces ON faces.photoId=photos.id
WHERE photos.id=:id
`, {
replacements: { id }
replacements: { id },
type: photoDB.Sequelize.QueryTypes.SELECT
}
);
if (results.length === 0) {
return res.status(404);
}
return res.status(200).json(results[0]);
const photo = results[0];
results = await photoDB.sequelize.query(
`
SELECT faces.* FROM faces
WHERE faces.photoId=:id
`, {
replacements: { id },
type: photoDB.Sequelize.QueryTypes.SELECT
}
);
photo.faces = results;
/* For each face, look up the Identity and clean up any
* fields we don't want to return vai the rest API */
await Promise.map(photo.faces, async (face) => {
face.faceId = face.id;
delete face.id;
delete face.descriptorId;
delete face.lastComparedId;
delete face.photoId;
delete face.scanVersion;
if (face.identityId) {
const results = await photoDB.sequelize.query(
`
SELECT displayName,firstName,lastName,middleName FROM identities
WHERE id=:id
`, {
replacements: { id: face.identityId },
type: photoDB.Sequelize.QueryTypes.SELECT
}
);
face.identity = results[0];
}
delete face.identityId;
});
return res.status(200).json(photo);
} catch (error) {
console.error(error);
return res.status(404).json({message: `Error connecting to DB for ${id}.`})