face_debug_section_widget.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import "dart:async";
  2. import "package:flutter/foundation.dart";
  3. import 'package:flutter/material.dart';
  4. import "package:logging/logging.dart";
  5. import "package:photos/core/event_bus.dart";
  6. import "package:photos/events/people_changed_event.dart";
  7. import "package:photos/face/db.dart";
  8. import "package:photos/face/model/person.dart";
  9. import 'package:photos/services/machine_learning/face_ml/face_ml_service.dart';
  10. import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart";
  11. import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
  12. import 'package:photos/theme/ente_theme.dart';
  13. import 'package:photos/ui/components/captioned_text_widget.dart';
  14. import 'package:photos/ui/components/expandable_menu_item_widget.dart';
  15. import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
  16. import 'package:photos/ui/settings/common_settings.dart';
  17. import "package:photos/utils/dialog_util.dart";
  18. import "package:photos/utils/local_settings.dart";
  19. import 'package:photos/utils/toast_util.dart';
  20. class FaceDebugSectionWidget extends StatefulWidget {
  21. const FaceDebugSectionWidget({Key? key}) : super(key: key);
  22. @override
  23. State<FaceDebugSectionWidget> createState() => _FaceDebugSectionWidgetState();
  24. }
  25. class _FaceDebugSectionWidgetState extends State<FaceDebugSectionWidget> {
  26. Timer? _timer;
  27. @override
  28. void initState() {
  29. super.initState();
  30. _timer = Timer.periodic(const Duration(seconds: 5), (timer) {
  31. setState(() {
  32. // Your state update logic here
  33. });
  34. });
  35. }
  36. @override
  37. void dispose() {
  38. _timer?.cancel();
  39. super.dispose();
  40. }
  41. @override
  42. Widget build(BuildContext context) {
  43. return ExpandableMenuItemWidget(
  44. title: "Faces Debug",
  45. selectionOptionsWidget: _getSectionOptions(context),
  46. leadingIcon: Icons.bug_report_outlined,
  47. );
  48. }
  49. Widget _getSectionOptions(BuildContext context) {
  50. final Logger _logger = Logger("FaceDebugSectionWidget");
  51. return Column(
  52. children: [
  53. MenuItemWidget(
  54. captionedTextWidget: FutureBuilder<int>(
  55. future: FaceMLDataDB.instance.getIndexedFileCount(),
  56. builder: (context, snapshot) {
  57. if (snapshot.hasData) {
  58. return CaptionedTextWidget(
  59. title: LocalSettings.instance.isFaceIndexingEnabled
  60. ? "Disable faces (${snapshot.data!} files done)"
  61. : "Enable faces (${snapshot.data!} files done)",
  62. );
  63. }
  64. return const SizedBox.shrink();
  65. },
  66. ),
  67. pressedColor: getEnteColorScheme(context).fillFaint,
  68. trailingIcon: Icons.chevron_right_outlined,
  69. trailingIconIsMuted: true,
  70. onTap: () async {
  71. try {
  72. final isEnabled =
  73. await LocalSettings.instance.toggleFaceIndexing();
  74. if (!isEnabled) {
  75. FaceMlService.instance.pauseIndexingAndClustering();
  76. }
  77. if (mounted) {
  78. setState(() {});
  79. }
  80. } catch (e, s) {
  81. _logger.warning('indexing failed ', e, s);
  82. await showGenericErrorDialog(context: context, error: e);
  83. }
  84. },
  85. ),
  86. sectionOptionSpacing,
  87. MenuItemWidget(
  88. captionedTextWidget: CaptionedTextWidget(
  89. title: LocalSettings.instance.remoteFetchEnabled
  90. ? "Remote fetch enabled"
  91. : "Remote fetch disabled",
  92. ),
  93. pressedColor: getEnteColorScheme(context).fillFaint,
  94. trailingIcon: Icons.chevron_right_outlined,
  95. trailingIconIsMuted: true,
  96. onTap: () async {
  97. try {
  98. await LocalSettings.instance.toggleRemoteFetch();
  99. if (mounted) {
  100. setState(() {});
  101. }
  102. } catch (e, s) {
  103. _logger.warning('Remote fetch toggle failed ', e, s);
  104. await showGenericErrorDialog(context: context, error: e);
  105. }
  106. },
  107. ),
  108. sectionOptionSpacing,
  109. MenuItemWidget(
  110. captionedTextWidget: CaptionedTextWidget(
  111. title: FaceMlService.instance.debugIndexingDisabled
  112. ? "Debug enable indexing again"
  113. : "Debug disable indexing",
  114. ),
  115. pressedColor: getEnteColorScheme(context).fillFaint,
  116. trailingIcon: Icons.chevron_right_outlined,
  117. trailingIconIsMuted: true,
  118. onTap: () async {
  119. try {
  120. FaceMlService.instance.debugIndexingDisabled =
  121. !FaceMlService.instance.debugIndexingDisabled;
  122. if (FaceMlService.instance.debugIndexingDisabled) {
  123. FaceMlService.instance.pauseIndexingAndClustering();
  124. }
  125. if (mounted) {
  126. setState(() {});
  127. }
  128. } catch (e, s) {
  129. _logger.warning('debugIndexingDisabled toggle failed ', e, s);
  130. await showGenericErrorDialog(context: context, error: e);
  131. }
  132. },
  133. ),
  134. sectionOptionSpacing,
  135. MenuItemWidget(
  136. captionedTextWidget: const CaptionedTextWidget(
  137. title: "Run sync, indexing, clustering",
  138. ),
  139. pressedColor: getEnteColorScheme(context).fillFaint,
  140. trailingIcon: Icons.chevron_right_outlined,
  141. trailingIconIsMuted: true,
  142. onTap: () async {
  143. try {
  144. FaceMlService.instance.debugIndexingDisabled = false;
  145. unawaited(FaceMlService.instance.indexAndClusterAll());
  146. } catch (e, s) {
  147. _logger.warning('indexAndClusterAll failed ', e, s);
  148. await showGenericErrorDialog(context: context, error: e);
  149. }
  150. },
  151. ),
  152. sectionOptionSpacing,
  153. MenuItemWidget(
  154. captionedTextWidget: const CaptionedTextWidget(
  155. title: "Run indexing",
  156. ),
  157. pressedColor: getEnteColorScheme(context).fillFaint,
  158. trailingIcon: Icons.chevron_right_outlined,
  159. trailingIconIsMuted: true,
  160. onTap: () async {
  161. try {
  162. FaceMlService.instance.debugIndexingDisabled = false;
  163. unawaited(FaceMlService.instance.indexAllImages());
  164. } catch (e, s) {
  165. _logger.warning('indexing failed ', e, s);
  166. await showGenericErrorDialog(context: context, error: e);
  167. }
  168. },
  169. ),
  170. sectionOptionSpacing,
  171. MenuItemWidget(
  172. captionedTextWidget: FutureBuilder<double>(
  173. future: FaceMLDataDB.instance.getClusteredToIndexableFilesRatio(),
  174. builder: (context, snapshot) {
  175. if (snapshot.hasData) {
  176. return CaptionedTextWidget(
  177. title:
  178. "Run clustering (${(100 * snapshot.data!).toStringAsFixed(0)}% done)",
  179. );
  180. }
  181. return const SizedBox.shrink();
  182. },
  183. ),
  184. pressedColor: getEnteColorScheme(context).fillFaint,
  185. trailingIcon: Icons.chevron_right_outlined,
  186. trailingIconIsMuted: true,
  187. onTap: () async {
  188. try {
  189. await PersonService.instance.storeRemoteFeedback();
  190. FaceMlService.instance.debugIndexingDisabled = false;
  191. await FaceMlService.instance
  192. .clusterAllImages(clusterInBuckets: true);
  193. Bus.instance.fire(PeopleChangedEvent());
  194. showShortToast(context, "Done");
  195. } catch (e, s) {
  196. _logger.warning('clustering failed ', e, s);
  197. await showGenericErrorDialog(context: context, error: e);
  198. }
  199. },
  200. ),
  201. sectionOptionSpacing,
  202. MenuItemWidget(
  203. captionedTextWidget: const CaptionedTextWidget(
  204. title: "Check for mixed clusters",
  205. ),
  206. pressedColor: getEnteColorScheme(context).fillFaint,
  207. trailingIcon: Icons.chevron_right_outlined,
  208. trailingIconIsMuted: true,
  209. onTap: () async {
  210. try {
  211. final susClusters =
  212. await ClusterFeedbackService.instance.checkForMixedClusters();
  213. for (final clusterinfo in susClusters) {
  214. Future.delayed(const Duration(seconds: 4), () {
  215. showToast(
  216. context,
  217. 'Cluster with ${clusterinfo.$2} photos is sus',
  218. );
  219. });
  220. }
  221. } catch (e, s) {
  222. _logger.warning('Checking for mixed clusters failed', e, s);
  223. await showGenericErrorDialog(context: context, error: e);
  224. }
  225. },
  226. ),
  227. sectionOptionSpacing,
  228. MenuItemWidget(
  229. captionedTextWidget: const CaptionedTextWidget(
  230. title: "Sync person mappings ",
  231. ),
  232. pressedColor: getEnteColorScheme(context).fillFaint,
  233. trailingIcon: Icons.chevron_right_outlined,
  234. trailingIconIsMuted: true,
  235. onTap: () async {
  236. try {
  237. await PersonService.instance.reconcileClusters();
  238. Bus.instance.fire(PeopleChangedEvent());
  239. showShortToast(context, "Done");
  240. } catch (e, s) {
  241. _logger.warning('sync person mappings failed ', e, s);
  242. await showGenericErrorDialog(context: context, error: e);
  243. }
  244. },
  245. ),
  246. sectionOptionSpacing,
  247. MenuItemWidget(
  248. captionedTextWidget: const CaptionedTextWidget(
  249. title: "Reset feedback",
  250. ),
  251. pressedColor: getEnteColorScheme(context).fillFaint,
  252. trailingIcon: Icons.chevron_right_outlined,
  253. trailingIconIsMuted: true,
  254. alwaysShowSuccessState: true,
  255. onTap: () async {
  256. await showChoiceDialog(
  257. context,
  258. title: "Are you sure?",
  259. body:
  260. "This will drop all people and their related feedback. It will keep clustering labels and embeddings untouched.",
  261. firstButtonLabel: "Yes, confirm",
  262. firstButtonOnTap: () async {
  263. try {
  264. await FaceMLDataDB.instance.dropFeedbackTables();
  265. Bus.instance.fire(PeopleChangedEvent());
  266. showShortToast(context, "Done");
  267. } catch (e, s) {
  268. _logger.warning('reset feedback failed ', e, s);
  269. await showGenericErrorDialog(context: context, error: e);
  270. }
  271. },
  272. );
  273. },
  274. ),
  275. sectionOptionSpacing,
  276. MenuItemWidget(
  277. captionedTextWidget: const CaptionedTextWidget(
  278. title: "Reset feedback and clustering",
  279. ),
  280. pressedColor: getEnteColorScheme(context).fillFaint,
  281. trailingIcon: Icons.chevron_right_outlined,
  282. trailingIconIsMuted: true,
  283. onTap: () async {
  284. await showChoiceDialog(
  285. context,
  286. title: "Are you sure?",
  287. body:
  288. "This will delete all people, their related feedback and clustering labels. It will keep embeddings untouched.",
  289. firstButtonLabel: "Yes, confirm",
  290. firstButtonOnTap: () async {
  291. try {
  292. final List<PersonEntity> persons =
  293. await PersonService.instance.getPersons();
  294. for (final PersonEntity p in persons) {
  295. await PersonService.instance.deletePerson(p.remoteID);
  296. }
  297. await FaceMLDataDB.instance.dropClustersAndPersonTable();
  298. Bus.instance.fire(PeopleChangedEvent());
  299. showShortToast(context, "Done");
  300. } catch (e, s) {
  301. _logger.warning('peopleToPersonMapping remove failed ', e, s);
  302. await showGenericErrorDialog(context: context, error: e);
  303. }
  304. },
  305. );
  306. },
  307. ),
  308. sectionOptionSpacing,
  309. MenuItemWidget(
  310. captionedTextWidget: const CaptionedTextWidget(
  311. title: "Reset everything (embeddings)",
  312. ),
  313. pressedColor: getEnteColorScheme(context).fillFaint,
  314. trailingIcon: Icons.chevron_right_outlined,
  315. trailingIconIsMuted: true,
  316. onTap: () async {
  317. await showChoiceDialog(
  318. context,
  319. title: "Are you sure?",
  320. body:
  321. "You will need to again re-index all the faces. You can drop feedback if you want to label again",
  322. firstButtonLabel: "Yes, confirm",
  323. firstButtonOnTap: () async {
  324. try {
  325. await FaceMLDataDB.instance
  326. .dropClustersAndPersonTable(faces: true);
  327. Bus.instance.fire(PeopleChangedEvent());
  328. showShortToast(context, "Done");
  329. } catch (e, s) {
  330. _logger.warning('drop feedback failed ', e, s);
  331. await showGenericErrorDialog(context: context, error: e);
  332. }
  333. },
  334. );
  335. },
  336. ),
  337. ],
  338. );
  339. }
  340. }