Issues (125)

lib/Db/FaceMapper.php (10 issues)

1
<?php
2
/**
3
 * @copyright Copyright (c) 2017-2020, Matias De lellis <[email protected]>
4
 * @copyright Copyright (c) 2018-2019, Branko Kokanovic <[email protected]>
5
 *
6
 * @author Matias De lellis <[email protected]>
7
 *
8
 * @license GNU AGPL version 3 or any later version
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License as
12
 * published by the Free Software Foundation, either version 3 of the
13
 * License, or (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 *
23
 */
24
25
namespace OCA\FaceRecognition\Db;
26
27
use OC\DB\QueryBuilder\Literal;
0 ignored issues
show
The type OC\DB\QueryBuilder\Literal was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
28
29
use OCP\IDBConnection;
30
use OCP\AppFramework\Db\QBMapper;
31
use OCP\AppFramework\Db\DoesNotExistException;
32
use OCP\DB\QueryBuilder\IQueryBuilder;
33
34
class FaceMapper extends QBMapper {
35
36 1
	public function __construct(IDBConnection $db) {
37 1
		parent::__construct($db, 'facerecog_faces', '\OCA\FaceRecognition\Db\Face');
38
	}
39
40 1
	public function find (int $faceId): ?Face {
41 1
		$qb = $this->db->getQueryBuilder();
42 1
		$qb->select('id', 'image', 'person', 'x', 'y', 'width', 'height', 'landmarks', 'descriptor', 'confidence')
43 1
			->from($this->getTableName(), 'f')
44 1
			->where($qb->expr()->eq('id', $qb->createNamedParameter($faceId)));
45
		try {
46 1
			return $this->findEntity($qb);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->findEntity($qb) returns the type OCP\AppFramework\Db\Entity which includes types incompatible with the type-hinted return OCA\FaceRecognition\Db\Face|null.
Loading history...
47
		} catch (DoesNotExistException $e) {
48
			return null;
49
		}
50
	}
51
52 1
	public function findDescriptorsBathed (array $faceIds): array {
53 1
		$qb = $this->db->getQueryBuilder();
54 1
		$qb->select('id', 'descriptor')
55 1
			->from($this->getTableName(), 'f')
56 1
			->where($qb->expr()->in('id', $qb->createParameter('face_ids')));
57
58 1
		$qb->setParameter('face_ids', $faceIds, IQueryBuilder::PARAM_INT_ARRAY);
59
60 1
		$descriptors = [];
61 1
		$result = $qb->executeQuery();
62 1
		while ($row = $result->fetch()) {
63 1
			$descriptors[] = [
64 1
				'id' => $row['id'],
65 1
				'descriptor' => json_decode($row['descriptor'])
66 1
			];
67
		}
68 1
		$result->closeCursor();
69
70 1
		return $descriptors;
71
	}
72
73
	/**
74
	 * Based on a given fileId, takes all faces that belong to that file
75
	 * and return an array with that.
76
	 *
77
	 * @param string $userId ID of the user that faces belong to
78
	 * @param int $modelId ID of the model that faces belgon to
79
	 * @param int $fileId ID of file for which to search faces.
80
	 *
81
	 * @return Face[]
82
	 */
83
	public function findFromFile(string $userId, int $modelId, int $fileId): array {
84
		$qb = $this->db->getQueryBuilder();
85
		$qb->select('f.id', 'x', 'y', 'width', 'height', 'person', 'confidence', 'creation_time')
86
			->from($this->getTableName(), 'f')
87
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
88
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
89
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model_id')))
90
			->andWhere($qb->expr()->eq('file', $qb->createParameter('file_id')))
91
			->setParameter('user_id', $userId)
92
			->setParameter('model_id', $modelId)
93
			->setParameter('file_id', $fileId)
94
			->orderBy('confidence', 'DESC');
95
		return $this->findEntities($qb);
96
	}
97
98
	/**
99
	 * Counts all the faces that belong to images of a given user, created using given model
100
	 *
101
	 * @param string $userId User to which faces and associated images belongs to
102
	 * @param int $model Model ID
103
	 * @param bool $onlyWithoutPersons True if we need to count only faces which are not having person associated for it.
104
	 * If false, all faces are counted.
105
	 */
106 14
	public function countFaces(string $userId, int $model, bool $onlyWithoutPersons=false): int {
107 14
		$qb = $this->db->getQueryBuilder();
108 14
		$qb = $qb
109 14
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('f.id') . ')'))
110 14
			->from($this->getTableName(), 'f')
