diff --git a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart index 581f447aca..f8c180b4f7 100644 --- a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart @@ -111,7 +111,7 @@ class FaceClusteringService { try { switch (function) { case ClusterOperation.linearIncrementalClustering: - final result = FaceClusteringService._runLinearClustering(args); + final result = FaceClusteringService.runLinearClustering(args); sendPort.send(result); break; case ClusterOperation.dbscanClustering: @@ -185,7 +185,7 @@ class FaceClusteringService { _inactivityTimer?.cancel(); } - /// Runs the clustering algorithm [_runLinearClustering] on the given [input], in an isolate. + /// Runs the clustering algorithm [runLinearClustering] on the given [input], in an isolate. /// /// Returns the clustering result, which is a list of clusters, where each cluster is a list of indices of the dataset. /// @@ -245,6 +245,65 @@ class FaceClusteringService { } } + /// Runs the clustering algorithm [runLinearClustering] on the given [input], in computer, without any dynamic thresholding + Future?> predictLinearComputer( + Map input, { + Map? fileIDToCreationTime, + double distanceThreshold = kRecommendedDistanceThreshold, + }) async { + if (input.isEmpty) { + _logger.warning( + "Linear Clustering dataset of embeddings is empty, returning empty list.", + ); + return {}; + } + + // Clustering inside the isolate + _logger.info( + "Start Linear clustering on ${input.length} embeddings inside computer isolate", + ); + + try { + final clusteringInput = input + .map((key, value) { + return MapEntry( + key, + FaceInfoForClustering( + faceID: key, + embeddingBytes: value, + faceScore: kMinHighQualityFaceScore + 0.01, + blurValue: kLapacianDefault, + ), + ); + }) + .values + .toSet(); + final startTime = DateTime.now(); + final faceIdToCluster = await _computer.compute( + runLinearClustering, + param: { + "input": clusteringInput, + "fileIDToCreationTime": fileIDToCreationTime, + "distanceThreshold": distanceThreshold, + "conservativeDistanceThreshold": distanceThreshold, + "useDynamicThreshold": false, + }, + taskName: "createImageEmbedding", + ) as Map; + final endTime = DateTime.now(); + _logger.info( + "Linear Clustering took: ${endTime.difference(startTime).inMilliseconds}ms", + ); + return faceIdToCluster; + } catch (e, s) { + _logger.severe(e, s); + rethrow; + } + } + + /// Runs the clustering algorithm [runCompleteClustering] on the given [input], in computer. + /// + /// WARNING: Only use on small datasets, as it is not optimized for large datasets. Future> predictCompleteComputer( Map input, { Map? fileIDToCreationTime, @@ -286,6 +345,41 @@ class FaceClusteringService { } } + Future?> predictWithinClusterComputer( + Map input, { + Map? fileIDToCreationTime, + double distanceThreshold = kRecommendedDistanceThreshold, + }) async { + _logger.info( + '`predictWithinClusterComputer` called with ${input.length} faces and distance threshold $distanceThreshold', + ); + try { + if (input.length < 100) { + final mergeThreshold = distanceThreshold + 0.06; + _logger.info( + 'Running complete clustering on ${input.length} faces with distance threshold $mergeThreshold', + ); + return predictCompleteComputer( + input, + fileIDToCreationTime: fileIDToCreationTime, + mergeThreshold: mergeThreshold, + ); + } else { + _logger.info( + 'Running linear clustering on ${input.length} faces with distance threshold $distanceThreshold', + ); + return predictLinearComputer( + input, + fileIDToCreationTime: fileIDToCreationTime, + distanceThreshold: distanceThreshold, + ); + } + } catch (e, s) { + _logger.severe(e, s); + rethrow; + } + } + Future>> predictDbscan( Map input, { Map? fileIDToCreationTime, @@ -335,7 +429,7 @@ class FaceClusteringService { return clusterFaceIDs; } - static Map _runLinearClustering(Map args) { + static Map runLinearClustering(Map args) { // final input = args['input'] as Map; final input = args['input'] as Set; final fileIDToCreationTime = args['fileIDToCreationTime'] as Map?; diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 68cae30af8..48feb1ea34 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -234,33 +234,11 @@ class ClusterFeedbackService { maxClusterID++; } } else { - // final clusteringInput = embeddings - // .map((key, value) { - // return MapEntry( - // key, - // FaceInfoForClustering( - // faceID: key, - // embeddingBytes: value, - // faceScore: kMinHighQualityFaceScore + 0.01, - // blurValue: kLapacianDefault, - // ), - // ); - // }) - // .values - // .toSet(); - // final faceIdToCluster = - // await FaceClusteringService.instance.predictLinear( - // clusteringInput, - // fileIDToCreationTime: fileIDToCreationTime, - // distanceThreshold: 0.23, - // useDynamicThreshold: false, - // ); final faceIdToCluster = - await FaceClusteringService.instance.predictCompleteComputer( + await FaceClusteringService.instance.predictWithinClusterComputer( embeddings, fileIDToCreationTime: fileIDToCreationTime, - distanceThreshold: 0.30, - mergeThreshold: 0.30, + distanceThreshold: 0.22, ); if (faceIdToCluster == null || faceIdToCluster.isEmpty) {