Compare commits
2 Commits
5d38cb4787
...
0043480ff8
Author | SHA1 | Date | |
---|---|---|---|
0043480ff8 | |||
1ead088eb8 |
@ -56,9 +56,14 @@ div {
|
|||||||
border: 0.25rem solid transparent;
|
border: 0.25rem solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ClusterEditor {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.Image .FaceBox {
|
.Image .FaceBox {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
border-radius: 0.25rem;
|
/* border-radius: 0.25rem;*/
|
||||||
position: absolute;
|
position: absolute;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,55 +37,31 @@ const makeFaceBoxes = (photo: any, dimensions: any): any => {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
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 Photo = ({ photoId }: any) => {
|
||||||
const [image, setImage] = useState<any>(undefined);
|
const [image, setImage] = useState<any>(undefined);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [dimensions, setDimensions] = React.useState({
|
const [dimensions, setDimensions] = React.useState({width: 0, height: 0});
|
||||||
height: window.innerHeight,
|
|
||||||
width: window.innerWidth
|
|
||||||
})
|
|
||||||
|
|
||||||
const faces = useMemo(() => {
|
const faces = useMemo(() => {
|
||||||
if (image === undefined) {
|
if (image === undefined || dimensions.height === 0) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
return makeFaceBoxes(image, dimensions);
|
return makeFaceBoxes(image, dimensions);
|
||||||
}, [image, dimensions]);
|
}, [image, dimensions]);
|
||||||
|
|
||||||
useEffect(() : any => {
|
useEffect(() => {
|
||||||
if (!ref || !ref.current) {
|
if (!ref.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const el: Element = ref.current as Element;
|
const el: Element = ref.current as Element;
|
||||||
|
if (dimensions.height !== el.clientHeight
|
||||||
const handleResize = () => {
|
|| dimensions.width !== el.clientWidth) {
|
||||||
setDimensions({
|
setDimensions({
|
||||||
height: el.clientHeight,
|
height: el.clientHeight,
|
||||||
width: el.clientWidth
|
width: el.clientWidth
|
||||||
})
|
})
|
||||||
};
|
}
|
||||||
|
}/*, [dimensions.height, dimensions.width]*/);
|
||||||
const debouncedHandleResize = handleResize;//debounce(handleResize, 250);
|
|
||||||
debouncedHandleResize();
|
|
||||||
window.addEventListener('resize', debouncedHandleResize);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener('resize', debouncedHandleResize)
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (photoId === 0) {
|
if (photoId === 0) {
|
||||||
@ -108,15 +84,15 @@ const Photo = ({ photoId }: any) => {
|
|||||||
return (<div className="Image"
|
return (<div className="Image"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={{
|
style={{
|
||||||
background: `url(../${image.path}thumbs/scaled/${image.filename})`
|
background: `url(../${image.path}thumbs/scaled/${image.filename})`.replace(/ /g, '%20')
|
||||||
}}>{ faces }</div>
|
}}>{ faces }</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Face = ({ faceId, onClick, title }: any) => {
|
const Face = ({ faceId, onClick, title, ...rest }: any) => {
|
||||||
const idPath = String(faceId % 100).padStart(2, '0');
|
const idPath = String(faceId % 100).padStart(2, '0');
|
||||||
return (
|
return (
|
||||||
<div onClick={(e) => { onClick(e, faceId) }}
|
<div {...rest} onClick={(e) => { onClick(e, faceId) }}
|
||||||
className='Face'>
|
className='Face'>
|
||||||
<div className='Image'
|
<div className='Image'
|
||||||
style={{
|
style={{
|
||||||
@ -129,26 +105,13 @@ const Face = ({ faceId, onClick, title }: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ClusterProps = {
|
type ClusterProps = {
|
||||||
id: number,
|
identity: Identity,
|
||||||
setImage(image: number): void
|
setImage(image: number): void,
|
||||||
|
setSelected(selected: number[]): void,
|
||||||
|
setIdentity(identity: Identity): void
|
||||||
};
|
};
|
||||||
|
|
||||||
const Cluster = ({ id, setImage }: ClusterProps) => {
|
const Cluster = ({ identity, setIdentity, setImage, setSelected }: 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 relatedFacesJSX = useMemo(() => {
|
||||||
const faceClicked = async (e: any, id: any) => {
|
const faceClicked = async (e: any, id: any) => {
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
@ -166,18 +129,25 @@ const Cluster = ({ id, setImage }: ClusterProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
el.classList.toggle('Selected');
|
el.classList.toggle('Selected');
|
||||||
|
const selected = [...el.parentElement
|
||||||
|
.querySelectorAll('.Selected')]
|
||||||
|
.map((face: any) => face.getAttribute('data-face-id'));
|
||||||
|
setSelected(selected);
|
||||||
console.log(face);
|
console.log(face);
|
||||||
}
|
}
|
||||||
if (identity === undefined) {
|
if (identity === undefined) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return identity.relatedFaces.map(face =>
|
return identity.relatedFaces.map(face =>
|
||||||
<Face key={face.faceId}
|
<Face
|
||||||
|
data-face-id={face.faceId}
|
||||||
|
key={face.faceId}
|
||||||
faceId={face.faceId}
|
faceId={face.faceId}
|
||||||
onClick={faceClicked}
|
onClick={faceClicked}
|
||||||
title={face.distance}/>
|
title={face.distance}/>
|
||||||
);
|
);
|
||||||
}, [identity, setImage]);
|
}, [identity, setImage, setSelected]);
|
||||||
|
|
||||||
const lastNameChanged = (e: any) => {
|
const lastNameChanged = (e: any) => {
|
||||||
setIdentity(Object.assign(
|
setIdentity(Object.assign(
|
||||||
@ -212,12 +182,6 @@ const Cluster = ({ id, setImage }: ClusterProps) => {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (<div className='Cluster'>
|
|
||||||
{loading && `Loading ${id}...`}
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identity === undefined) {
|
if (identity === undefined) {
|
||||||
return (<div className='Cluster'>
|
return (<div className='Cluster'>
|
||||||
Select identity to load.
|
Select identity to load.
|
||||||
@ -281,21 +245,23 @@ type Identity = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface IdentitiesProps {
|
interface IdentitiesProps {
|
||||||
setIdentity?(id: number): void,
|
setIdentity(identity: Identity): void,
|
||||||
identities: Identity[]
|
identities: Identity[]
|
||||||
};
|
};
|
||||||
|
|
||||||
const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
||||||
const identitiesJSX = useMemo(() => {
|
const identitiesJSX = useMemo(() => {
|
||||||
const loadIdentity = (id: number): void => {
|
const loadIdentity = async (id: number) => {
|
||||||
if (setIdentity) {
|
const res = await window.fetch(`../api/v1/identities/${id}`);
|
||||||
setIdentity(id)
|
const data = await res.json();
|
||||||
}
|
setIdentity(data[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return identities.map((identity) => {
|
return identities.map((identity) => {
|
||||||
const face = identity.relatedFaces[0];
|
const face = identity.relatedFaces[0];
|
||||||
return (
|
return (
|
||||||
<Face key={face.faceId}
|
<Face key={face.faceId}
|
||||||
|
data-face-id={face.faceId}
|
||||||
faceId={face.faceId}
|
faceId={face.faceId}
|
||||||
onClick={() => loadIdentity(identity.id)}
|
onClick={() => loadIdentity(identity.id)}
|
||||||
title={identity.displayName}/>
|
title={identity.displayName}/>
|
||||||
@ -310,13 +276,22 @@ const Identities = ({ identities, setIdentity } : IdentitiesProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Button = ({ onClick, children }: any) => {
|
||||||
|
return (
|
||||||
|
<button type="button" onClick={onClick}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [identities, setIdentities] = useState<Identity[]>([]);
|
const [identities, setIdentities] = useState<Identity[]>([]);
|
||||||
const [identity, setIdentity] = useState<number>(0);
|
const [identity, setIdentity] = useState<any>(undefined);
|
||||||
const [image, setImage] = useState<number>(0);
|
const [image, setImage] = useState<number>(0);
|
||||||
const { loading, data } = useApi(
|
const { loading, data } = useApi(
|
||||||
'../api/v1/identities'
|
'../api/v1/identities'
|
||||||
);
|
);
|
||||||
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data && data.length) {
|
if (data && data.length) {
|
||||||
@ -324,17 +299,49 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const removeSelected = async () => {
|
||||||
|
try {
|
||||||
|
const res = await window.fetch(
|
||||||
|
`../api/v1/identities/faces/remove/${identity.id}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ faces: selected })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const pre = identity.relatedFaces.length;
|
||||||
|
/* Remove all relatedFaces which are part of the set of removed
|
||||||
|
* faces */
|
||||||
|
identity.relatedFaces = identity.relatedFaces.filter(
|
||||||
|
(face: FaceData) => data.faces.indexOf(face.faceId) === -1);
|
||||||
|
if (pre !== identity.relatedFaces.length) {
|
||||||
|
setIdentity({...identity})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<div className="Worksheet">
|
<div className="Worksheet">
|
||||||
<PanelGroup className="Explorer"
|
<PanelGroup className="Explorer"
|
||||||
autoSaveId="persistence" direction="horizontal">
|
autoSaveId="persistence" direction="horizontal">
|
||||||
<Panel defaultSize={50}>
|
<Panel defaultSize={50} className="ClusterEditor">
|
||||||
{loading && <div style={{ margin: '1rem' }}>Loading...</div>}
|
{loading && <div style={{ margin: '1rem' }}>Loading...</div>}
|
||||||
{!loading && identity !== 0 && <Cluster id={identity} {...{ setImage, }} />}
|
{!loading && identity !== 0 &&
|
||||||
|
<Cluster {...{
|
||||||
|
identity,
|
||||||
|
setIdentity,
|
||||||
|
setImage,
|
||||||
|
setSelected
|
||||||
|
}} />}
|
||||||
{!loading && identity === 0 && <div className="Cluster">
|
{!loading && identity === 0 && <div className="Cluster">
|
||||||
Select identity to edit
|
Select identity to edit
|
||||||
</div>}
|
</div>}
|
||||||
|
<div className="Actions">
|
||||||
|
{ selected.length !== 0 && <Button onClick={removeSelected}>Remove</Button> }
|
||||||
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
<PanelResizeHandle className="Resizer"/>
|
<PanelResizeHandle className="Resizer"/>
|
||||||
<Panel>
|
<Panel>
|
||||||
|
@ -24,6 +24,9 @@ html_base = config['basePath']
|
|||||||
if html_base == "/":
|
if html_base == "/":
|
||||||
html_base = "."
|
html_base = "."
|
||||||
|
|
||||||
|
MAX_CLUSTER_DISTANCE = 0.14 # Used to merge clusters
|
||||||
|
MAX_DISTANCE_FROM_CENTROID = 0.14 # Used to prune outliers
|
||||||
|
|
||||||
# TODO
|
# TODO
|
||||||
# Switch to using DBSCAN
|
# Switch to using DBSCAN
|
||||||
#
|
#
|
||||||
@ -86,8 +89,10 @@ def load_faces(db_path = db_path):
|
|||||||
res = cur.execute('''
|
res = cur.execute('''
|
||||||
SELECT faces.id,facedescriptors.descriptors,faces.faceConfidence,faces.photoId,faces.focus
|
SELECT faces.id,facedescriptors.descriptors,faces.faceConfidence,faces.photoId,faces.focus
|
||||||
FROM faces
|
FROM faces
|
||||||
|
INNER JOIN photos ON (photos.duplicate == 0 OR photos.duplicate IS NULL)
|
||||||
JOIN facedescriptors ON (faces.descriptorId=facedescriptors.id)
|
JOIN facedescriptors ON (faces.descriptorId=facedescriptors.id)
|
||||||
WHERE faces.identityId IS null AND faces.faceConfidence>0.99
|
WHERE faces.identityId IS null AND faces.faceConfidence>0.99
|
||||||
|
AND faces.photoId=photos.id
|
||||||
''')
|
''')
|
||||||
for row in res.fetchall():
|
for row in res.fetchall():
|
||||||
id, descriptors, confidence, photoId, focus = row
|
id, descriptors, confidence, photoId, focus = row
|
||||||
@ -109,13 +114,15 @@ def load_faces(db_path = db_path):
|
|||||||
faces.append(face)
|
faces.append(face)
|
||||||
return faces
|
return faces
|
||||||
|
|
||||||
def update_distances(identities, prune = False):
|
def update_distances(identities,
|
||||||
|
prune = False,
|
||||||
|
maxDistance = MAX_DISTANCE_FROM_CENTROID):
|
||||||
removed = 0
|
removed = 0
|
||||||
for identity in identities:
|
for identity in identities:
|
||||||
for face in identity['faces']:
|
for face in identity['faces']:
|
||||||
average = identity['descriptors']
|
average = identity['descriptors']
|
||||||
distance = findCosineDistance(average, face['descriptors'])
|
distance = findCosineDistanceBaked(identity, face)
|
||||||
if prune and distance > MAX_EPOCH_DISTANCE:
|
if prune and distance > maxDistance:
|
||||||
average = np.dot(average, len(identity['faces']))
|
average = np.dot(average, len(identity['faces']))
|
||||||
average = np.subtract(average, face['descriptors'])
|
average = np.subtract(average, face['descriptors'])
|
||||||
|
|
||||||
@ -123,7 +130,11 @@ def update_distances(identities, prune = False):
|
|||||||
face['distance'] = 0
|
face['distance'] = 0
|
||||||
identity['faces'].remove(face)
|
identity['faces'].remove(face)
|
||||||
|
|
||||||
identity['descriptors'] = np.divide(average, len(identity['faces']))
|
average = np.divide(average, len(identity['faces']))
|
||||||
|
identity['descriptors'] = average
|
||||||
|
identity['sqrtsummul'] = np.sqrt(np.sum(np.multiply(
|
||||||
|
average, average)))
|
||||||
|
|
||||||
removed += 1
|
removed += 1
|
||||||
else:
|
else:
|
||||||
face['distance'] = distance
|
face['distance'] = distance
|
||||||
@ -159,19 +170,19 @@ def build_straglers(faces):
|
|||||||
|
|
||||||
print('Loading faces from database')
|
print('Loading faces from database')
|
||||||
faces = load_faces()
|
faces = load_faces()
|
||||||
minPts = len(faces) / 100
|
|
||||||
eps = 0.2
|
minPts = max(len(faces) / 500, 5)
|
||||||
|
eps = 0.185
|
||||||
print(f'Scanning {len(faces)} faces for clusters (minPts: {minPts}, eps: {eps})')
|
print(f'Scanning {len(faces)} faces for clusters (minPts: {minPts}, eps: {eps})')
|
||||||
identities = DBSCAN(faces, minPts = minPts, eps = eps)
|
identities = DBSCAN(faces, minPts = minPts, eps = eps)
|
||||||
print(f'{len(identities)} clusters grouped')
|
print(f'{len(identities)} clusters grouped')
|
||||||
|
|
||||||
MAX_CLUSTER_DISTANCE = 0.15 # Used to merge clusters
|
|
||||||
MAX_EPOCH_DISTANCE = 0.14 # Used to prune outliers
|
|
||||||
|
|
||||||
# Compute average center for all clusters
|
# Compute average center for all clusters
|
||||||
identities = update_cluster_averages(identities)
|
identities = update_cluster_averages(identities)
|
||||||
|
epoch_prune = True
|
||||||
|
merge_identities = True
|
||||||
|
|
||||||
if False:
|
if epoch_prune:
|
||||||
removed = -1
|
removed = -1
|
||||||
epoch = 1
|
epoch = 1
|
||||||
# Filter each cluster removing any face that is > cluster_max_distance
|
# Filter each cluster removing any face that is > cluster_max_distance
|
||||||
@ -179,14 +190,17 @@ if False:
|
|||||||
while removed != 0:
|
while removed != 0:
|
||||||
print(f'Epoch {epoch}...')
|
print(f'Epoch {epoch}...')
|
||||||
epoch += 1
|
epoch += 1
|
||||||
removed = update_distances(identities, prune = True)
|
removed = update_distances(
|
||||||
|
identities,
|
||||||
|
prune = True,
|
||||||
|
maxDistance = MAX_DISTANCE_FROM_CENTROID)
|
||||||
if removed > 0:
|
if removed > 0:
|
||||||
print(f'Excluded {removed} faces this epoch')
|
print(f'Excluded {removed} faces this epoch')
|
||||||
|
|
||||||
print(f'{len(identities)} identities seeded.')
|
print(f'{len(identities)} identities seeded.')
|
||||||
reduced = identities
|
reduced = identities
|
||||||
|
|
||||||
if False:
|
if merge_identities:
|
||||||
# Cluster the clusters...
|
# Cluster the clusters...
|
||||||
print('Reducing clusters via DBSCAN')
|
print('Reducing clusters via DBSCAN')
|
||||||
reduced = DBSCAN(identities, eps = MAX_CLUSTER_DISTANCE, minPts = 3)
|
reduced = DBSCAN(identities, eps = MAX_CLUSTER_DISTANCE, minPts = 3)
|
||||||
@ -229,9 +243,7 @@ for id, identity in enumerate(reduced):
|
|||||||
face['cluster'] = identity
|
face['cluster'] = identity
|
||||||
|
|
||||||
reduced = update_cluster_averages(reduced)
|
reduced = update_cluster_averages(reduced)
|
||||||
|
|
||||||
update_distances(reduced)
|
update_distances(reduced)
|
||||||
|
|
||||||
sort_identities(reduced)
|
sort_identities(reduced)
|
||||||
|
|
||||||
if False:
|
if False:
|
||||||
@ -294,6 +306,7 @@ print(f'Connecting to database: {db_path}')
|
|||||||
conn = create_connection(db_path)
|
conn = create_connection(db_path)
|
||||||
with conn:
|
with conn:
|
||||||
for identity in reduced:
|
for identity in reduced:
|
||||||
|
print(f'Writing identity {identity["id"]} to DB')
|
||||||
id = create_identity(conn, identity)
|
id = create_identity(conn, identity)
|
||||||
for face in identity['faces']:
|
for face in identity['faces']:
|
||||||
update_face_identity(conn, face['id'], id)
|
update_face_identity(conn, face['id'], id)
|
||||||
|
@ -197,9 +197,10 @@ conn = create_connection('../db/photos.db')
|
|||||||
with conn:
|
with conn:
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
res = cur.execute('''
|
res = cur.execute('''
|
||||||
SELECT photos.id,photos.faces,albums.path,photos.filename FROM photos
|
SELECT photos.id,photos.faces,albums.path,photos.filename
|
||||||
|
FROM photos
|
||||||
LEFT JOIN albums ON (albums.id=photos.albumId)
|
LEFT JOIN albums ON (albums.id=photos.albumId)
|
||||||
WHERE photos.faces=-1
|
WHERE photos.faces=-1 AND photos.duplicate=0
|
||||||
''')
|
''')
|
||||||
rows = res.fetchall()
|
rows = res.fetchall()
|
||||||
count = len(rows)
|
count = len(rows)
|
||||||
|
@ -11,13 +11,8 @@ Noise = -2
|
|||||||
# Union of two lists of dicts, adding unique elements of B to
|
# Union of two lists of dicts, adding unique elements of B to
|
||||||
# end of A
|
# end of A
|
||||||
def Union(A, B):
|
def Union(A, B):
|
||||||
# 5.012 of 100s sample
|
|
||||||
return A + [x for x in B if x not in A]
|
return A + [x for x in B if x not in A]
|
||||||
# 5.039 of 100s sample
|
|
||||||
# for key in B:
|
|
||||||
# if key not in A:
|
|
||||||
# A.append(key)
|
|
||||||
# return A
|
|
||||||
|
|
||||||
# https://en.wikipedia.org/wiki/DBSCAN
|
# https://en.wikipedia.org/wiki/DBSCAN
|
||||||
#
|
#
|
||||||
@ -91,7 +86,7 @@ def RangeQuery(points, Q, eps):
|
|||||||
for P in points: # Scan all points in the database
|
for P in points: # Scan all points in the database
|
||||||
if P == Q:
|
if P == Q:
|
||||||
continue
|
continue
|
||||||
distance = findCoseinDistanceBaked(# Compute distance
|
distance = findCosineDistanceBaked(# Compute distance
|
||||||
Q, P)
|
Q, P)
|
||||||
if distance <= eps: # Check epsilon
|
if distance <= eps: # Check epsilon
|
||||||
neighbors.append(P) # Add to result
|
neighbors.append(P) # Add to result
|
||||||
|
@ -23,7 +23,6 @@ def redirect_off():
|
|||||||
sys.stdout = original
|
sys.stdout = original
|
||||||
original = None
|
original = None
|
||||||
|
|
||||||
|
|
||||||
def zlib_uuencode(databytes, name='<data>'):
|
def zlib_uuencode(databytes, name='<data>'):
|
||||||
''' Compress databytes with zlib & uuencode the result '''
|
''' Compress databytes with zlib & uuencode the result '''
|
||||||
inbuff = BytesIO(zlib.compress(databytes, 9))
|
inbuff = BytesIO(zlib.compress(databytes, 9))
|
||||||
@ -47,15 +46,11 @@ class NpEncoder(json.JSONEncoder):
|
|||||||
if isinstance(obj, np.ndarray):
|
if isinstance(obj, np.ndarray):
|
||||||
return obj.tolist()
|
return obj.tolist()
|
||||||
|
|
||||||
def findCoseinDistanceBaked(src, dst):
|
def findCosineDistanceBaked(src, dst):
|
||||||
a = np.matmul(np.transpose(src['descriptors']), dst['descriptors'])
|
a = np.matmul(np.transpose(src['descriptors']), dst['descriptors'])
|
||||||
return 1 - (a / (src['sqrtsummul'] * dst['sqrtsummul']))
|
return 1 - (a / (src['sqrtsummul'] * dst['sqrtsummul']))
|
||||||
|
|
||||||
def findCosineDistance(source_representation, test_representation):
|
def findCosineDistance(source_representation, test_representation):
|
||||||
# if type(source_representation) == list:
|
|
||||||
# source_representation = np.array(source_representation)
|
|
||||||
# if type(test_representation) == list:
|
|
||||||
# test_representation = np.array(test_representation)
|
|
||||||
a = np.matmul(np.transpose(source_representation), test_representation)
|
a = np.matmul(np.transpose(source_representation), test_representation)
|
||||||
b = np.sum(np.multiply(source_representation, source_representation))
|
b = np.sum(np.multiply(source_representation, source_representation))
|
||||||
c = np.sum(np.multiply(test_representation, test_representation))
|
c = np.sum(np.multiply(test_representation, test_representation))
|
||||||
|
@ -173,6 +173,11 @@ function init() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
expertAssignment: {
|
||||||
|
type: Sequelize.BOOLEAN,
|
||||||
|
defaultValue: false
|
||||||
|
},
|
||||||
|
|
||||||
lastComparedId: {
|
lastComparedId: {
|
||||||
type: Sequelize.INTEGER,
|
type: Sequelize.INTEGER,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
|
@ -11,6 +11,42 @@ require("../db/photos").then(function(db) {
|
|||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
|
router.put("/faces/remove/:id", (req, res) => {
|
||||||
|
console.log(`PUT ${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 = parseInt(req.params.id);
|
||||||
|
if (id != req.params.id) {
|
||||||
|
return res.status(400).send({ message: "Invalid identity id." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(req.body.faces) || req.body.faces.length == 0) {
|
||||||
|
return res.status(400).send({ message: "No faces supplied." });
|
||||||
|
}
|
||||||
|
|
||||||
|
return photoDB.sequelize.query(
|
||||||
|
"UPDATE faces SET identityId=null " +
|
||||||
|
"WHERE id IN (:faceIds)", {
|
||||||
|
replacements: {
|
||||||
|
identityId: id,
|
||||||
|
faceIds: req.body.faces
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
const identity = {
|
||||||
|
id: id,
|
||||||
|
faces: req.body.faces
|
||||||
|
};
|
||||||
|
return res.status(200).json(identity);
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return res.status(500).send({message: "Error processing request." });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
router.put("/faces/add/:id", (req, res) => {
|
router.put("/faces/add/:id", (req, res) => {
|
||||||
if (!req.user.maintainer) {
|
if (!req.user.maintainer) {
|
||||||
console.warn(`${req.user.name} attempted to modify photos.`);
|
console.warn(`${req.user.name} attempted to modify photos.`);
|
||||||
@ -182,6 +218,7 @@ router.get("/:id?", async (req, res) => {
|
|||||||
delete identity.descriptors;
|
delete identity.descriptors;
|
||||||
delete identity.relatedFaceIds;
|
delete identity.relatedFaceIds;
|
||||||
delete identity.relatedFacePhotoIds;
|
delete identity.relatedFacePhotoIds;
|
||||||
|
delete identity.relatedFaceDescriptorIds;
|
||||||
delete identity.relatedIdentityDescriptors;
|
delete identity.relatedIdentityDescriptors;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user