Skip to content

Commit 574dd0a

Browse files
committed
fix: Set new constants for clustering vectors from the new backend app
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
1 parent 1ee30aa commit 574dd0a

5 files changed

Lines changed: 79 additions & 28 deletions

File tree

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"php": "8.2.0"
4343
},
4444
"allow-plugins": {
45+
"bamarni/composer-bin-plugin": true,
4546
"composer/package-versions-deprecated": true
4647
},
4748
"autoloader-suffix": "Recognize",

lib/Classifiers/TaskProcessing/ImageFaceRecognitionClassifier.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@
1414
use OCA\Recognize\TaskProcessing\ImageFaceRecognitionTaskType;
1515

1616
final class ImageFaceRecognitionClassifier extends AbstractTaskProcessingClassifier {
17+
const MIN_FACE_RECOGNITION_SCORE = 0.6;
18+
19+
public const MIN_DATASET_SIZE = 120;
20+
public const MIN_DETECTION_SIZE = 0.03;
21+
public const MIN_CLUSTER_SEPARATION = 0.7;
22+
public const MAX_CLUSTER_EDGE_LENGTH = 1.0;
23+
public const DIMENSIONS = 512;
24+
public const MAX_OVERLAP_NEW_CLUSTER = 0.1;
25+
public const MIN_OVERLAP_EXISTING_CLUSTER = 0.5;
26+
1727
protected function getTaskTypeId(): string {
1828
return ImageFaceRecognitionTaskType::ID;
1929
}

lib/Db/FaceDetectionMapper.php

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
declare(strict_types=1);
88
namespace OCA\Recognize\Db;
99

10+
use OCA\Recognize\Classifiers\TaskProcessing\ImageFaceRecognitionClassifier;
1011
use OCA\Recognize\Service\FaceClusterAnalyzer;
12+
use OCA\Recognize\Service\SettingsService;
1113
use OCP\AppFramework\Db\DoesNotExistException;
1214
use OCP\AppFramework\Db\Entity;
1315
use OCP\AppFramework\Db\QBMapper;
@@ -21,11 +23,20 @@
2123
*/
2224
final class FaceDetectionMapper extends QBMapper {
2325
private IConfig $config;
26+
private SettingsService $settingsService;
2427

25-
public function __construct(IDBConnection $db, IConfig $config) {
28+
public function __construct(IDBConnection $db, IConfig $config, SettingsService $settingsService) {
2629
parent::__construct($db, 'recognize_face_detections', FaceDetection::class);
2730
$this->db = $db;
2831
$this->config = $config;
32+
$this->settingsService = $settingsService;
33+
}
34+
35+
private function getMinDetectionSize(): float {
36+
if ($this->settingsService->getSetting('taskprocessing.enabled') === 'true') {
37+
return ImageFaceRecognitionClassifier::MIN_DETECTION_SIZE;
38+
}
39+
return FaceClusterAnalyzer::MIN_DETECTION_SIZE;
2940
}
3041

3142
/**
@@ -329,12 +340,13 @@ public function findDetectionForPreviewImageByClusterId(int $clusterId) : FaceDe
329340
}
330341

331342
public function countUnclustered(): int {
343+
$minDetectionSize = $this->getMinDetectionSize();
332344
$qb = $this->db->getQueryBuilder();
333345
$qb->select($qb->func()->count('id'))
334346
->from('recognize_face_detections')
335347
->where($qb->expr()->isNull('cluster_id'))
336-
->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)))
337-
->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)));
348+
->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter($minDetectionSize)))
349+
->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter($minDetectionSize)));
338350
$result = $qb->executeQuery();
339351
/** @var int|string $count */
340352
$count = $result->fetch(\PDO::FETCH_COLUMN);
@@ -347,12 +359,13 @@ public function countUnclustered(): int {
347359
* @throws \OCP\DB\Exception
348360
*/
349361
public function getUsersForUnclustered(): array {
362+
$minDetectionSize = $this->getMinDetectionSize();
350363
$qb = $this->db->getQueryBuilder();
351364
$qb->selectDistinct('user_id')
352365
->from('recognize_face_detections')
353366
->where($qb->expr()->isNull('cluster_id'))
354-
->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)))
355-
->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter(FaceClusterAnalyzer::MIN_DETECTION_SIZE)));
367+
->andWhere($qb->expr()->gte('height', $qb->createPositionalParameter($minDetectionSize)))
368+
->andWhere($qb->expr()->gte('width', $qb->createPositionalParameter($minDetectionSize)));
356369
$result = $qb->executeQuery();
357370
/** @var array<string> $users */
358371
$users = $result->fetchAll(\PDO::FETCH_COLUMN);

