2022-10-07 19:28:39 +00:00
|
|
|
<?php
|
2022-10-19 17:10:36 +00:00
|
|
|
|
2022-10-07 19:28:39 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace OCA\Memories\Db;
|
|
|
|
|
|
|
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
2022-10-19 17:10:36 +00:00
|
|
|
use OCP\IDBConnection;
|
2022-10-07 19:28:39 +00:00
|
|
|
|
2022-12-08 21:00:53 +00:00
|
|
|
trait TimelineQueryPeopleRecognize
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-10-07 19:28:39 +00:00
|
|
|
protected IDBConnection $connection;
|
|
|
|
|
2023-03-19 02:03:17 +00:00
|
|
|
public function transformPeopleRecognitionFilter(IQueryBuilder $query, string $userId, string $faceStr, bool $isAggregate)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-12-08 21:00:53 +00:00
|
|
|
// Get name and uid of face user
|
2022-10-08 06:46:08 +00:00
|
|
|
$faceNames = explode('/', $faceStr);
|
2022-10-19 17:10:36 +00:00
|
|
|
if (2 !== \count($faceNames)) {
|
|
|
|
throw new \Exception('Invalid face query');
|
|
|
|
}
|
2023-03-19 02:03:17 +00:00
|
|
|
|
|
|
|
// Starting with Recognize v3.6, the detections are duplicated for each user
|
|
|
|
// So we don't need to use the user ID provided by the user, but retain
|
|
|
|
// this here for backwards compatibility + API consistency with Face Recognition
|
|
|
|
// $faceUid = $faceNames[0];
|
|
|
|
|
2022-10-08 06:26:09 +00:00
|
|
|
$faceName = $faceNames[1];
|
|
|
|
|
2023-03-15 23:12:55 +00:00
|
|
|
if (!$isAggregate) {
|
|
|
|
// Multiple detections for the same image
|
|
|
|
$query->addSelect('rfd.id AS faceid');
|
|
|
|
}
|
|
|
|
|
2022-10-08 06:26:09 +00:00
|
|
|
// Join with cluster
|
2023-03-15 23:12:55 +00:00
|
|
|
$clusterQuery = null;
|
2023-03-19 02:03:17 +00:00
|
|
|
if ('NULL' === $faceName) {
|
|
|
|
$clusterQuery = $query->expr()->isNull('rfd.cluster_id');
|
|
|
|
} else {
|
2023-03-15 23:12:55 +00:00
|
|
|
$nameField = is_numeric($faceName) ? 'rfc.id' : 'rfc.title';
|
|
|
|
$query->innerJoin('m', 'recognize_face_clusters', 'rfc', $query->expr()->andX(
|
2023-03-19 02:03:17 +00:00
|
|
|
$query->expr()->eq('rfc.user_id', $query->createNamedParameter($userId)),
|
2023-03-15 23:12:55 +00:00
|
|
|
$query->expr()->eq($nameField, $query->createNamedParameter($faceName)),
|
|
|
|
));
|
|
|
|
$clusterQuery = $query->expr()->eq('rfd.cluster_id', 'rfc.id');
|
|
|
|
}
|
2022-10-18 21:08:27 +00:00
|
|
|
|
|
|
|
// Join with detections
|
2022-10-07 19:28:39 +00:00
|
|
|
$query->innerJoin('m', 'recognize_face_detections', 'rfd', $query->expr()->andX(
|
|
|
|
$query->expr()->eq('rfd.file_id', 'm.fileid'),
|
2023-03-15 23:12:55 +00:00
|
|
|
$clusterQuery,
|
2022-10-07 19:28:39 +00:00
|
|
|
));
|
2022-10-18 21:08:27 +00:00
|
|
|
}
|
|
|
|
|
2022-12-08 21:00:53 +00:00
|
|
|
public function transformPeopleRecognizeRect(IQueryBuilder &$query, string $userId)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-10-18 21:08:27 +00:00
|
|
|
// Include detection params in response
|
|
|
|
$query->addSelect(
|
|
|
|
'rfd.width AS face_w',
|
|
|
|
'rfd.height AS face_h',
|
|
|
|
'rfd.x AS face_x',
|
|
|
|
'rfd.y AS face_y',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-03-23 21:45:56 +00:00
|
|
|
public function getPeopleRecognize(string $uid)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-10-07 19:28:39 +00:00
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
|
|
|
|
// SELECT all face clusters
|
|
|
|
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
|
2022-10-08 06:26:09 +00:00
|
|
|
$query->select('rfc.id', 'rfc.user_id', 'rfc.title', $count)->from('recognize_face_clusters', 'rfc');
|
2022-10-07 19:28:39 +00:00
|
|
|
|
|
|
|
// WHERE there are faces with this cluster
|
|
|
|
$query->innerJoin('rfc', 'recognize_face_detections', 'rfd', $query->expr()->eq('rfc.id', 'rfd.cluster_id'));
|
|
|
|
|
|
|
|
// WHERE these items are memories indexed photos
|
|
|
|
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
|
|
|
|
|
|
|
|
// WHERE these photos are in the user's requested folder recursively
|
2023-03-23 21:45:56 +00:00
|
|
|
$query = $this->joinFilecache($query);
|
2022-10-07 19:28:39 +00:00
|
|
|
|
2023-03-19 02:03:17 +00:00
|
|
|
// WHERE this cluster belongs to the user
|
|
|
|
$query->where($query->expr()->eq('rfc.user_id', $query->createNamedParameter($uid)));
|
|
|
|
|
2022-10-07 19:28:39 +00:00
|
|
|
// GROUP by ID of face cluster
|
|
|
|
$query->groupBy('rfc.id');
|
|
|
|
|
|
|
|
// ORDER by number of faces in cluster
|
2022-10-18 15:02:56 +00:00
|
|
|
$query->orderBy($query->createFunction("rfc.title <> ''"), 'DESC');
|
|
|
|
$query->addOrderBy('count', 'DESC');
|
2022-10-16 23:46:37 +00:00
|
|
|
$query->addOrderBy('rfc.id'); // tie-breaker
|
2022-10-07 19:28:39 +00:00
|
|
|
|
|
|
|
// FETCH all faces
|
2022-10-27 16:19:25 +00:00
|
|
|
$cursor = $this->executeQueryWithCTEs($query);
|
|
|
|
$faces = $cursor->fetchAll();
|
2022-10-07 19:28:39 +00:00
|
|
|
|
|
|
|
// Post process
|
2022-10-19 17:10:36 +00:00
|
|
|
foreach ($faces as &$row) {
|
2022-10-19 17:15:14 +00:00
|
|
|
$row['id'] = (int) $row['id'];
|
2023-03-19 02:03:17 +00:00
|
|
|
$row['count'] = (int) $row['count'];
|
2022-10-19 17:10:36 +00:00
|
|
|
$row['name'] = $row['title'];
|
|
|
|
unset($row['title']);
|
2022-10-07 19:28:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return $faces;
|
|
|
|
}
|
|
|
|
|
2023-03-23 21:45:56 +00:00
|
|
|
public function getPeopleRecognizePhotos(int $id, ?int $limit): array
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-10-07 19:28:39 +00:00
|
|
|
$query = $this->connection->getQueryBuilder();
|
|
|
|
|
|
|
|
// SELECT face detections for ID
|
2022-10-08 02:00:55 +00:00
|
|
|
$query->select(
|
2022-10-19 17:10:36 +00:00
|
|
|
'rfd.file_id', // Get actual file
|
|
|
|
'rfd.x', // Image cropping
|
|
|
|
'rfd.y',
|
|
|
|
'rfd.width',
|
|
|
|
'rfd.height',
|
|
|
|
'm.w as image_width', // Scoring
|
|
|
|
'm.h as image_height',
|
|
|
|
'm.fileid',
|
|
|
|
'm.datetaken', // Just in case, for postgres
|
2022-10-08 02:00:55 +00:00
|
|
|
)->from('recognize_face_detections', 'rfd');
|
2023-03-19 02:03:17 +00:00
|
|
|
|
2023-03-22 23:44:51 +00:00
|
|
|
// WHERE detection belongs to this cluster
|
|
|
|
$query->where($query->expr()->eq('rfd.cluster_id', $query->createNamedParameter($id)));
|
2022-10-07 19:28:39 +00:00
|
|
|
|
|
|
|
// WHERE these photos are memories indexed
|
|
|
|
$query->innerJoin('rfd', 'memories', 'm', $query->expr()->eq('m.fileid', 'rfd.file_id'));
|
|
|
|
|
|
|
|
// WHERE these photos are in the user's requested folder recursively
|
2023-03-23 21:45:56 +00:00
|
|
|
$query = $this->joinFilecache($query);
|
2022-10-07 19:28:39 +00:00
|
|
|
|
2022-10-17 17:41:58 +00:00
|
|
|
// LIMIT results
|
2023-03-22 23:44:51 +00:00
|
|
|
if (null !== $limit) {
|
|
|
|
$query->setMaxResults($limit);
|
|
|
|
}
|
2022-10-17 17:41:58 +00:00
|
|
|
|
|
|
|
// Sort by date taken so we get recent photos
|
|
|
|
$query->orderBy('m.datetaken', 'DESC');
|
|
|
|
$query->addOrderBy('m.fileid', 'DESC'); // tie-breaker
|
|
|
|
|
|
|
|
// FETCH face detections
|
2023-03-22 23:44:51 +00:00
|
|
|
return $this->executeQueryWithCTEs($query)->fetchAll();
|
2022-10-07 19:28:39 +00:00
|
|
|
}
|
2022-10-19 17:10:36 +00:00
|
|
|
|
|
|
|
/** Convert face fields to object */
|
2023-03-23 00:44:22 +00:00
|
|
|
private function processPeopleRecognizeDetection(&$row)
|
2022-10-19 17:10:36 +00:00
|
|
|
{
|
2022-12-08 21:00:53 +00:00
|
|
|
// Differentiate Recognize queries from Face Recognition
|
2022-10-19 17:10:36 +00:00
|
|
|
if (!isset($row) || !isset($row['face_w'])) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-23 00:44:22 +00:00
|
|
|
// Convert face rect to object
|
|
|
|
$row['facerect'] = [
|
|
|
|
'w' => (float) $row['face_w'],
|
|
|
|
'h' => (float) $row['face_h'],
|
|
|
|
'x' => (float) $row['face_x'],
|
|
|
|
'y' => (float) $row['face_y'],
|
|
|
|
];
|
2022-10-19 17:10:36 +00:00
|
|
|
|
|
|
|
unset($row['face_w'], $row['face_h'], $row['face_x'], $row['face_y']);
|
|
|
|
}
|
|
|
|
}
|