Compare commits
3 Commits
34960de283
...
a71fb177e9
Author | SHA1 | Date | |
---|---|---|---|
a71fb177e9 | |||
e5a55de73c | |||
f6685e78e1 |
@ -19,12 +19,19 @@ div {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Resizer {
|
.Explorer .Resizer {
|
||||||
width: 0.5rem;
|
width: 0.5rem;
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ExplorerVertical .Resizer {
|
||||||
|
height: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ccc;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
.Explorer {
|
.Explorer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -35,6 +42,16 @@ div {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ExplorerVertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-self: stretch;
|
||||||
|
align-self: stretch;
|
||||||
|
width: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.Actions {
|
.Actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@ -44,15 +61,23 @@ div {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PhotoFaces,
|
||||||
.Identities {
|
.Identities {
|
||||||
display: grid;
|
display: grid;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow-y: scroll;
|
|
||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-height: 100%; /* scroll if too large */
|
max-height: 100%; /* scroll if too large */
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PhotoFaces {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(6.25rem, auto));
|
||||||
|
}
|
||||||
|
|
||||||
|
.Identities {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(4.25rem, auto));
|
grid-template-columns: repeat(auto-fill, minmax(4.25rem, auto));
|
||||||
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Face {
|
.Face {
|
||||||
@ -73,16 +98,26 @@ div {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Viewer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.PhotoPanel {
|
.PhotoPanel {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
max-height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PhotoPanel .FaceInfo {
|
||||||
|
padding: 0.25rem;
|
||||||
|
background-color: #444;
|
||||||
|
color: white;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PhotoPanel .ImageInfo {
|
||||||
|
padding: 0.25rem;
|
||||||
|
background-color: #222;
|
||||||
|
color: white;
|
||||||
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Guess {
|
.Guess {
|
||||||
@ -96,34 +131,34 @@ button {
|
|||||||
min-width: 4rem;
|
min-width: 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Image .FaceBox {
|
.PhotoInfo,
|
||||||
|
.FaceInfo {
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
align-self: center;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Photo .FaceBox {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Image .FaceBox:hover {
|
.Photo .FaceBox:hover {
|
||||||
background-color: rgba(255, 255, 255, 0.2);
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
box-shadow: 0px 0px 10px black;
|
box-shadow: 0px 0px 10px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Image {
|
.Photo {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.PhotoPanel .FaceInfo {
|
.PhotoFaces .UnknownFace,
|
||||||
padding: 0.25rem;
|
|
||||||
background-color: #444;
|
|
||||||
color: white;
|
|
||||||
margin-top: 0.25rem;}
|
|
||||||
|
|
||||||
.PhotoPanel .ImageInfo {
|
|
||||||
padding: 0.25rem;
|
|
||||||
background-color: #222;
|
|
||||||
color: white;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Identities .UnknownFace {
|
.Identities .UnknownFace {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
}
|
||||||
@ -174,6 +209,11 @@ button {
|
|||||||
height: 10rem;
|
height: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PhotoFaces .Face .Image {
|
||||||
|
min-width: 6rem;
|
||||||
|
min-height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
.Identities .Face .Image {
|
.Identities .Face .Image {
|
||||||
min-width: 4rem;
|
min-width: 4rem;
|
||||||
min-height: 4rem;
|
min-height: 4rem;
|
||||||
@ -210,7 +250,7 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.Viewer .PhotoPanel img {
|
.Photo img {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
@ -62,10 +62,27 @@ const makeFaceBoxes = (photo: any,
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type PhotoData = {
|
||||||
|
photoId: number,
|
||||||
|
faces: FaceData[],
|
||||||
|
filename: string,
|
||||||
|
path: string,
|
||||||
|
taken: number
|
||||||
|
};
|
||||||
|
|
||||||
|
const EmptyPhoto = {
|
||||||
|
photoId: -1,
|
||||||
|
faces: [],
|
||||||
|
filename: '',
|
||||||
|
path: '',
|
||||||
|
taken: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
const Photo = ({ photoId, onFaceClick }: any) => {
|
const Photo = ({ photoId, onFaceClick }: any) => {
|
||||||
const [image, setImage] = useState<any>(undefined);
|
const [photo, setPhoto] = useState<PhotoData>(EmptyPhoto);
|
||||||
const [faceInfo, setFaceInfo] = useState<string>('');
|
const [faceInfo, setFaceInfo] = useState<string>('');
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
const [photoSelected, setPhotoSelected] = useState<FaceData[]>([]);
|
||||||
const [dimensions, setDimensions] = React.useState({width: 0, height: 0});
|
const [dimensions, setDimensions] = React.useState({width: 0, height: 0});
|
||||||
|
|
||||||
const onFaceEnter = (e: any, face: FaceData) => {
|
const onFaceEnter = (e: any, face: FaceData) => {
|
||||||
@ -79,12 +96,12 @@ const Photo = ({ photoId, onFaceClick }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const faces = useMemo(() => {
|
const faces = useMemo(() => {
|
||||||
if (image === undefined || dimensions.height === 0) {
|
if (photo === undefined || dimensions.height === 0) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return makeFaceBoxes(image, dimensions,
|
return makeFaceBoxes(photo, dimensions,
|
||||||
onFaceClick, onFaceEnter, onFaceLeave);
|
onFaceClick, onFaceEnter, onFaceLeave);
|
||||||
}, [image, dimensions, onFaceClick]);
|
}, [photo, dimensions, onFaceClick]);
|
||||||
|
|
||||||
const checkResize = useCallback(() => {
|
const checkResize = useCallback(() => {
|
||||||
if (!ref.current) {
|
if (!ref.current) {
|
||||||
@ -101,7 +118,6 @@ const Photo = ({ photoId, onFaceClick }: any) => {
|
|||||||
}
|
}
|
||||||
}, [setDimensions, dimensions]);
|
}, [setDimensions, dimensions]);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let timer = setInterval(() => checkResize(), 250);
|
let timer = setInterval(() => checkResize(), 250);
|
||||||
return () => { clearInterval(timer); }
|
return () => { clearInterval(timer); }
|
||||||
@ -111,35 +127,110 @@ const Photo = ({ photoId, onFaceClick }: any) => {
|
|||||||
if (photoId === 0) {
|
if (photoId === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fetchImageData = async (image: number) => {
|
const fetchPhotoData = async (photoId: number) => {
|
||||||
console.log(`Loading photo ${image}`);
|
console.log(`Loading photo ${photoId}`);
|
||||||
const res = await window.fetch(`${base}/api/v1/photos/${image}`);
|
const res = await window.fetch(`${base}/api/v1/photos/${photoId}`);
|
||||||
const photo = await res.json();
|
const photo = await res.json();
|
||||||
setImage(photo);
|
setPhoto(photo);
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchImageData(photoId);
|
setPhotoSelected([]);
|
||||||
}, [photoId, setImage]);
|
fetchPhotoData(photoId);
|
||||||
|
}, [photoId, setPhoto, setPhotoSelected]);
|
||||||
|
|
||||||
if (image === undefined) {
|
const forget = async () => {
|
||||||
|
try {
|
||||||
|
const res = await window.fetch(
|
||||||
|
`${base}/api/v1/faces`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'forget',
|
||||||
|
faces: photoSelected.map(face => face.faceId)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
await res.json();
|
||||||
|
setPhotoSelected([]);
|
||||||
|
photo.faces = photo.faces.filter(
|
||||||
|
face => photoSelected.findIndex(x => x.faceId === face.faceId) === -1
|
||||||
|
);
|
||||||
|
setPhoto({...photo});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAll = async () => {
|
||||||
|
setPhotoSelected([...photo.faces]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSelection = async () => {
|
||||||
|
setPhotoSelected([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const faceClick = useCallback((e: any, face: FaceData) => {
|
||||||
|
const el = e.currentTarget;
|
||||||
|
/* Control -- select / deselect single item */
|
||||||
|
if (e.ctrlKey) {
|
||||||
|
const index = photoSelected.findIndex(x => x.faceId === face.faceId);
|
||||||
|
if (index !== -1) {
|
||||||
|
el.classList.remove('Selected');
|
||||||
|
photoSelected.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
el.classList.add('Selected');
|
||||||
|
photoSelected.push(face);
|
||||||
|
}
|
||||||
|
setPhotoSelected([...photoSelected]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shift -- select groups */
|
||||||
|
if (e.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default to load image */
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
}, [photoSelected, setPhotoSelected]);
|
||||||
|
|
||||||
|
if (photo.photoId === -1) {
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<div className="PhotoPanel">
|
return (
|
||||||
<div className="Image" ref={ref}>
|
<div className="PhotoPanel">
|
||||||
|
<div className="Photo" ref={ref}>
|
||||||
<img
|
<img
|
||||||
alt={image.filename}
|
alt={photo.filename}
|
||||||
src={`${base}/../${image.path}thumbs/scaled/${image.filename}`.replace(/ /g, '%20')}/>
|
src={`${base}/../${photo.path}thumbs/scaled/${photo.filename}`.replace(/ /g, '%20')}/>
|
||||||
{ faces }
|
{ faces }
|
||||||
</div>
|
</div>
|
||||||
<div className="ImageInfo">{
|
<div className="PhotoInfo">{
|
||||||
moment(image.taken)
|
moment(photo.taken).format('MMMM Do YYYY, h:mm:ss a')
|
||||||
.format('MMMM Do YYYY, h:mm:ss a')
|
|
||||||
}, {
|
}, {
|
||||||
moment(image.taken)
|
moment(photo.taken).fromNow()
|
||||||
.fromNow()
|
|
||||||
}.</div>
|
}.</div>
|
||||||
<div className="FaceInfo">{ faceInfo ? faceInfo : 'Hover over face for information.'}</div>
|
<div className="FaceInfo">
|
||||||
|
{ faceInfo ? faceInfo : 'Hover over face for information.'}
|
||||||
|
</div>
|
||||||
|
<div className="Actions">
|
||||||
|
{ photoSelected.length !== 0 && <Button onClick={forget}>
|
||||||
|
Forget Selected</Button> }
|
||||||
|
<Button onClick={selectAll}>Select All</Button>
|
||||||
|
<Button onClick={clearSelection}>Select None</Button>
|
||||||
|
</div>
|
||||||
|
{ photoSelected.length !== 0 && <div style={{ display: 'flex' }}>
|
||||||
|
Selected faces (CTRL-CLICK to remove):
|
||||||
|
</div> }
|
||||||
|
<div className="PhotoFaces">
|
||||||
|
{ photoSelected.map(face =>
|
||||||
|
<Face
|
||||||
|
key={face.faceId}
|
||||||
|
face={face}
|
||||||
|
onFaceClick={(e: any) => faceClick(e, face)}/>
|
||||||
|
) }
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -738,6 +829,26 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const forgetFace = async () => {
|
||||||
|
try {
|
||||||
|
const res = await window.fetch(
|
||||||
|
`${base}/api/v1/faces`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'forget',
|
||||||
|
faces: selected
|
||||||
|
})
|
||||||
|
});
|
||||||
|
await res.json();
|
||||||
|
removeFacesFromIdentity(selected);
|
||||||
|
deselectAll();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const changeSelectedIdentity = async () => {
|
const changeSelectedIdentity = async () => {
|
||||||
if (selectedIdentities.length === 0) {
|
if (selectedIdentities.length === 0) {
|
||||||
window.alert('You need to select an identity first (CTRL+CLICK)');
|
window.alert('You need to select an identity first (CTRL+CLICK)');
|
||||||
@ -928,6 +1039,7 @@ const App = () => {
|
|||||||
<Button onClick={guessIdentity}>Guess</Button>
|
<Button onClick={guessIdentity}>Guess</Button>
|
||||||
</>}
|
</>}
|
||||||
{ selected.length !== 0 && <>
|
{ selected.length !== 0 && <>
|
||||||
|
<Button onClick={forgetFace}>Forget</Button>
|
||||||
<Button onClick={removeFaceFromIdentity}>Remove</Button>
|
<Button onClick={removeFaceFromIdentity}>Remove</Button>
|
||||||
<Button onClick={updateFasAsNotFace}>Not a face</Button>
|
<Button onClick={updateFasAsNotFace}>Not a face</Button>
|
||||||
<Button onClick={changeSelectedIdentity}>Change Identity</Button>
|
<Button onClick={changeSelectedIdentity}>Change Identity</Button>
|
||||||
@ -939,19 +1051,21 @@ const App = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
<PanelResizeHandle className="Resizer"/>
|
<PanelResizeHandle className="Resizer"/>
|
||||||
<Panel>
|
<Panel><>
|
||||||
<div className="Viewer">
|
{ image === 0 &&
|
||||||
{image === 0 && <div style={{ margin: '1rem' }}>Select image to view</div>}
|
<div style={{ margin: '1rem' }}>Select image to view</div>
|
||||||
{image !== 0 && <Photo onFaceClick={onFaceClick} photoId={image}/> }
|
}
|
||||||
{guess !== undefined && guess.identity && <div
|
{ image !== 0 &&
|
||||||
className="Guess">
|
<Photo onFaceClick={onFaceClick} photoId={image}/>
|
||||||
<Face
|
}
|
||||||
face={guess.identity.relatedFaces[0]}
|
{ guess !== undefined && guess.identity && <div
|
||||||
onFaceClick={guessOnFaceClick}
|
className="Guess">
|
||||||
title={`${guess.identity.displayName} (${guess.distance})`}/>
|
<Face
|
||||||
</div> }
|
face={guess.identity.relatedFaces[0]}
|
||||||
</div>
|
onFaceClick={guessOnFaceClick}
|
||||||
</Panel>
|
title={`${guess.identity.displayName} (${guess.distance})`}/>
|
||||||
|
</div> }
|
||||||
|
</></Panel>
|
||||||
<PanelResizeHandle className="Resizer" />
|
<PanelResizeHandle className="Resizer" />
|
||||||
<Panel defaultSize={8.5} minSize={8.5} className="IdentitiesList">
|
<Panel defaultSize={8.5} minSize={8.5} className="IdentitiesList">
|
||||||
{ !loaded && <div style={{ margin: '1rem' }}>
|
{ !loaded && <div style={{ margin: '1rem' }}>
|
||||||
|
@ -101,6 +101,7 @@ def load_faces(db_path ):
|
|||||||
JOIN facedescriptors ON (faces.descriptorId=facedescriptors.id)
|
JOIN facedescriptors ON (faces.descriptorId=facedescriptors.id)
|
||||||
WHERE faces.identityId IS null
|
WHERE faces.identityId IS null
|
||||||
AND faces.classifiedBy != 'not-a-face'
|
AND faces.classifiedBy != 'not-a-face'
|
||||||
|
AND faces.classifiedBy != 'forget'
|
||||||
AND faces.photoId=photos.id
|
AND faces.photoId=photos.id
|
||||||
''')
|
''')
|
||||||
for row in res.fetchall():
|
for row in res.fetchall():
|
||||||
|
52
server/db/MODIFY.md
Normal file
52
server/db/MODIFY.md
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
|
||||||
|
# Dump schema
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat << EOF | sqlite3 db/photos.db > photos.schema
|
||||||
|
.schema
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
# Edit schema
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nano photos.schema
|
||||||
|
```
|
||||||
|
|
||||||
|
# Backup database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp db/photos.db photos.db.bk
|
||||||
|
```
|
||||||
|
|
||||||
|
# For the table you want to modify
|
||||||
|
|
||||||
|
## Backup the table
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat << EOF | sqlite3 db/photos.db
|
||||||
|
.output faces.dump
|
||||||
|
.dump faces
|
||||||
|
.quit
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Drop the table
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat << EOF | sqlite3 db/photos.db
|
||||||
|
drop table faces
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create the table with the modified schema
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat photos.schema | sqlite3 db/photos.db
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-populate the table
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat faces.dump | sqlite3 db/photos.db
|
||||||
|
```
|
@ -197,6 +197,7 @@ function init() {
|
|||||||
type: Sequelize.DataTypes.ENUM(
|
type: Sequelize.DataTypes.ENUM(
|
||||||
'machine', /* DBSCAN with VGG-Face */
|
'machine', /* DBSCAN with VGG-Face */
|
||||||
'human', /* Human identified */
|
'human', /* Human identified */
|
||||||
|
'forget', /* implies "human"; identityId=NULL */
|
||||||
'not-a-face'), /* implies "human"; identityId=NULL */
|
'not-a-face'), /* implies "human"; identityId=NULL */
|
||||||
defaultValue: 'machine',
|
defaultValue: 'machine',
|
||||||
},
|
},
|
||||||
|
@ -36,27 +36,16 @@ router.put("/:id?", async (req, res/*, next*/) => {
|
|||||||
}
|
}
|
||||||
const { action } = req.body;
|
const { action } = req.body;
|
||||||
console.log(`${action}: ${faces}`);
|
console.log(`${action}: ${faces}`);
|
||||||
switch (action) {
|
if ([ 'not-a-face', 'forget' ].indexOf(action) !== -1) {
|
||||||
case 'not-a-face':
|
|
||||||
await photoDB.sequelize.query(
|
await photoDB.sequelize.query(
|
||||||
`UPDATE faces SET classifiedBy='not-a-face',identityId=NULL ` +
|
`UPDATE faces SET classifiedBy=:action,identityId=NULL ` +
|
||||||
`WHERE id IN (:faces)`, {
|
`WHERE id IN (:faces)`, {
|
||||||
replacements: { faces }
|
replacements: {
|
||||||
|
action,
|
||||||
|
faces
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
/*
|
|
||||||
faces = await photoDB.sequelize.query(
|
|
||||||
'SELECT * FROM faces WHERE id IN (:faces)', {
|
|
||||||
replacements: { faces },
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
faces.forEach(face => {
|
|
||||||
face.faceId = face.id;
|
|
||||||
delete face.id;
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
return res.status(200).json(faces);
|
return res.status(200).json(faces);
|
||||||
}
|
}
|
||||||
return res.status(400).json({ message: "Invalid request" });
|
return res.status(400).json({ message: "Invalid request" });
|
||||||
|
@ -448,8 +448,12 @@ router.put("/faces/add/:id", async (req, res) => {
|
|||||||
/* Do not block on this call finishing -- update can occur
|
/* Do not block on this call finishing -- update can occur
|
||||||
* in the background */
|
* in the background */
|
||||||
Promise.map([identity, ...tuples], (x, i) => {
|
Promise.map([identity, ...tuples], (x, i) => {
|
||||||
|
if (x.identityId === null) {
|
||||||
|
/* Moving from the Unknown group */
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
updateIdentityFaces(x);
|
updateIdentityFaces(x);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(i, x);
|
console.log(i, x);
|
||||||
throw error;
|
throw error;
|
||||||
@ -528,10 +532,12 @@ const getUnknownIdentity = async (faceCount) => {
|
|||||||
faces AS total
|
faces AS total
|
||||||
WHERE
|
WHERE
|
||||||
total.identityId IS NULL
|
total.identityId IS NULL
|
||||||
AND total.classifiedBy != 'not-a-face') AS total
|
AND total.classifiedBy != 'not-a-face'
|
||||||
|
AND total.classifiedBy != 'forget') AS total
|
||||||
WHERE
|
WHERE
|
||||||
faces.identityId IS NULL
|
faces.identityId IS NULL
|
||||||
AND faces.classifiedBy != 'not-a-face'
|
AND faces.classifiedBy != 'not-a-face'
|
||||||
|
AND faces.classifiedBy != 'forget'
|
||||||
${ limit }`;
|
${ limit }`;
|
||||||
|
|
||||||
unknownIdentity.relatedFaces = await photoDB.sequelize.query(sql, {
|
unknownIdentity.relatedFaces = await photoDB.sequelize.query(sql, {
|
||||||
|
@ -1109,6 +1109,7 @@ router.get("/:id", async (req, res) => {
|
|||||||
`
|
`
|
||||||
SELECT faces.* FROM faces
|
SELECT faces.* FROM faces
|
||||||
WHERE faces.photoId=:id
|
WHERE faces.photoId=:id
|
||||||
|
AND faces.classifiedBy NOT IN ('not-a-face', 'forget' )
|
||||||
`, {
|
`, {
|
||||||
replacements: { id },
|
replacements: { id },
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT
|
type: photoDB.Sequelize.QueryTypes.SELECT
|
||||||
|
Loading…
x
Reference in New Issue
Block a user