lib/Service/FaceClusterAnalyzer.php

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use \OCA\Recognize\Vendor\Rubix\ML\Datasets\Labeled;
1111
use \OCA\Recognize\Vendor\Rubix\ML\Kernels\Distance\Euclidean;
12+
use OCA\Recognize\Classifiers\TaskProcessing\ImageFaceRecognitionClassifier;
1213
use OCA\Recognize\Clustering\HDBSCAN;
1314
use OCA\Recognize\Db\FaceCluster;
1415
use 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;

lib/TaskProcessing/TaskResultListener.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use OCA\Recognize\Classifiers\Images\ClusteringFaceClassifier;
1616
use OCA\Recognize\Classifiers\Images\ImagenetClassifier;
1717
use OCA\Recognize\Classifiers\Images\LandmarksClassifier;
18+
use OCA\Recognize\Classifiers\TaskProcessing\ImageFaceRecognitionClassifier;
1819
use OCA\Recognize\Classifiers\Video\MovinetClassifier;
1920
use OCA\Recognize\Db\FaceDetection;
2021
use OCA\Recognize\Db\FaceDetectionMapper;
@@ -27,9 +28,11 @@
2728
use OCP\EventDispatcher\IEventListener;
2829
use OCP\Files\Config\ICachedMountInfo;
2930
use OCP\Files\Config\IUserMountCache;
31+
use OCP\IUserManager;
3032
use OCP\TaskProcessing\Events\TaskFailedEvent;
3133
use OCP\TaskProcessing\Events\TaskSuccessfulEvent;
3234
use OCP\TaskProcessing\Task;
35+
use OCP\IUserSession;
3336
use Psr\Log\LoggerInterface;
3437

3538
/**
@@ -49,6 +52,8 @@ public function __construct(
4952
private IAppConfig $config,
5053
private IJobList $jobList,
5154
private QueueService $queue,
55+
private IUserSession $userSession,
56+
private IUserManager $userManager,
5257
) {
5358
}
5459

@@ -90,6 +95,8 @@ private function handleSuccess(TaskSuccessfulEvent $event): void {
9095
$fileIds = array_map('intval', array_values($input));
9196
$results = array_values($output);
9297

98+
$this->userSession->setUser($this->userManager->get($task->getUserId()));
99+
93100
switch ($task->getTaskTypeId()) {
94101
case ImageClassificationTaskType::ID:
95102
$this->applyTagResults($fileIds, $results, ImagenetClassifier::MODEL_NAME, false);
@@ -188,13 +195,7 @@ private function applyFaceResults(array $fileIds, array $results): void {
188195
if (!is_array($face)) {
189196
continue;
190197
}
191-
if (isset($face['score']) && (float)$face['score'] < ClusteringFaceClassifier::MIN_FACE_RECOGNITION_SCORE) {
192-
continue;
193-
}
194-
if (isset($face['angle']['roll'], $face['angle']['yaw'])
195-
&& (abs((float)$face['angle']['roll']) > ClusteringFaceClassifier::MAX_FACE_ROLL
196-
|| abs((float)$face['angle']['yaw']) > ClusteringFaceClassifier::MAX_FACE_YAW)
197-
) {
198+
if (isset($face['score']) && (float)$face['score'] < ImageFaceRecognitionClassifier::MIN_FACE_RECOGNITION_SCORE) {
198199
continue;
199200
}
200201
// Accept either a full face object {x,y,width,height,score,vector,angle}

0 commit comments

Comments
 (0)