[mob] Persist cluster information during person assignment

This commit is contained in:
Neeraj Gupta 2024-04-04 22:04:19 +05:30
parent f5a9679c0e
commit d8bf0ad2d5
6 changed files with 68 additions and 40 deletions

View file

@ -80,31 +80,28 @@ class FaceMLDataDB {
}
}
Future<void> updatePersonIDForFaceIDIFNotSet(
Map<String, int> faceIDToPersonID,
Future<void> updateClusterIdToFaceId(
Map<String, int> faceIDToClusterID,
) async {
final db = await instance.database;
const batchSize = 500;
final numBatches = (faceIDToPersonID.length / batchSize).ceil();
final numBatches = (faceIDToClusterID.length / batchSize).ceil();
for (int i = 0; i < numBatches; i++) {
_logger.info('updatePersonIDForFaceIDIFNotSet Batch $i of $numBatches');
final start = i * batchSize;
final end = min((i + 1) * batchSize, faceIDToPersonID.length);
final batch = faceIDToPersonID.entries.toList().sublist(start, end);
final end = min((i + 1) * batchSize, faceIDToClusterID.length);
final batch = faceIDToClusterID.entries.toList().sublist(start, end);
final batchUpdate = db.batch();
for (final entry in batch) {
final faceID = entry.key;
final personID = entry.value;
final clusterID = entry.value;
batchUpdate.insert(
faceClustersTable,
{fcClusterID: personID, fcFaceId: faceID},
{fcClusterID: clusterID, fcFaceId: faceID},
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
await batchUpdate.commit(noResult: true);
}
}
@ -496,14 +493,7 @@ class FaceMLDataDB {
mapPersonToRow(p),
conflictAlgorithm: ConflictAlgorithm.replace,
);
await db.insert(
clusterPersonTable,
{
personIdColumn: p.remoteID,
cluserIDColumn: cluserID,
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
await assignClusterToPerson(personID: p.remoteID, clusterID: cluserID);
}
Future<void> updatePerson(PersonEntity p) async {

View file

@ -20,6 +20,7 @@ import 'package:photos/core/errors.dart';
import 'package:photos/core/network/network.dart';
import 'package:photos/db/upload_locks_db.dart';
import 'package:photos/ente_theme_data.dart';
import "package:photos/face/db.dart";
import "package:photos/l10n/l10n.dart";
import 'package:photos/services/app_lifecycle_service.dart';
import 'package:photos/services/billing_service.dart';
@ -32,6 +33,7 @@ import 'package:photos/services/local_file_update_service.dart';
import 'package:photos/services/local_sync_service.dart';
import "package:photos/services/location_service.dart";
import 'package:photos/services/machine_learning/face_ml/face_ml_service.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import 'package:photos/services/machine_learning/file_ml/remote_fileml_service.dart';
import "package:photos/services/machine_learning/machine_learning_controller.dart";
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
@ -238,6 +240,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
unawaited(FaceMlService.instance.init());
FaceMlService.instance.listenIndexOnDiffSync();
}
PersonService.init(EntityService.instance, FaceMLDataDB.instance, preferences);
_logger.info("Initialization done");
}

View file

@ -416,8 +416,7 @@ class FaceMlService {
return;
}
await FaceMLDataDB.instance
.updatePersonIDForFaceIDIFNotSet(faceIdToCluster);
await FaceMLDataDB.instance.updateClusterIdToFaceId(faceIdToCluster);
offset += offsetIncrement;
}
} else {
@ -456,8 +455,7 @@ class FaceMlService {
_logger.info(
'Updating ${faceIdToCluster.length} FaceIDs with clusterIDs in the DB',
);
await FaceMLDataDB.instance
.updatePersonIDForFaceIDIFNotSet(faceIdToCluster);
await FaceMLDataDB.instance.updateClusterIdToFaceId(faceIdToCluster);
_logger.info('Done updating FaceIDs with clusterIDs in the DB, in '
'${DateTime.now().difference(clusterDoneTime).inSeconds} seconds');
}

View file

@ -1,10 +1,52 @@
import "dart:convert";
import "package:photos/face/db.dart";
import "package:photos/face/model/person.dart";
import "package:photos/models/api/entity/type.dart";
import "package:photos/services/entity_service.dart";
import "package:shared_preferences/shared_preferences.dart";
class PersonService {
final EntityService entityService;
final FaceMLDataDB faceMLDataDB;
final SharedPreferences _prefs;
PersonService(this.entityService, this.faceMLDataDB, this._prefs);
final SharedPreferences prefs;
PersonService(this.entityService, this.faceMLDataDB, this.prefs);
// instance
static PersonService? _instance;
static PersonService get instance {
if (_instance == null) {
throw Exception("PersonService not initialized");
}
return _instance!;
}
static init(
EntityService entityService,
FaceMLDataDB faceMLDataDB,
SharedPreferences prefs,
) {
_instance = PersonService(entityService, faceMLDataDB, prefs);
}
Future<PersonEntity> addPerson(String name, int clusterID) async {
final faceIds = await faceMLDataDB.getFaceIDsForCluster(clusterID);
final data = PersonData(
name: name,
assigned: <ClusterInfo>[
ClusterInfo(
id: clusterID,
faces: faceIds.toSet(),
),
],
);
final result = await entityService.addOrUpdate(
EntityType.person,
json.encode(data.toJson()),
);
await faceMLDataDB.assignClusterToPerson(
personID: result.id,
clusterID: clusterID,
);
return PersonEntity(result.id, data);
}
}

View file

@ -11,6 +11,7 @@ import "package:photos/face/db.dart";
import "package:photos/face/model/person.dart";
import "package:photos/generated/l10n.dart";
import 'package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart';
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/common/loading_widget.dart';
@ -23,7 +24,6 @@ import "package:photos/ui/viewer/people/new_person_item_widget.dart";
import "package:photos/ui/viewer/people/person_row_item.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/toast_util.dart";
import "package:uuid/uuid.dart";
enum PersonActionType {
assignPerson,
@ -269,12 +269,8 @@ class _PersonActionSheetState extends State<PersonActionSheet> {
return;
}
try {
final String id = const Uuid().v4().toString();
final PersonEntity p = PersonEntity(
id,
PersonData(name: text, assigned: <ClusterInfo>[]),
);
await FaceMLDataDB.instance.insert(p, clusterID);
final PersonEntity p =
await PersonService.instance.addPerson(text, clusterID);
final bool extraPhotosFound = await ClusterFeedbackService.instance
.checkAndDoAutomaticMerges(p);
if (extraPhotosFound) {
@ -282,7 +278,6 @@ class _PersonActionSheetState extends State<PersonActionSheet> {
}
Bus.instance.fire(PeopleChangedEvent());
Navigator.pop(context, p);
log("inserted person");
} catch (e, s) {
Logger("_PersonActionSheetState")
.severe("Failed to rename album", e, s);

View file

@ -17,12 +17,16 @@ class PersonFaceWidget extends StatelessWidget {
final String? personId;
final int? clusterID;
// PersonFaceWidget constructor checks that both personId and clusterID are not null
// and that the file is not null
const PersonFaceWidget(
this.file, {
this.personId,
this.clusterID,
Key? key,
}) : super(key: key);
}) : assert(personId != null || clusterID != null,
"PersonFaceWidget requires either personId or clusterID to be non-null"),
super(key: key);
@override
Widget build(BuildContext context) {
@ -53,7 +57,7 @@ class PersonFaceWidget extends StatelessWidget {
);
} else {
return FutureBuilder<Face?>(
future: getFace(),
future: _getFace(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final Face face = snapshot.data!;
@ -76,7 +80,7 @@ class PersonFaceWidget extends StatelessWidget {
}
}
Future<Face?> getFace() async {
Future<Face?> _getFace() async {
return await FaceMLDataDB.instance.getCoverFaceForPerson(
recentFileID: file.uploadedFileID!,
personID: personId,
@ -86,11 +90,7 @@ class PersonFaceWidget extends StatelessWidget {
Future<Uint8List?> getFaceCrop() async {
try {
final Face? face = await FaceMLDataDB.instance.getCoverFaceForPerson(
recentFileID: file.uploadedFileID!,
personID: personId,
clusterID: clusterID,
);
final Face? face = await _getFace();
if (face == null) {
debugPrint(
"No cover face for person: $personId and cluster $clusterID and recentFile ${file.uploadedFileID}",