mirror of
https://github.com/ente-io/ente.git
synced 2025-08-12 17:20:37 +00:00
[mob][photos] Debug experiment for detecting mixed clusters
This commit is contained in:
parent
d08edacb66
commit
b74a572f1a
@ -315,6 +315,99 @@ class ClusterFeedbackService {
|
|||||||
return;
|
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
|
// TODO: iterate over this method to find sweet spot
|
||||||
Future<ClusteringResult> breakUpCluster(
|
Future<ClusteringResult> breakUpCluster(
|
||||||
int clusterID, {
|
int clusterID, {
|
||||||
|
@ -8,6 +8,7 @@ import "package:photos/events/people_changed_event.dart";
|
|||||||
import "package:photos/face/db.dart";
|
import "package:photos/face/db.dart";
|
||||||
import "package:photos/face/model/person.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/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/services/machine_learning/face_ml/person/person_service.dart";
|
||||||
import 'package:photos/theme/ente_theme.dart';
|
import 'package:photos/theme/ente_theme.dart';
|
||||||
import 'package:photos/ui/components/captioned_text_widget.dart';
|
import 'package:photos/ui/components/captioned_text_widget.dart';
|
||||||
@ -227,6 +228,32 @@ class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
sectionOptionSpacing,
|
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(
|
MenuItemWidget(
|
||||||
captionedTextWidget: const CaptionedTextWidget(
|
captionedTextWidget: const CaptionedTextWidget(
|
||||||
title: "Reset feedback & clusters",
|
title: "Reset feedback & clusters",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user