99
1010use \OCA \Recognize \Vendor \Rubix \ML \Datasets \Labeled ;
1111use \OCA \Recognize \Vendor \Rubix \ML \Kernels \Distance \Euclidean ;
12+ use OCA \Recognize \Classifiers \TaskProcessing \ImageFaceRecognitionClassifier ;
1213use OCA \Recognize \Clustering \HDBSCAN ;
1314use OCA \Recognize \Db \FaceCluster ;
1415use OCA \Recognize \Db \FaceClusterMapper ;
@@ -27,14 +28,35 @@ final class FaceClusterAnalyzer {
2728 private FaceDetectionMapper $ faceDetections ;
2829 private FaceClusterMapper $ faceClusters ;
2930 private Logger $ logger ;
30- private int $ minDatasetSize = self ::MIN_DATASET_SIZE ;
31+ private int $ minDatasetSize ;
32+ private float $ minDetectionSize ;
33+ private float $ minClusterSeparation ;
34+ private float $ maxClusterEdgeLength ;
35+ private float $ maxOverlapNewCluster ;
36+ private float $ minOverlapExistingCluster ;
3137 private SettingsService $ settingsService ;
3238
3339 public function __construct (FaceDetectionMapper $ faceDetections , FaceClusterMapper $ faceClusters , Logger $ logger , SettingsService $ settingsService ) {
3440 $ this ->faceDetections = $ faceDetections ;
3541 $ this ->faceClusters = $ faceClusters ;
3642 $ this ->logger = $ logger ;
3743 $ this ->settingsService = $ settingsService ;
44+
45+ if ($ this ->settingsService ->getSetting ('taskprocessing.enabled ' ) === 'true ' ) {
46+ $ this ->minDatasetSize = ImageFaceRecognitionClassifier::MIN_DATASET_SIZE ;
47+ $ this ->minDetectionSize = ImageFaceRecognitionClassifier::MIN_DETECTION_SIZE ;
48+ $ this ->minClusterSeparation = ImageFaceRecognitionClassifier::MIN_CLUSTER_SEPARATION ;
49+ $ this ->maxClusterEdgeLength = ImageFaceRecognitionClassifier::MAX_CLUSTER_EDGE_LENGTH ;
50+ $ this ->maxOverlapNewCluster = ImageFaceRecognitionClassifier::MAX_OVERLAP_NEW_CLUSTER ;
51+ $ this ->minOverlapExistingCluster = ImageFaceRecognitionClassifier::MIN_OVERLAP_EXISTING_CLUSTER ;
52+ } else {
53+ $ this ->minDatasetSize = self ::MIN_DATASET_SIZE ;
54+ $ this ->minDetectionSize = self ::MIN_DETECTION_SIZE ;
55+ $ this ->minClusterSeparation = self ::MIN_CLUSTER_SEPARATION ;
56+ $ this ->maxClusterEdgeLength = self ::MAX_CLUSTER_EDGE_LENGTH ;
57+ $ this ->maxOverlapNewCluster = self ::MAX_OVERLAP_NEW_CLUSTER ;
58+ $ this ->minOverlapExistingCluster = self ::MIN_OVERLAP_EXISTING_CLUSTER ;
59+ }
3860 }
3961
4062 public function setMinDatasetSize (int $ minSize ) : void {
@@ -64,12 +86,12 @@ public function calculateClusters(string $userId, int $batchSize = 0): void {
6486 }
6587
6688 if ($ batchSize > 0 ) {
67- $ rejectedDetections = $ this ->faceDetections ->sampleRejectedDetectionsByUserId ($ userId , $ this ->getRejectSampleSize ($ batchSize ), self :: MIN_DETECTION_SIZE , self :: MIN_DETECTION_SIZE );
89+ $ rejectedDetections = $ this ->faceDetections ->sampleRejectedDetectionsByUserId ($ userId , $ this ->getRejectSampleSize ($ batchSize ), $ this -> minDetectionSize , $ this -> minDetectionSize );
6890 $ requestedFreshDetectionCount = max ($ batchSize - count ($ rejectedDetections ) - count ($ sampledDetections ), 500 );
69- $ freshDetections = $ this ->faceDetections ->findUnclusteredByUserId ($ userId , $ requestedFreshDetectionCount , self :: MIN_DETECTION_SIZE , self :: MIN_DETECTION_SIZE );
91+ $ freshDetections = $ this ->faceDetections ->findUnclusteredByUserId ($ userId , $ requestedFreshDetectionCount , $ this -> minDetectionSize , $ this -> minDetectionSize );
7092 } else {
71- $ freshDetections = $ this ->faceDetections ->findUnclusteredByUserId ($ userId , 0 , self :: MIN_DETECTION_SIZE , self :: MIN_DETECTION_SIZE );
72- $ rejectedDetections = $ this ->faceDetections ->sampleRejectedDetectionsByUserId ($ userId , $ this ->getRejectSampleSize (count ($ freshDetections )), self :: MIN_DETECTION_SIZE , self :: MIN_DETECTION_SIZE );
93+ $ freshDetections = $ this ->faceDetections ->findUnclusteredByUserId ($ userId , 0 , $ this -> minDetectionSize , $ this -> minDetectionSize );
94+ $ rejectedDetections = $ this ->faceDetections ->sampleRejectedDetectionsByUserId ($ userId , $ this ->getRejectSampleSize (count ($ freshDetections )), $ this -> minDetectionSize , $ this -> minDetectionSize );
7395 }
7496
7597
@@ -94,7 +116,7 @@ public function calculateClusters(string $userId, int $batchSize = 0): void {
94116 $ hdbscan = new HDBSCAN ($ dataset , $ this ->getMinClusterSize ($ n ), $ this ->getMinSampleSize ($ n ));
95117
96118 $ numberOfClusteredDetections = 0 ;
97- $ clusters = $ hdbscan ->predict (self :: MIN_CLUSTER_SEPARATION , self :: MAX_CLUSTER_EDGE_LENGTH );
119+ $ clusters = $ hdbscan ->predict ($ this -> minClusterSeparation , $ this -> maxClusterEdgeLength );
98120
99121 foreach ($ clusters as $ flatCluster ) {
100122 /** @var int[] $detectionKeys */
@@ -132,10 +154,10 @@ public function calculateClusters(string $userId, int $batchSize = 0): void {
132154 }
133155
134156 // If more than X% of already clustered detections are for this, we keep it
135- if ($ overlap > self :: MIN_OVERLAP_EXISTING_CLUSTER ) {
157+ if ($ overlap > $ this -> minOverlapExistingCluster ) {
136158 $ clusterId = $ oldClusterId ;
137159 $ cluster = $ this ->faceClusters ->find ($ clusterId );
138- } elseif ($ overlap < self :: MAX_OVERLAP_NEW_CLUSTER ) {
160+ } elseif ($ overlap < $ this -> maxOverlapNewCluster ) {
139161 // otherwise we create a new cluster
140162
141163 $ cluster = new FaceCluster ();
@@ -187,17 +209,21 @@ public function calculateClusters(string $userId, int $batchSize = 0): void {
187209 * @return list<float>
188210 */
189211 public static function calculateCentroidOfDetections (array $ detections ): array {
190- // init 128 dimensional vector
191- /** @var list<float> $sum */
192- $ sum = [];
193- for ($ i = 0 ; $ i < self ::DIMENSIONS ; $ i ++) {
194- $ sum [] = 0.0 ;
195- }
196-
197212 if (count ($ detections ) === 0 ) {
198- return $ sum ;
213+ /** @var list<float> $empty */
214+ $ empty = [];
215+ for ($ i = 0 ; $ i < self ::DIMENSIONS ; $ i ++) {
216+ $ empty [] = 0.0 ;
217+ }
218+ return $ empty ;
199219 }
200220
221+ // Size the accumulator from the first detection so both 128-dim (legacy) and
222+ // 512-dim (buffalo_l/taskprocessing) embeddings work without a runtime switch.
223+ $ dimensions = count (reset ($ detections )->getVector ());
224+ /** @var list<float> $sum */
225+ $ sum = array_fill (0 , $ dimensions , 0.0 );
226+
201227 foreach ($ detections as $ detection ) {
202228 $ sum = array_map (static function (float $ el , float $ el2 ): float {
203229 return $ el + $ el2 ;
0 commit comments