From 3f4250dab3ca912bd6a76fc2b1aafa5d06bc7839 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 25 Sep 2024 17:31:03 +0530 Subject: [PATCH] Reconcile 1 --- .../new/photos/services/ml/cluster.ts | 69 +++++++++++++++---- web/packages/new/photos/services/ml/people.ts | 7 +- .../new/photos/services/user-entity/index.ts | 2 +- 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/web/packages/new/photos/services/ml/cluster.ts b/web/packages/new/photos/services/ml/cluster.ts index 675830dd47..2f2c032f99 100644 --- a/web/packages/new/photos/services/ml/cluster.ts +++ b/web/packages/new/photos/services/ml/cluster.ts @@ -3,7 +3,7 @@ import log from "@/base/log"; import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; import type { EnteFile } from "../../types/file"; -import { savedCGroups } from "../user-entity"; +import { savedCGroupUserEntities, type CGroupUserEntity } from "../user-entity"; import { savedFaceClusters } from "./db"; import { faceDirection, @@ -12,6 +12,7 @@ import { type FaceIndex, } from "./face"; import { dotProduct } from "./math"; +import type { CGroup } from "./people"; /** * A face cluster is an set of faces, and a nanoid to uniquely identify it. @@ -111,12 +112,16 @@ export const clusterFaces = async ( let clusters: FaceCluster[] = []; // Get the locally available remote cluster groups. - const cgroups = await savedCGroups(); - // Sort them so that the latest ones are first. - const sortedCGroups = cgroups.sort((a, b) => b.updatedAt - a.updatedAt); + const cgroupUserEntities = await savedCGroupUserEntities(); + // Sort them so that the latest ones are first. This is not expected to be + // needed, it is just a safety check in case a client puts a face into two + // clusters, in which case we want to use the more recent cgroup. + const sortedCGroupUserEntities = cgroupUserEntities.sort( + (a, b) => b.updatedAt - a.updatedAt, + ); // Extract the remote clusters. clusters = clusters.concat( - sortedCGroups.map((cg) => cg.data.assigned).flat(), + sortedCGroupUserEntities.map((cg) => cg.data.assigned).flat(), ); // Add on the clusters we have available locally. @@ -189,19 +194,59 @@ export const clusterFaces = async ( `Generated ${sortedClusters.length} clusters from ${faces.length} faces (${clusteredFaceCount} clustered ${faces.length - clusteredFaceCount} unclustered) (${timeTakenMs} ms)`, ); - // // TODO-Cluster - // // This isn't really part of the clustering, but help the main thread out by - // // pre-computing temporary in-memory people, one per cluster. + // Clustering is complete at this point. Now we want to + // 1. Update remote cgroups that have changed. + // 1. Update our local state. + + // Index clusters by their ID for fast lookup. + const clusterByID = new Map(clusters.map((c) => [c.id, c])); + + // Map from clusters to their (remote) cgroup. + const clusterIDToCGroupID = new Map(); + for (const cgroup of cgroupUserEntities) { + for (const cluster of cgroup.data.assigned) + clusterIDToCGroupID.set(cluster.id, cgroup.id); + } + + // Find the cgroups that have changed since we started. + const changedCGroupUserEntities: CGroupUserEntity[] = []; + for (const cgroupEntity of cgroupUserEntities) { + for (const oldCluster of cgroupEntity.data.assigned) { + // The clustering algorithm does not remove any existing faces, it + // can only add new ones to the cluster. So we can use the count as + // an indication if something changed. + if ( + oldCluster.faces.length != + ensure(clusterByID.get(oldCluster.id)).faces.length + ) { + changedCGroupUserEntities.push({ + ...cgroupEntity, + data: { + ...cgroupEntity.data, + assigned: cgroupEntity.data.assigned.map(({ id }) => + ensure(clusterByID.get(id)), + ), + }, + }); + break; + } + } + } + + console.log("Should PUT remote clusters", changedCGroupUserEntities); + + // Find clusters that are not part of a remote cgroup. + const localClusters = clusters.filter( + ({ id }) => !clusterIDToCGroupID.has(id), + ); + + console.log("Should save local clusters", localClusters); // // For fast reverse lookup - map from face ids to the face. // const faceForFaceID = new Map(faces.map((f) => [f.faceID, f])); // const people = toPeople(sortedClusters, localFileByID, faceForFaceID); - // // reconcileClusters - // // Save local - // // updated map -> remote - Put - return { clusters: sortedClusters, people: [] }; }; diff --git a/web/packages/new/photos/services/ml/people.ts b/web/packages/new/photos/services/ml/people.ts index 934575017f..08fb77ddb8 100644 --- a/web/packages/new/photos/services/ml/people.ts +++ b/web/packages/new/photos/services/ml/people.ts @@ -1,12 +1,12 @@ import { masterKeyFromSession } from "@/base/session-store"; +import { ensure } from "@/utils/ensure"; import { wipClusterEnable } from "."; import type { EnteFile } from "../../types/file"; import { getLocalFiles } from "../files"; -import { pullUserEntities, savedCGroups } from "../user-entity"; +import { pullUserEntities, savedCGroupUserEntities } from "../user-entity"; import type { FaceCluster } from "./cluster"; import { getFaceIndexes } from "./db"; import { fileIDFromFaceID, type Face } from "./face"; -import { ensure } from "@/utils/ensure"; /** * A cgroup ("cluster group") is a group of clusters (possibly containing just a @@ -181,7 +181,7 @@ export const namedPeopleFromCGroups = async (): Promise => { } // Convert cgroups to people. - const cgroups = await savedCGroups(); + const cgroups = await savedCGroupUserEntities(); return cgroups .map(({ id, data: cgroup }) => { // Hidden cgroups are clusters specifically marked so as to not be shown @@ -236,7 +236,6 @@ export const namedPeopleFromCGroups = async (): Promise => { .sort((a, b) => b.fileIDs.length - a.fileIDs.length); }; - /** * Construct a {@link Person} object for each cluster. */ diff --git a/web/packages/new/photos/services/user-entity/index.ts b/web/packages/new/photos/services/user-entity/index.ts index 35e3709288..229c9101d1 100644 --- a/web/packages/new/photos/services/user-entity/index.ts +++ b/web/packages/new/photos/services/user-entity/index.ts @@ -94,7 +94,7 @@ export type CGroupUserEntity = Omit & { /** * Return the list of locally available cgroup user entities. */ -export const savedCGroups = (): Promise => +export const savedCGroupUserEntities = (): Promise => savedEntities("cgroup").then((es) => es.map((e) => ({ ...e, data: RemoteCGroup.parse(e.data) })), );