Fix FaceRecognition integration doesn't work with postgresql database.

Following the discussion in #304, I tried this solution using the
strict mode of mariadb/mysql.

Split the Clusters and People queries to minimize the considerations
of both functions. Otherwise I had to add many nested ifs.

Also change to only show 15 clusters (as a small optimization) since
there is no point in showing everything, and it is better to show these
little by little.
pull/391/head
Matias De lellis 2023-02-02 22:28:59 -03:00
parent cf5ebf199d
commit 3b29328377
1 changed files with 109 additions and 54 deletions

View File

@ -61,63 +61,11 @@ trait TimelineQueryPeopleFaceRecognition
public function getPeopleFaceRecognition(TimelineRoot &$root, int $currentModel, bool $show_clusters = false, bool $show_singles = false, bool $show_hidden = false)
{
$query = $this->connection->getQueryBuilder();
// SELECT all face clusters
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'), 'count');
$query->select('frp.id', 'frp.user as user_id', 'frp.name', $count)->from('facerecog_persons', 'frp');
// WHERE there are faces with this cluster
$query->innerJoin('frp', 'facerecog_faces', 'frf', $query->expr()->eq('frp.id', 'frf.person'));
// WHERE faces are from images.
$query->innerJoin('frf', 'facerecog_images', 'fri', $query->expr()->eq('fri.id', 'frf.image'));
// WHERE these items are memories indexed photos
$query->innerJoin('fri', 'memories', 'm', $query->expr()->andX(
$query->expr()->eq('fri.file', 'm.fileid'),
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
));
// WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $root, true, false);
if ($show_clusters) {
// GROUP by ID of face cluster
$query->groupBy('frp.id');
$query->where($query->expr()->isNull('frp.name'));
} else {
// GROUP by name of face clusters
$query->groupBy('frp.name');
$query->where($query->expr()->isNotNull('frp.name'));
return $this->getFaceRecognitionClusters($root, $currentModel, $show_singles, $show_hidden);
}
// By default hides individual faces when they have no name.
if ($show_clusters && !$show_singles) {
$query->having($query->expr()->gt('count', $query->createNamedParameter(1)));
}
// By default it shows the people who were not hidden
if (!$show_hidden) {
$query->andWhere($query->expr()->eq('frp.is_visible', $query->createNamedParameter(true)));
}
// ORDER by number of faces in cluster
$query->orderBy('count', 'DESC');
$query->addOrderBy('name', 'ASC');
$query->addOrderBy('frp.id'); // tie-breaker
// FETCH all faces
$cursor = $this->executeQueryWithCTEs($query);
$faces = $cursor->fetchAll();
// Post process
foreach ($faces as &$row) {
$row['id'] = $row['name'] ?: (int) $row['id'];
$row['count'] = (int) $row['count'];
}
return $faces;
return $this->getFaceRecognitionPersons($root, $currentModel);
}
public function getFaceRecognitionPreview(TimelineRoot &$root, $currentModel, $previewId)
@ -214,6 +162,113 @@ trait TimelineQueryPeopleFaceRecognition
return $previews;
}
private function getFaceRecognitionClusters(TimelineRoot &$root, int $currentModel, bool $show_singles = false, bool $show_hidden = false)
{
$query = $this->connection->getQueryBuilder();
// SELECT all face clusters
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'));
$query->select('frp.id')->from('facerecog_persons', 'frp');
$query->selectAlias($count, 'count');
$query->selectAlias('frp.user', 'user_id');
// WHERE there are faces with this cluster
$query->innerJoin('frp', 'facerecog_faces', 'frf', $query->expr()->eq('frp.id', 'frf.person'));
// WHERE faces are from images.
$query->innerJoin('frf', 'facerecog_images', 'fri', $query->expr()->eq('fri.id', 'frf.image'));
// WHERE these items are memories indexed photos
$query->innerJoin('fri', 'memories', 'm', $query->expr()->andX(
$query->expr()->eq('fri.file', 'm.fileid'),
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
));
// WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $root, true, false);
// GROUP by ID of face cluster
$query->groupBy('frp.id');
$query->addGroupBy('frp.user');
$query->where($query->expr()->isNull('frp.name'));
// By default hides individual faces when they have no name.
if (!$show_singles) {
$query->having($count, $query->createNamedParameter(1));
}
// By default it shows the people who were not hidden
if (!$show_hidden) {
$query->andWhere($query->expr()->eq('frp.is_visible', $query->createNamedParameter(true)));
}
// ORDER by number of faces in cluster and id for response stability.
$query->orderBy('count', 'DESC');
$query->addOrderBy('frp.id', 'DESC');
// It is not worth displaying all unnamed clusters. We show 15 to name them progressively,
$query->setMaxResults(15);
// FETCH all faces
$cursor = $this->executeQueryWithCTEs($query);
$faces = $cursor->fetchAll();
// Post process
foreach ($faces as &$row) {
$row['id'] = (int) $row['id'];
$row['count'] = (int) $row['count'];
}
return $faces;
}
private function getFaceRecognitionPersons(TimelineRoot &$root, int $currentModel)
{
$query = $this->connection->getQueryBuilder();
// SELECT all face clusters
$count = $query->func()->count($query->createFunction('DISTINCT m.fileid'));
$query->select('frp.name')->from('facerecog_persons', 'frp');
$query->selectAlias($count, 'count');
$query->selectAlias('frp.user', 'user_id');
// WHERE there are faces with this cluster
$query->innerJoin('frp', 'facerecog_faces', 'frf', $query->expr()->eq('frp.id', 'frf.person'));
// WHERE faces are from images.
$query->innerJoin('frf', 'facerecog_images', 'fri', $query->expr()->eq('fri.id', 'frf.image'));
// WHERE these items are memories indexed photos
$query->innerJoin('fri', 'memories', 'm', $query->expr()->andX(
$query->expr()->eq('fri.file', 'm.fileid'),
$query->expr()->eq('fri.model', $query->createNamedParameter($currentModel)),
));
// WHERE these photos are in the user's requested folder recursively
$query = $this->joinFilecache($query, $root, true, false);
// GROUP by name of face clusters
$query->where($query->expr()->isNotNull('frp.name'));
$query->groupBy('frp.user');
$query->addGroupBy('frp.name');
// ORDER by number of faces in cluster
$query->orderBy('count', 'DESC');
$query->addOrderBy('frp.name', 'ASC');
// FETCH all faces
$cursor = $this->executeQueryWithCTEs($query);
$faces = $cursor->fetchAll();
// Post process
foreach ($faces as &$row) {
$row['id'] = $row['name'];
$row['count'] = (int) $row['count'];
}
return $faces;
}
/** Convert face fields to object */
private function processFaceRecognitionDetection(&$row, $days = false)
{