memories/lib/Db/TimelineQueryFaces.php

179 lines
6.2 KiB
PHP
Raw Normal View History

<?php
2022-10-19 17:10:36 +00:00
declare(strict_types=1);
namespace OCA\Memories\Db;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\Files\Folder;
2022-10-19 17:10:36 +00:00
use OCP\IDBConnection;
2022-10-19 17:10:36 +00:00
trait TimelineQueryFaces
{
protected IDBConnection $connection;
2022-10-19 17:10:36 +00:00
public function transformFaceFilter(IQueryBuilder &$query, string $userId, string $faceStr)
{
2022-10-08 06:26:09 +00:00
// Get title 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');
}
2022-10-08 06:26:09 +00:00
$faceUid = $faceNames[0];
$faceName = $faceNames[1];
// Join with cluster
2022-10-18 21:08:27 +00:00
$nameField = is_numeric($faceName) ? 'rfc.id' : 'rfc.title';
$query->innerJoin('m', 'recognize_face_clusters', 'rfc', $query->expr()->andX(
2022-10-22 15:40:41 +00:00
$query->expr()->eq('rfc.user_id', $query->createNamedParameter($faceUid)),
2022-10-18 21:08:27 +00:00
$query->expr()->eq($nameField, $query->createNamedParameter($faceName)),
));
// Join with detections
$query->innerJoin('m', 'recognize_face_detections', 'rfd', $query->expr()->andX(
$query->expr()->eq('rfd.file_id', 'm.fileid'),
2022-10-18 21:08:27 +00:00
$query->expr()->eq('rfd.cluster_id', 'rfc.id'),
));
2022-10-18 21:08:27 +00:00
}
2022-10-19 17:10:36 +00:00
public function transformFaceRect(IQueryBuilder &$query, string $userId)
{
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',
);
}
2022-10-19 17:10:36 +00:00
public function getFaces(Folder $folder)
{
$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');
// 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
$query->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, true, false));
// GROUP by ID of face cluster
$query->groupBy('rfc.id');
// ORDER by number of faces in cluster
$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
// FETCH all faces
$faces = $query->executeQuery()->fetchAll();
// 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'];
2022-10-19 17:10:36 +00:00
$row['name'] = $row['title'];
unset($row['title']);
2022-10-19 17:15:14 +00:00
$row['count'] = (int) $row['count'];
}
return $faces;
}
2022-10-19 17:10:36 +00:00
public function getFacePreviewDetection(Folder &$folder, int $id)
{
$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');
2022-10-17 17:41:58 +00:00
$query->where($query->expr()->eq('rfd.cluster_id', $query->createNamedParameter($id)));
// 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
$query->innerJoin('m', 'filecache', 'f', $this->getFilecacheJoinQuery($query, $folder, true, false));
2022-10-17 17:41:58 +00:00
// LIMIT results
$query->setMaxResults(15);
// Sort by date taken so we get recent photos
$query->orderBy('m.datetaken', 'DESC');
$query->addOrderBy('m.fileid', 'DESC'); // tie-breaker
// FETCH face detections
$previews = $query->executeQuery()->fetchAll();
if (empty($previews)) {
return null;
}
// Score the face detections
foreach ($previews as &$p) {
// Get actual pixel size of face
2022-10-19 17:10:36 +00:00
$iw = min((int) ($p['image_width'] ?: 512), 2048);
$ih = min((int) ($p['image_height'] ?: 512), 2048);
2022-10-21 03:52:47 +00:00
$w = (float) $p['width'];
$h = (float) $p['height'];
2022-10-17 17:41:58 +00:00
// Get center of face
2022-10-19 17:15:14 +00:00
$x = (float) $p['x'] + (float) $p['width'] / 2;
$y = (float) $p['y'] + (float) $p['height'] / 2;
2022-10-17 17:41:58 +00:00
// 3D normal distribution - if the face is closer to the center, it's better
2022-10-19 17:10:36 +00:00
$positionScore = exp(-($x - 0.5) ** 2 * 4) * exp(-($y - 0.5) ** 2 * 4);
2022-10-17 17:41:58 +00:00
2022-10-21 03:52:47 +00:00
// Root size distribution - if the image is bigger, it's better,
// but it doesn't matter beyond a certain point
$imgSizeScore = ($iw * 100) ** (1 / 2) * ($ih * 100) ** (1 / 2);
// Faces occupying too much of the image don't look particularly good
2022-10-21 03:53:38 +00:00
$faceSizeScore = (-$w ** 2 + $w) * (-$h ** 2 + $h);
2022-10-17 17:41:58 +00:00
// Combine scores
2022-10-21 03:52:47 +00:00
$p['score'] = $positionScore * $imgSizeScore * $faceSizeScore;
}
2022-10-17 17:41:58 +00:00
// Sort previews by score descending
2022-10-19 17:10:36 +00:00
usort($previews, function ($a, $b) {
return $b['score'] <=> $a['score'];
2022-10-17 17:41:58 +00:00
});
return $previews;
}
2022-10-19 17:10:36 +00:00
/** Convert face fields to object */
private function processFace(&$row, $days = false)
{
if (!isset($row) || !isset($row['face_w'])) {
return;
}
if (!$days) {
$row['facerect'] = [
2022-10-19 17:15:14 +00:00
'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']);
}
}