Clusterface (#4626)

## Description

Fixed bug where we showed "Face not clustered yet, please come back
later" toast message even for faces which had a score too low to ever be
clustered automatically.
This commit is contained in:
Laurens Priem 2025-01-08 07:02:45 +01:00 committed by GitHub
commit 50c65125a7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 27 additions and 20 deletions

View File

@ -604,8 +604,6 @@ class MLDataDB {
} }
Future<List<FaceDbInfoForClustering>> getFaceInfoForClustering({ Future<List<FaceDbInfoForClustering>> getFaceInfoForClustering({
double minScore = kMinimumQualityFaceScore,
int minClarity = kLaplacianHardThreshold,
int maxFaces = 20000, int maxFaces = 20000,
int offset = 0, int offset = 0,
int batchSize = 10000, int batchSize = 10000,
@ -622,7 +620,7 @@ class MLDataDB {
// Query a batch of rows // Query a batch of rows
final List<Map<String, dynamic>> maps = await db.getAll( final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT $faceIDColumn, $embeddingColumn, $faceScore, $faceBlur, $isSideways FROM $facesTable' 'SELECT $faceIDColumn, $embeddingColumn, $faceScore, $faceBlur, $isSideways FROM $facesTable'
' WHERE $faceScore > $minScore AND $faceBlur > $minClarity' ' WHERE $faceScore > $kMinimumQualityFaceScore AND $faceBlur > $kLaplacianHardThreshold'
' ORDER BY $faceIDColumn' ' ORDER BY $faceIDColumn'
' DESC LIMIT $batchSize OFFSET $offset', ' DESC LIMIT $batchSize OFFSET $offset',
); );
@ -698,12 +696,10 @@ class MLDataDB {
return result; return result;
} }
Future<int> getTotalFaceCount({ Future<int> getTotalFaceCount() async {
double minFaceScore = kMinimumQualityFaceScore,
}) async {
final db = await instance.asyncDB; final db = await instance.asyncDB;
final List<Map<String, dynamic>> maps = await db.getAll( final List<Map<String, dynamic>> maps = await db.getAll(
'SELECT COUNT(*) as count FROM $facesTable WHERE $faceScore > $minFaceScore AND $faceBlur > $kLaplacianHardThreshold', 'SELECT COUNT(*) as count FROM $facesTable WHERE $faceScore > $kMinimumQualityFaceScore AND $faceBlur > $kLaplacianHardThreshold',
); );
return maps.first['count'] as int; return maps.first['count'] as int;
} }

View File

@ -17,7 +17,6 @@ import "package:photos/services/filedata/filedata_service.dart";
import "package:photos/services/filedata/model/file_data.dart"; import "package:photos/services/filedata/model/file_data.dart";
import 'package:photos/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart'; import 'package:photos/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart';
import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart"; import "package:photos/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart";
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.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/services/machine_learning/ml_indexing_isolate.dart"; import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
import 'package:photos/services/machine_learning/ml_result.dart'; import 'package:photos/services/machine_learning/ml_result.dart';
@ -238,10 +237,7 @@ class MLService {
} }
} }
Future<void> clusterAllImages({ Future<void> clusterAllImages({bool clusterInBuckets = true}) async {
double minFaceScore = kMinimumQualityFaceScore,
bool clusterInBuckets = true,
}) async {
if (_cannotRunMLFunction()) return; if (_cannotRunMLFunction()) return;
_logger.info("`clusterAllImages()` called"); _logger.info("`clusterAllImages()` called");
@ -269,13 +265,12 @@ class MLService {
// Get a sense of the total number of faces in the database // Get a sense of the total number of faces in the database
final int totalFaces = final int totalFaces =
await MLDataDB.instance.getTotalFaceCount(minFaceScore: minFaceScore); await MLDataDB.instance.getTotalFaceCount();
final fileIDToCreationTime = final fileIDToCreationTime =
await FilesDB.instance.getFileIDToCreationTime(); await FilesDB.instance.getFileIDToCreationTime();
final startEmbeddingFetch = DateTime.now(); final startEmbeddingFetch = DateTime.now();
// read all embeddings // read all embeddings
final result = await MLDataDB.instance.getFaceInfoForClustering( final result = await MLDataDB.instance.getFaceInfoForClustering(
minScore: minFaceScore,
maxFaces: totalFaces, maxFaces: totalFaces,
); );
final Set<int> missingFileIDs = {}; final Set<int> missingFileIDs = {};

View File

@ -5,12 +5,13 @@ import "package:flutter/cupertino.dart";
import "package:flutter/foundation.dart" show kDebugMode; import "package:flutter/foundation.dart" show kDebugMode;
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:photos/db/ml/db.dart"; import "package:photos/db/ml/db.dart";
import "package:photos/extensions/stop_watch.dart";
import "package:photos/generated/l10n.dart"; import "package:photos/generated/l10n.dart";
import "package:photos/models/base/id.dart";
import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file.dart';
import "package:photos/models/ml/face/face.dart"; import "package:photos/models/ml/face/face.dart";
import "package:photos/models/ml/face/person.dart"; import "package:photos/models/ml/face/person.dart";
import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart"; import "package:photos/services/machine_learning/face_ml/face_detection/detection.dart";
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart"; import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart";
import "package:photos/services/search_service.dart"; import "package:photos/services/search_service.dart";
import "package:photos/theme/ente_theme.dart"; import "package:photos/theme/ente_theme.dart";
@ -68,16 +69,13 @@ class _FaceWidgetState extends State<FaceWidget> {
if (widget.editMode) return; if (widget.editMode) return;
log( log(
"FaceWidget is tapped, with person ${widget.person} and clusterID ${widget.clusterID}", "FaceWidget is tapped, with person ${widget.person?.data.name} and clusterID ${widget.clusterID}",
name: "FaceWidget", name: "FaceWidget",
); );
if (widget.person == null && widget.clusterID == null) { if (widget.person == null && widget.clusterID == null) {
// Get faceID and double check that it doesn't belong to an existing clusterID. If it does, push that cluster page // Double check that it doesn't belong to an existing clusterID.
final w = (kDebugMode ? EnteWatch('FaceWidget') : null)
?..start();
final existingClusterID = await MLDataDB.instance final existingClusterID = await MLDataDB.instance
.getClusterIDForFaceID(widget.face.faceID); .getClusterIDForFaceID(widget.face.faceID);
w?.log('getting existing clusterID for faceID');
if (existingClusterID != null) { if (existingClusterID != null) {
final fileIdsToClusterIds = final fileIdsToClusterIds =
await MLDataDB.instance.getFileIdToClusterIds(); await MLDataDB.instance.getFileIdToClusterIds();
@ -99,6 +97,24 @@ class _FaceWidgetState extends State<FaceWidget> {
), ),
), ),
); );
return;
}
if (widget.face.score <= kMinimumQualityFaceScore) {
// The face score is too low for automatic clustering,
// assigning a manual new clusterID so that the user can cluster it manually
final String clusterID = newClusterID();
await MLDataDB.instance.updateFaceIdToClusterId(
{widget.face.faceID: clusterID},
);
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ClusterPage(
[widget.file],
clusterID: clusterID,
),
),
);
return;
} }
showShortToast( showShortToast(