111 14
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
112 14
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
113 14
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')));
114 14
		if ($onlyWithoutPersons) {
115
			$qb = $qb->andWhere($qb->expr()->isNull('person'));
116
		}
117 14
		$query = $qb
118 14
			->setParameter('user', $userId)
119 14
			->setParameter('model', $model);
120 14
		$resultStatement = $query->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

120
		$resultStatement = /** @scrutinizer ignore-deprecated */ $query->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
121 14
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
122 14
		$resultStatement->closeCursor();
123
124 14
		return (int)$data[0];
125
	}
126
127
	/**
128
	 * Gets oldest created face from database, for a given user and model, that is not associated with a person.
129
	 *
130
	 * @param string $userId User to which faces and associated images belongs to
131
	 * @param int $model Model ID
132
	 *
133
	 * @return Face Oldest face, if any is found
134
	 * @throws DoesNotExistException If there is no faces in database without person for a given user and model.
135
	 */
136
	public function getOldestCreatedFaceWithoutPerson(string $userId, int $model) {
137
		$qb = $this->db->getQueryBuilder();
138
		$qb
139
			->select('f.id', 'f.creation_time')
140
			->from($this->getTableName(), 'f')
141
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
142
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
143
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
144
			->andWhere($qb->expr()->isNull('person'))
145
			->orderBy('f.creation_time', 'ASC');
146
		$cursor = $qb->execute();
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

146
		$cursor = /** @scrutinizer ignore-deprecated */ $qb->execute();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
147
		$row = $cursor->fetch();
148
		if($row === false) {
149
			$cursor->closeCursor();
150
			throw new DoesNotExistException("No faces found and we should have at least one");
151
		}
152
		$face = $this->mapRowToEntity($row);
153
		$cursor->closeCursor();
154
		return $face;
155
	}
156
157 17
	public function getFaces(string $userId, int $model): array {
158 17
		$qb = $this->db->getQueryBuilder();
159 17
		$qb->select('f.id', 'f.person', 'f.x', 'f.y', 'f.width', 'f.height', 'f.confidence', 'f.descriptor', 'f.is_groupable')
160 17
			->from($this->getTableName(), 'f')
161 17
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
162 17
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
163 17
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
164 17
			->setParameter('user', $userId)
165 17
			->setParameter('model', $model);
166 17
		return $this->findEntities($qb);
167
	}
168
169 1
	public function getGroupableFaces(string $userId, int $model, int $minSize, float $minConfidence): array {
170 1
		$qb = $this->db->getQueryBuilder();
171 1
		$qb->select('f.id', 'f.person')
172 1
			->from($this->getTableName(), 'f')
173 1
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
174 1
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
175 1
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
176 1
			->andWhere($qb->expr()->gte('width', $qb->createParameter('min_size')))
177 1
			->andWhere($qb->expr()->gte('height', $qb->createParameter('min_size')))
178 1
			->andWhere($qb->expr()->gte('confidence', $qb->createParameter('min_confidence')))
179 1
			->andWhere($qb->expr()->eq('is_groupable', $qb->createParameter('is_groupable')))
180 1
			->setParameter('user', $userId)
181 1
			->setParameter('model', $model)
182 1
			->setParameter('min_size', $minSize)
183 1
			->setParameter('min_confidence', $minConfidence)
184 1
			->setParameter('is_groupable', true, IQueryBuilder::PARAM_BOOL);
185
186 1
		$result = $qb->executeQuery();
187 1
		$rows = $result->fetchAll();
188 1
		$result->closeCursor();
189
190 1
		return $rows;
191
	}
