[mobile] Add method to reconsile mappings

This commit is contained in:
Neeraj Gupta 2024-05-02 07:04:31 +05:30
parent 689833d8aa
commit ca3172c33e
3 changed files with 123 additions and 1 deletions

View file

@ -386,7 +386,26 @@ class FaceMLDataDB {
return maps.map((e) => e[fcFaceId] as String).toSet();
}
Future<Iterable<String>> getFaceIDsForPerson(String personID) async {
// Get Map of personID to Map of clusterID to faceIDs
Future<Map<String, Map<int, Set<String>>>>
getPersonToClusterIdToFaceIds() async {
final db = await instance.asyncDB;
final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT $personIdColumn, $faceClustersTable.$fcClusterID, $fcFaceId FROM $clusterPersonTable '
'LEFT JOIN $faceClustersTable ON $clusterPersonTable.$clusterIDColumn = $faceClustersTable.$fcClusterID',
);
final Map<String, Map<int, Set<String>>> result = {};
for (final map in maps) {
final personID = map[personIdColumn] as String;
final clusterID = map[fcClusterID] as int;
final faceID = map[fcFaceId] as String;
result.putIfAbsent(personID, () => {}).putIfAbsent(clusterID, () => {})
..add(faceID);
}
return result;
}
Future<Set<String>> getFaceIDsForPerson(String personID) async {
final db = await instance.asyncDB;
final faceIdsResult = await db.getAll(
'SELECT $fcFaceId FROM $faceClustersTable LEFT JOIN $clusterPersonTable '

View file

@ -1,10 +1,12 @@
import "dart:async" show unawaited;
import "dart:convert";
import "dart:developer";
import "package:flutter/foundation.dart";
import "package:logging/logging.dart";
import "package:photos/core/event_bus.dart";
import "package:photos/events/people_changed_event.dart";
import "package:photos/extensions/stop_watch.dart";
import "package:photos/face/db.dart";
import "package:photos/face/model/person.dart";
import "package:photos/models/api/entity/type.dart";
@ -69,6 +71,89 @@ class PersonService {
return entities.map((e) => e.id).toSet();
}
Future<void> reconcileClusters() async {
final EnteWatch? w = kDebugMode ? EnteWatch("reconcileClusters") : null;
w?.start();
await storeRemoteFeedback();
w?.log("Stored remote feedback");
final dbPersonClusterInfo =
await faceMLDataDB.getPersonToClusterIdToFaceIds();
w?.log("Got DB person cluster info");
final persons = await getPersonsMap();
w?.log("Got persons");
for (var personID in dbPersonClusterInfo.keys) {
final person = persons[personID];
if (person == null) {
logger.warning("Person $personID not found");
continue;
}
final personData = person.data;
final Map<int, Set<String>> dbPersonCluster =
dbPersonClusterInfo[personID]!;
if (_shouldUpdateRemotePerson(personData, dbPersonCluster)) {
final personData = person.data;
personData.assigned = dbPersonCluster.entries
.map(
(e) => ClusterInfo(
id: e.key,
faces: e.value,
),
)
.toList();
entityService
.addOrUpdate(
EntityType.person,
json.encode(personData.toJson()),
id: personID,
)
.ignore();
personData.logStats();
}
}
w?.log("Reconciled clusters for ${persons.length} persons");
}
bool _shouldUpdateRemotePerson(
PersonData personData, Map<int, Set<String>> dbPersonCluster) {
bool result = false;
if ((personData.assigned?.length ?? 0) != dbPersonCluster.length) {
log(
"Person ${personData.name} has ${personData.assigned?.length} clusters, but ${dbPersonCluster.length} clusters found in DB",
name: "PersonService",
);
result = true;
} else {
for (ClusterInfo info in personData.assigned!) {
final dbCluster = dbPersonCluster[info.id];
if (dbCluster == null) {
log(
"Cluster ${info.id} not found in DB for person ${personData.name}",
name: "PersonService",
);
result = true;
continue;
}
if (info.faces.length != dbCluster.length) {
log(
"Cluster ${info.id} has ${info.faces.length} faces, but ${dbCluster.length} faces found in DB",
name: "PersonService",
);
result = true;
}
for (var faceId in info.faces) {
if (!dbCluster.contains(faceId)) {
log(
"Face $faceId not found in cluster ${info.id} for person ${personData.name}",
name: "PersonService",
);
result = true;
}
}
}
}
return result;
}
Future<PersonEntity> addPerson(String name, int clusterID) async {
final faceIds = await faceMLDataDB.getFaceIDsForCluster(clusterID);
final data = PersonData(

View file

@ -288,6 +288,24 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
);
},
),
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Sync person mappings ",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
await PersonService.instance.reconcileClusters();
Bus.instance.fire(PeopleChangedEvent());
showShortToast(context, "Done");
} catch (e, s) {
_logger.warning('sync person mappings failed ', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
// sectionOptionSpacing,
// MenuItemWidget(
// captionedTextWidget: const CaptionedTextWidget(