Include preview faces

This commit is contained in:
Manav Rathi
2024-10-14 17:20:57 +05:30
parent 5aeb3fa615
commit bf096572a6
4 changed files with 69 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
import { useIsSmallWidth } from "@/base/hooks";
import { pt } from "@/base/i18n";
import { faceCrop, type AnnotatedFaceID } from "@/new/photos/services/ml";
import type { Person } from "@/new/photos/services/ml/people";
import type { Person, PreviewableFace } from "@/new/photos/services/ml/people";
import type { EnteFile } from "@/new/photos/types/file";
import { Skeleton, Typography, styled } from "@mui/material";
import { t } from "i18next";
@@ -198,7 +198,7 @@ export interface SuggestionFaceListProps {
* Faces, each annotated with the corresponding {@link EnteFile}, to show in
* the list.
*/
faces: { enteFile: EnteFile; faceID: string }[];
faces: [];
}
/**
@@ -228,14 +228,10 @@ const SuggestionFaceList_ = styled("div")`
gap: 5px;
`;
interface FaceCropImageViewProps {
/** The ID of the face to display. */
faceID: string;
/** The {@link EnteFile} which contains this face. */
enteFile: EnteFile;
type FaceCropImageViewProps = PreviewableFace & {
/** Width and height for the placeholder. */
placeholderDimension: number;
}
};
/**
* An image view showing the face crop for the given face.

View File

@@ -381,7 +381,6 @@ const SuggestionsDialog: React.FC<SuggestionsDialogProps> = ({
const go = async () => {
try {
const suggestions = await suggestionsForPerson(person);
console.log("fetching");
dispatch({ type: "fetched", personID, suggestions });
} catch (e) {
log.error("Failed to generate suggestions", e);
@@ -426,8 +425,6 @@ const SuggestionsDialog: React.FC<SuggestionsDialogProps> = ({
}
};
console.log({ f: "render", open, person, state });
return (
<Dialog
open={open}
@@ -518,7 +515,7 @@ const SuggestionsList: React.FC<SuggestionsListProps> = ({
}}
key={suggestion.id}
>
<Typography>{`${suggestion.faces.length} faces ntaoheu naoehtu aosnehu asoenuh aoenuht`}</Typography>
<Typography>{`${suggestion.previewFaces.length} faces ntaoheu naoehtu aosnehu asoenuh aoenuht`}</Typography>
<ToggleButtonGroup
value={markedSuggestionIDs.get(suggestion.id)}
exclusive

View File

@@ -45,17 +45,6 @@ export type ClusterFace = Omit<Face, "embedding"> & {
isBadFace: boolean;
};
export interface ClusterPreview {
clusterSize: number;
faces: ClusterPreviewFace[];
}
export interface ClusterPreviewFace {
face: ClusterFace;
cosineSimilarity: number;
wasMerged: boolean;
}
/**
* Generates clusters from the given faces using a batched form of linear
* clustering, with a bit of lookback (and a dollop of heuristics) to get the

View File

@@ -1,3 +1,4 @@
import { assertionFailed } from "@/base/assert";
import log from "@/base/log";
import type { EnteFile } from "../../types/file";
import { getLocalFiles } from "../files";
@@ -144,6 +145,19 @@ export type NamedPerson = Person & {
name: string;
};
/**
* A face ID annotated with the {@link EnteFile} that contains it.
*
* Both these pieces of information are needed for a UI element to show the
* face.
*/
export interface PreviewableFace {
/** The ID of the face to display. */
faceID: string;
/** The {@link EnteFile} which contains this face. */
enteFile: EnteFile;
}
/**
* Construct in-memory people using the data present locally, ignoring faces
* belonging to deleted and hidden files.
@@ -308,7 +322,23 @@ export const filterNamedPeople = (people: Person[]): NamedPerson[] => {
return namedPeople;
};
export type PersonSuggestion = FaceCluster;
export interface PersonSuggestion {
/**
* The ID of the suggestion. This is the same as the cluster's ID,
* duplicated here for the UI's convenience.
*/
id: string;
/**
* The underlying {@link FaceCluster} that is being offered as the
* suggestion.
*/
cluster: FaceCluster;
/**
* A list of up to 3 "preview" faces for this cluster, each annotated with
* the corresponding {@link EnteFile} that contains them.
*/
previewFaces: PreviewableFace[];
}
/**
* Returns suggestions for the given person.
@@ -367,13 +397,41 @@ export const suggestionsForPerson = async (person: CGroupPerson) => {
if (suggest) suggestedClusters.push(cluster);
}
const result = suggestedClusters.sort(
(a, b) => b.faces.length - a.faces.length,
);
suggestedClusters.sort((a, b) => b.faces.length - a.faces.length);
// Annotate the clusters with the information that the UI needs to show its
// preview faces.
const files = await getLocalFiles("normal");
const fileByID = new Map(files.map((f) => [f.id, f]));
const suggestions = suggestedClusters.map((cluster) => {
const previewFaces = cluster.faces
.slice(0, 3)
.map((faceID) => {
const fileID = fileIDFromFaceID(faceID);
if (!fileID) {
assertionFailed();
return undefined;
}
const file = fileByID.get(fileID);
if (!file) {
// TODO-Cluster: This might be a hidden/trash file, so this
// assert is not appropriate, we instead need a "until 3".
// assertionFailed();
return undefined;
}
return { enteFile: file, faceID };
})
.filter((f) => !!f);
const id = cluster.id;
return { id, cluster, previewFaces };
});
log.info(
`Generated ${result.length} suggestions for ${person.id} (${Date.now() - startTime} ms)`,
`Generated ${suggestions.length} suggestions for ${person.id} (${Date.now() - startTime} ms)`,
);
return result;
return suggestions;
};