192
193 1
	public function getNonGroupableFaces(string $userId, int $model, int $minSize, float $minConfidence): array {
194 1
		$qb = $this->db->getQueryBuilder();
195 1
		$qb->select('f.id', 'f.person')
196 1
			->from($this->getTableName(), 'f')
197 1
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
198 1
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
199 1
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
200 1
			->andWhere($qb->expr()->orX(
201 1
				$qb->expr()->lt('width', $qb->createParameter('min_size')),
202 1
				$qb->expr()->lt('height', $qb->createParameter('min_size')),
203 1
				$qb->expr()->lt('confidence', $qb->createParameter('min_confidence')),
204 1
				$qb->expr()->eq('is_groupable', $qb->createParameter('is_groupable'))
205 1
			))
206 1
			->setParameter('user', $userId)
207 1
			->setParameter('model', $model)
208 1
			->setParameter('min_size', $minSize)
209 1
			->setParameter('min_confidence', $minConfidence)
210 1
			->setParameter('is_groupable', false, IQueryBuilder::PARAM_BOOL);
211
212 1
		$result = $qb->executeQuery();
213 1
		$rows = $result->fetchAll();
214 1
		$result->closeCursor();
215
216 1
		return $rows;
217
	}
218
219
	/**
220
	 * @param int|null $limit
221
	 */
222 13
	public function findFromCluster(string $userId, int $clusterId, int $model, ?int $limit = null, $offset = null): array {
223 13
		$qb = $this->db->getQueryBuilder();
224 13
		$qb->select('f.id', 'f.image', 'f.person')
225 13
			->from($this->getTableName(), 'f')
226 13
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
227 13
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
228 13
			->andWhere($qb->expr()->eq('person', $qb->createNamedParameter($clusterId)))
229 13
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)));
230
231 13
		$qb->setMaxResults($limit);
232 13
		$qb->setFirstResult($offset);
233
234 13
		$faces = $this->findEntities($qb);
235 13
		return $faces;
236
	}
237
238
	/**
239
	 * @param int|null $limit
240
	 */
241
	public function findFromPerson(string $userId, string $personId, int $model, ?int $limit = null, $offset = null): array {
242
		$qb = $this->db->getQueryBuilder();
243
		$qb->select('f.id')
244
			->from($this->getTableName(), 'f')
245
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
246
			->innerJoin('f', 'facerecog_persons' ,'p', $qb->expr()->eq('f.person', 'p.id'))
247
			->where($qb->expr()->eq('p.user', $qb->createNamedParameter($userId)))
248
			->andWhere($qb->expr()->eq('name', $qb->createNamedParameter($personId)))
249
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
250
			->orderBy('i.file', 'DESC');
251
252
		$qb->setMaxResults($limit);
253
		$qb->setFirstResult($offset);
254
255
		$faces = $this->findEntities($qb);
256
257
		return $faces;
258
	}
259
260
	/**
261
	 * Finds all faces contained in one image
262
	 * Note that this is independent of any Model
263
	 *
264
	 * @param int $imageId Image for which to find all faces for
265
	 *
266
	 */
267
	public function findByImage(int $imageId): array {
268
		$qb = $this->db->getQueryBuilder();
269
		$qb->select('id', 'image', 'person')
270
			->from($this->getTableName())
271
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)));
272
		$faces = $this->findEntities($qb);
273
		return $faces;
274
	}
275
276
	/**
277
	 * Removes all faces contained in one image.
278
	 * Note that this is independent of any Model
279
	 *
280
	 * @param int $imageId Image for which to delete faces for
281
	 *
282
	 * @return void
283
	 */
284 2
	public function removeFromImage(int $imageId): void {
285 2
		$qb = $this->db->getQueryBuilder();
286 2
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

286
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
287 2
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)))
288 2
			->execute();
289
	}
290
291
	/**
292
	 * Deletes all faces from that user.
293
	 *
294
	 * @param string $userId User to drop faces from table.
295
	 *
296
	 * @return void
297
	 */
298 28
	public function deleteUserFaces(string $userId): void {
299 28
		$sub = $this->db->getQueryBuilder();
300 28
		$sub->select(new Literal('1'));
301 28
		$sub->from('facerecog_images', 'i')
302 28
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
303 28
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
304
305 28
		$qb = $this->db->getQueryBuilder();
306 28
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

306
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
307 28
			->where('EXISTS (' . $sub->getSQL() . ')')
308 28
			->setParameter('user', $userId)
309 28
			->execute();
310
	}
