Ml fixesv148 (#3028)
## Description - Don't process ML on mobile for large files - ML user developer options to clear local data
This commit is contained in:
@@ -668,6 +668,22 @@ class MLDataDB {
|
||||
return maps.first['count'] as int;
|
||||
}
|
||||
|
||||
Future<Set<int>> getErroredFileIDs() async {
|
||||
final db = await instance.asyncDB;
|
||||
final List<Map<String, dynamic>> maps = await db.getAll(
|
||||
'SELECT DISTINCT $fileIDColumn FROM $facesTable WHERE $faceScore < 0',
|
||||
);
|
||||
return maps.map((e) => e[fileIDColumn] as int).toSet();
|
||||
}
|
||||
|
||||
Future<void> deleteFaceIndexForFiles(List<int> fileIDs) async {
|
||||
final db = await instance.asyncDB;
|
||||
final String sql = '''
|
||||
DELETE FROM $facesTable WHERE $fileIDColumn IN (${fileIDs.join(", ")})
|
||||
''';
|
||||
await db.execute(sql);
|
||||
}
|
||||
|
||||
Future<int> getClusteredOrFacelessFileCount() async {
|
||||
final db = await instance.asyncDB;
|
||||
final List<Map<String, dynamic>> clustered = await db.getAll(
|
||||
|
||||
@@ -7,3 +7,4 @@ const minimumClusterSize = 2;
|
||||
|
||||
const embeddingFetchLimit = 200;
|
||||
final fileDownloadMlLimit = Platform.isIOS ? 5 : 10;
|
||||
const maxFileDownloadSize = 100000000;
|
||||
|
||||
@@ -189,9 +189,8 @@ class MLIndexingIsolate {
|
||||
/// Analyzes the given image data by running the full pipeline for faces, using [_analyzeImageSync] in the isolate.
|
||||
Future<MLResult?> analyzeImage(
|
||||
FileMLInstruction instruction,
|
||||
String filePath,
|
||||
) async {
|
||||
final String filePath = await getImagePathForML(instruction.file);
|
||||
|
||||
final Stopwatch stopwatch = Stopwatch()..start();
|
||||
late MLResult result;
|
||||
|
||||
|
||||
@@ -391,8 +391,11 @@ class MLService {
|
||||
bool actuallyRanML = false;
|
||||
|
||||
try {
|
||||
final String filePath = await getImagePathForML(instruction.file);
|
||||
|
||||
final MLResult? result = await MLIndexingIsolate.instance.analyzeImage(
|
||||
instruction,
|
||||
filePath,
|
||||
);
|
||||
// Check if there's no result simply because MLController paused indexing
|
||||
if (result == null) {
|
||||
@@ -467,25 +470,20 @@ class MLService {
|
||||
_logger.info("Results for file ${result.fileId} stored locally");
|
||||
return actuallyRanML;
|
||||
} catch (e, s) {
|
||||
bool acceptedIssue = false;
|
||||
final String errorString = e.toString();
|
||||
if (errorString.contains('ThumbnailRetrievalException')) {
|
||||
_logger.severe(
|
||||
'ThumbnailRetrievalException while processing image with ID ${instruction.file.uploadedFileID}, storing empty results so indexing does not get stuck',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
acceptedIssue = true;
|
||||
}
|
||||
if (errorString.contains('InvalidImageFormatException')) {
|
||||
_logger.severe(
|
||||
'$errorString with ID ${instruction.file.uploadedFileID}, storing empty results so indexing does not get stuck',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
acceptedIssue = true;
|
||||
}
|
||||
final String format = instruction.file.displayName.split('.').last;
|
||||
final int? size = instruction.file.fileSize;
|
||||
final fileType = instruction.file.fileType;
|
||||
final bool acceptedIssue =
|
||||
errorString.contains('ThumbnailRetrievalException') ||
|
||||
errorString.contains('InvalidImageFormatException') ||
|
||||
errorString.contains('FileSizeTooLargeForMobileIndexing');
|
||||
if (acceptedIssue) {
|
||||
_logger.severe(
|
||||
'$errorString with ID ${instruction.file.uploadedFileID} (format $format, type $fileType, size $size), storing empty results so indexing does not get stuck',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await MLDataDB.instance.bulkInsertFaces(
|
||||
[Face.empty(instruction.file.uploadedFileID!, error: true)],
|
||||
);
|
||||
@@ -495,7 +493,7 @@ class MLService {
|
||||
return true;
|
||||
}
|
||||
_logger.severe(
|
||||
"Failed to analyze using FaceML for image with ID: ${instruction.file.uploadedFileID} and format ${instruction.file.displayName.split('.').last} (${instruction.file.fileType}). Not storing any results locally, which means it will be automatically retried later.",
|
||||
"Failed to index file with ID: ${instruction.file.uploadedFileID} (format $format, type $fileType, size $size). Not storing any results locally, which means it will be automatically retried later.",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
|
||||
import 'package:photos/ui/components/title_bar_title_widget.dart';
|
||||
import 'package:photos/ui/components/title_bar_widget.dart';
|
||||
import "package:photos/ui/components/toggle_switch_widget.dart";
|
||||
import "package:photos/ui/settings/machine_learning_settings_page.dart";
|
||||
import "package:photos/ui/settings/ml/machine_learning_settings_page.dart";
|
||||
import 'package:photos/ui/tools/debug/app_storage_viewer.dart';
|
||||
import 'package:photos/ui/viewer/gallery/photo_grid_size_picker_page.dart';
|
||||
import 'package:photos/utils/navigation_util.dart';
|
||||
|
||||
@@ -26,6 +26,7 @@ import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_widget.dart";
|
||||
import "package:photos/ui/components/toggle_switch_widget.dart";
|
||||
import "package:photos/ui/settings/ml/enable_ml_consent.dart";
|
||||
import "package:photos/ui/settings/ml/ml_user_dev_screen.dart";
|
||||
import "package:photos/utils/ml_util.dart";
|
||||
import "package:photos/utils/network_util.dart";
|
||||
import "package:photos/utils/wakelock_util.dart";
|
||||
@@ -42,6 +43,8 @@ class _MachineLearningSettingsPageState
|
||||
extends State<MachineLearningSettingsPage> {
|
||||
final EnteWakeLock _wakeLock = EnteWakeLock();
|
||||
Timer? _timer;
|
||||
int _titleTapCount = 0;
|
||||
Timer? _advancedOptionsTimer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -55,6 +58,9 @@ class _MachineLearningSettingsPageState
|
||||
}
|
||||
});
|
||||
}
|
||||
_advancedOptionsTimer = Timer.periodic(const Duration(seconds: 7), (timer) {
|
||||
_titleTapCount = 0;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -63,6 +69,7 @@ class _MachineLearningSettingsPageState
|
||||
_wakeLock.disable();
|
||||
MachineLearningController.instance.forceOverrideML(turnOn: false);
|
||||
_timer?.cancel();
|
||||
_advancedOptionsTimer?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -73,8 +80,26 @@ class _MachineLearningSettingsPageState
|
||||
primary: false,
|
||||
slivers: <Widget>[
|
||||
TitleBarWidget(
|
||||
flexibleSpaceTitle: TitleBarTitleWidget(
|
||||
title: S.of(context).machineLearning,
|
||||
flexibleSpaceTitle: GestureDetector(
|
||||
child: TitleBarTitleWidget(
|
||||
title: S.of(context).machineLearning,
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_titleTapCount++;
|
||||
if (_titleTapCount >= 7) {
|
||||
_titleTapCount = 0;
|
||||
// showShortToast(context, "Advanced options enabled");
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return const MLUserDeveloperOptions();
|
||||
},
|
||||
),
|
||||
).ignore();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
actionIcons: [
|
||||
IconButtonWidget(
|
||||
103
mobile/lib/ui/settings/ml/ml_user_dev_screen.dart
Normal file
103
mobile/lib/ui/settings/ml/ml_user_dev_screen.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:photos/core/event_bus.dart";
|
||||
import "package:photos/db/ml/clip_db.dart";
|
||||
import "package:photos/db/ml/db.dart";
|
||||
import "package:photos/events/people_changed_event.dart";
|
||||
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
import "package:photos/ui/components/models/button_type.dart";
|
||||
import "package:photos/ui/components/title_bar_title_widget.dart";
|
||||
import "package:photos/ui/components/title_bar_widget.dart";
|
||||
import "package:photos/utils/dialog_util.dart";
|
||||
import "package:photos/utils/toast_util.dart";
|
||||
|
||||
class MLUserDeveloperOptions extends StatelessWidget {
|
||||
const MLUserDeveloperOptions({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
primary: false,
|
||||
slivers: <Widget>[
|
||||
const TitleBarWidget(
|
||||
flexibleSpaceTitle: TitleBarTitleWidget(
|
||||
title: "ML debug options",
|
||||
),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(delegateBuildContext, index) => Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
"Only use if you know what you're doing",
|
||||
textAlign: TextAlign.left,
|
||||
style: getEnteTextTheme(context).body.copyWith(
|
||||
color: getEnteColorScheme(context).textMuted,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 48),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Purge empty indices",
|
||||
onTap: () async {
|
||||
await deleteEmptyIndices(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Reset all local ML",
|
||||
onTap: () async {
|
||||
await deleteAllLocalML(context);
|
||||
},
|
||||
),
|
||||
const SafeArea(
|
||||
child: SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
childCount: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> deleteEmptyIndices(BuildContext context) async {
|
||||
try {
|
||||
final Set<int> emptyFileIDs = await MLDataDB.instance.getErroredFileIDs();
|
||||
await MLDataDB.instance.deleteFaceIndexForFiles(emptyFileIDs.toList());
|
||||
await MLDataDB.instance.deleteEmbeddings(emptyFileIDs.toList());
|
||||
showShortToast(context, "Deleted ${emptyFileIDs.length} entries");
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteAllLocalML(BuildContext context) async {
|
||||
try {
|
||||
await MLDataDB.instance.dropClustersAndPersonTable(faces: true);
|
||||
await SemanticSearchService.instance.clearIndexes();
|
||||
Bus.instance.fire(PeopleChangedEvent());
|
||||
showShortToast(context, "All local ML cleared");
|
||||
} catch (e) {
|
||||
// ignore: unawaited_futures
|
||||
showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import "package:photos/models/search/search_constants.dart";
|
||||
import "package:photos/models/search/search_result.dart";
|
||||
import "package:photos/models/search/search_types.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/settings/machine_learning_settings_page.dart";
|
||||
import "package:photos/ui/settings/ml/machine_learning_settings_page.dart";
|
||||
import "package:photos/ui/viewer/file/no_thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
|
||||
import "package:photos/ui/viewer/people/add_person_action_sheet.dart";
|
||||
|
||||
@@ -153,7 +153,8 @@ Future<List<FileMLInstruction>> getFilesForMlIndexing() async {
|
||||
}
|
||||
|
||||
Stream<List<FileMLInstruction>> fetchEmbeddingsAndInstructions(
|
||||
int yieldSize,) async* {
|
||||
int yieldSize,
|
||||
) async* {
|
||||
final List<FileMLInstruction> filesToIndex = await getFilesForMlIndexing();
|
||||
final List<List<FileMLInstruction>> chunks =
|
||||
filesToIndex.chunks(embeddingFetchLimit);
|
||||
@@ -312,6 +313,12 @@ Future<String> getImagePathForML(EnteFile enteFile) async {
|
||||
throw ThumbnailRetrievalException(e.toString(), s);
|
||||
}
|
||||
} else {
|
||||
// Don't process the file if it's too large (more than 100MB)
|
||||
if (enteFile.fileSize != null && enteFile.fileSize! > maxFileDownloadSize) {
|
||||
throw Exception(
|
||||
"FileSizeTooLargeForMobileIndexing: size is ${enteFile.fileSize}",
|
||||
);
|
||||
}
|
||||
try {
|
||||
file = await getFile(enteFile, isOrigin: true);
|
||||
} catch (e, s) {
|
||||
|
||||
Reference in New Issue
Block a user