mirror of
https://github.com/ente-io/ente.git
synced 2025-05-30 06:19:44 +00:00
136 lines
3.5 KiB
Dart
136 lines
3.5 KiB
Dart
import "package:photos/models/ml/face/box.dart";
|
|
import "package:photos/models/ml/face/detection.dart";
|
|
import "package:photos/models/ml/face/dimension.dart";
|
|
import "package:photos/models/ml/face/landmark.dart";
|
|
import 'package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart';
|
|
import "package:photos/services/machine_learning/ml_result.dart";
|
|
|
|
// FileInfo contains the image width and height of the image the face was detected in.
|
|
class FileInfo {
|
|
int? imageWidth;
|
|
int? imageHeight;
|
|
FileInfo({
|
|
this.imageWidth,
|
|
this.imageHeight,
|
|
});
|
|
}
|
|
|
|
class Face {
|
|
final String faceID;
|
|
final List<double> embedding;
|
|
Detection detection;
|
|
final double score;
|
|
final double blur;
|
|
|
|
///#region Local DB fields
|
|
// This is not stored on the server, using it for local DB row
|
|
FileInfo? fileInfo;
|
|
final int fileID;
|
|
|
|
///#endregion
|
|
|
|
bool get isBlurry => blur < kLaplacianHardThreshold;
|
|
|
|
bool get hasHighScore => score > kMinimumQualityFaceScore;
|
|
|
|
bool get isHighQuality => (!isBlurry) && hasHighScore;
|
|
|
|
Face(
|
|
this.faceID,
|
|
this.fileID,
|
|
this.embedding,
|
|
this.score,
|
|
this.detection,
|
|
this.blur, {
|
|
this.fileInfo,
|
|
});
|
|
|
|
factory Face.fromFaceResult(
|
|
FaceResult faceResult,
|
|
int fileID,
|
|
Dimensions decodedDimensions,
|
|
) {
|
|
final detection = Detection(
|
|
box: FaceBox(
|
|
x: faceResult.detection.xMinBox,
|
|
y: faceResult.detection.yMinBox,
|
|
width: faceResult.detection.width,
|
|
height: faceResult.detection.height,
|
|
),
|
|
landmarks: faceResult.detection.allKeypoints
|
|
.map(
|
|
(keypoint) => Landmark(
|
|
x: keypoint[0],
|
|
y: keypoint[1],
|
|
),
|
|
)
|
|
.toList(),
|
|
);
|
|
return Face(
|
|
faceResult.faceId,
|
|
fileID,
|
|
faceResult.embedding,
|
|
faceResult.detection.score,
|
|
detection,
|
|
faceResult.blurValue,
|
|
fileInfo: FileInfo(
|
|
imageHeight: decodedDimensions.height,
|
|
imageWidth: decodedDimensions.width,
|
|
),
|
|
);
|
|
}
|
|
|
|
factory Face.empty(int fileID, {bool error = false}) {
|
|
return Face(
|
|
"${fileID}_0_0_0_0",
|
|
fileID,
|
|
<double>[],
|
|
error ? -1.0 : 0.0,
|
|
Detection.empty(),
|
|
0.0,
|
|
);
|
|
}
|
|
|
|
factory Face.fromJson(Map<String, dynamic> json) {
|
|
final String faceID = json['faceID'] as String;
|
|
final int fileID = getFileIdFromFaceId(faceID);
|
|
return Face(
|
|
faceID,
|
|
fileID,
|
|
parseAsDoubleList(json['embedding'] as List),
|
|
json['score'] as double,
|
|
Detection.fromJson(json['detection'] as Map<String, dynamic>),
|
|
// high value means t
|
|
(json['blur'] ?? kLapacianDefault) as double,
|
|
);
|
|
}
|
|
|
|
// Note: Keep the information in toJson minimum. Keep in sync with desktop.
|
|
// Derive fields like fileID from other values whenever possible
|
|
Map<String, dynamic> toJson() => {
|
|
'faceID': faceID,
|
|
'embedding': embedding,
|
|
'detection': detection.toJson(),
|
|
'score': score,
|
|
'blur': blur,
|
|
};
|
|
}
|
|
|
|
List<double> parseAsDoubleList(List<dynamic> inputList) {
|
|
if (inputList.isEmpty) return const [];
|
|
|
|
if (inputList is List<double>) return inputList;
|
|
return List<double>.generate(
|
|
inputList.length,
|
|
(index) {
|
|
final value = inputList[index];
|
|
if (value is int) return value.toDouble();
|
|
if (value is double) return value;
|
|
throw FormatException(
|
|
'Invalid type at index $index: ${value.runtimeType}',
|
|
);
|
|
},
|
|
growable: false,
|
|
);
|
|
}
|