311
312
	/**
313
	 * Deletes all faces from that user and model
314
	 *
315
	 * @param string $userId User to drop faces from table.
316
	 * @param int $modelId model to drop faces from table.
317
	 *
318
	 * @return void
319
	 */
320
	public function deleteUserModel(string $userId, $modelId): void {
321
		$sub = $this->db->getQueryBuilder();
322
		$sub->select(new Literal('1'));
323
		$sub->from('facerecog_images', 'i')
324
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
325
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')))
326
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')));
327
328
		$qb = $this->db->getQueryBuilder();
329
		$qb->delete($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

329
		/** @scrutinizer ignore-deprecated */ $qb->delete($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
330
			->where('EXISTS (' . $sub->getSQL() . ')')
331
			->setParameter('user', $userId)
332
			->setParameter('model', $modelId)
333
			->execute();
334
	}
335
336
	/**
337
	 * Unset relation beetwen faces and persons from that user in order to reset clustering
338
	 *
339
	 * @param string $userId User to drop fo unset relation.
340
	 *
341
	 * @return void
342
	 */
343
	public function unsetPersonsRelationForUser(string $userId, int $model): void {
344
		$sub = $this->db->getQueryBuilder();
345
		$sub->select(new Literal('1'));
346
		$sub->from('facerecog_images', 'i')
347
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
348
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')))
349
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
350
351
		$qb = $this->db->getQueryBuilder();
352
		$qb->update($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

352
		/** @scrutinizer ignore-deprecated */ $qb->update($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
353
			->set("person", $qb->createNamedParameter(null))
354
			->where('EXISTS (' . $sub->getSQL() . ')')
355
			->setParameter('model', $model)
356
			->setParameter('user', $userId)
357
			->execute();
358
	}
359
360
	/**
361
	 * Insert one face to database.
362
	 * Note: only reason we are not using (idiomatic) QBMapper method is
363
	 * because "QueryBuilder::PARAM_DATE" cannot be set there
364
	 *
365
	 * @param Face $face Face to insert
366
	 * @param IDBConnection $db Existing connection, if we need to reuse it. Null if we commit immediatelly.
367
	 *
368
	 * @return Face
369
	 */
370 15
	public function insertFace(Face $face, IDBConnection $db = null): Face {
371 15
		if ($db !== null) {
372 1
			$qb = $db->getQueryBuilder();
373
		} else {
374 14
			$qb = $this->db->getQueryBuilder();
375
		}
376
377 15
		$qb->insert($this->getTableName())
0 ignored issues
show
Deprecated Code introduced by
The function OCP\DB\QueryBuilder\IQueryBuilder::execute() has been deprecated: 22.0.0 Use executeQuery or executeStatement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

377
		/** @scrutinizer ignore-deprecated */ $qb->insert($this->getTableName())

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
378 15
			->values([
379 15
				'image' => $qb->createNamedParameter($face->image),
380 15
				'person' => $qb->createNamedParameter($face->person),
381 15
				'x' => $qb->createNamedParameter($face->x),
382 15
				'y' => $qb->createNamedParameter($face->y),
383 15
				'width' => $qb->createNamedParameter($face->width),
384 15
				'height' => $qb->createNamedParameter($face->height),
385 15
				'confidence' => $qb->createNamedParameter($face->confidence),
386 15
				'landmarks' => $qb->createNamedParameter(json_encode($face->landmarks)),
387 15
				'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)),
388 15
				'creation_time' => $qb->createNamedParameter($face->creationTime, IQueryBuilder::PARAM_DATE),
0 ignored issues
show
OCP\DB\QueryBuilder\IQueryBuilder::PARAM_DATE of type string is incompatible with the type OCP\DB\QueryBuilder\IQueryBuilder expected by parameter $type of OCP\DB\QueryBuilder\IQue...:createNamedParameter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

388
				'creation_time' => $qb->createNamedParameter($face->creationTime, /** @scrutinizer ignore-type */ IQueryBuilder::PARAM_DATE),
Loading history...
389 15
			])
390 15
			->execute();
391
392 15
		$face->setId($qb->getLastInsertId());
393
394 15
		return $face;
395
	}
396
}