[mob][photos] Debug experiment for detecting mixed clusters

This commit is contained in:
laurenspriem 2024-05-09 19:52:05 +05:30
parent d08edacb66
commit b74a572f1a
2 changed files with 120 additions and 0 deletions

View file

@ -315,6 +315,99 @@ class ClusterFeedbackService {
return;
}
Future<List<(int, int)>> checkForMixedClusters() async {
final faceMlDb = FaceMLDataDB.instance;
final allClusterToFaceCount = await faceMlDb.clusterIdToFaceCount();
final clustersToInspect = <int>[];
for (final clusterID in allClusterToFaceCount.keys) {
if (allClusterToFaceCount[clusterID]! > 20 &&
allClusterToFaceCount[clusterID]! < 500) {
clustersToInspect.add(clusterID);
}
}
final fileIDToCreationTime =
await FilesDB.instance.getFileIDToCreationTime();
final susClusters = <(int, int)>[];
final inspectionStart = DateTime.now();
for (final clusterID in clustersToInspect) {
final int originalClusterSize = allClusterToFaceCount[clusterID]!;
final faceIDs = await faceMlDb.getFaceIDsForCluster(clusterID);
final originalFaceIDsSet = faceIDs.toSet();
final embeddings = await faceMlDb.getFaceEmbeddingMapForFaces(faceIDs);
final clusterResult =
await FaceClusteringService.instance.predictWithinClusterComputer(
embeddings,
fileIDToCreationTime: fileIDToCreationTime,
distanceThreshold: 0.14,
);
if (clusterResult == null || clusterResult.isEmpty) {
_logger.warning(
'[CheckMixedClusters] Clustering did not seem to work for cluster $clusterID of size ${allClusterToFaceCount[clusterID]}',
);
continue;
}
final newClusterIdToCount =
clusterResult.newClusterIdToFaceIds!.map((key, value) {
return MapEntry(key, value.length);
});
final amountOfNewClusters = newClusterIdToCount.length;
_logger.info(
'[CheckMixedClusters] Broke up cluster $clusterID into $amountOfNewClusters clusters \n ${newClusterIdToCount.toString()}',
);
// Now find the sizes of the biggest and second biggest cluster
final int biggestClusterID = newClusterIdToCount.keys.reduce((a, b) {
return newClusterIdToCount[a]! > newClusterIdToCount[b]! ? a : b;
});
final int biggestSize = newClusterIdToCount[biggestClusterID]!;
final biggestRatio = biggestSize / originalClusterSize;
if (newClusterIdToCount.length > 1) {
final List<int> clusterIDs = newClusterIdToCount.keys.toList();
clusterIDs.remove(biggestClusterID);
final int secondBiggestClusterID = clusterIDs.reduce((a, b) {
return newClusterIdToCount[a]! > newClusterIdToCount[b]! ? a : b;
});
final int secondBiggestSize =
newClusterIdToCount[secondBiggestClusterID]!;
final secondBiggestRatio = secondBiggestSize / originalClusterSize;
if (biggestRatio < 0.5 || secondBiggestRatio > 0.2) {
final faceIdsOfCluster =
await faceMlDb.getFaceIDsForCluster(clusterID);
final uniqueFileIDs =
faceIdsOfCluster.map(getFileIdFromFaceId).toSet();
susClusters.add((clusterID, uniqueFileIDs.length));
_logger.info(
'[CheckMixedClusters] Detected that cluster $clusterID with size ${uniqueFileIDs.length} might be mixed',
);
}
} else {
_logger.info(
'[CheckMixedClusters] For cluster $clusterID we only found one cluster after reclustering',
);
}
}
_logger.info(
'[CheckMixedClusters] Inspection took ${DateTime.now().difference(inspectionStart).inSeconds} seconds',
);
if (susClusters.isNotEmpty) {
_logger.info(
'[CheckMixedClusters] Found ${susClusters.length} clusters that might be mixed: $susClusters',
);
} else {
_logger.info('[CheckMixedClusters] No mixed clusters found');
}
return susClusters;
}
// TODO: iterate over this method to find sweet spot
Future<ClusteringResult> breakUpCluster(
int clusterID, {

View file

@ -8,6 +8,7 @@ import "package:photos/events/people_changed_event.dart";
import "package:photos/face/db.dart";
import "package:photos/face/model/person.dart";
import 'package:photos/services/machine_learning/face_ml/face_ml_service.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/ente_theme.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
@ -227,6 +228,32 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Check for mixed clusters",
),
pressedColor: getEnteColorScheme(context).fillFaint,
trailingIcon: Icons.chevron_right_outlined,
trailingIconIsMuted: true,
onTap: () async {
try {
final susClusters =
await ClusterFeedbackService.instance.checkForMixedClusters();
for (final clusterinfo in susClusters) {
Future.delayed(const Duration(seconds: 4), () {
showToast(
context,
'Cluster with ${clusterinfo.$2} photos is sus',
);
});
}
} catch (e, s) {
_logger.warning('Checking for mixed clusters failed', e, s);
await showGenericErrorDialog(context: context, error: e);
}
},
),
sectionOptionSpacing,
MenuItemWidget(
captionedTextWidget: const CaptionedTextWidget(
title: "Reset feedback & clusters",