Passed
Push — bathV2.1 ( 221ea5...35eb45 )
by Matias
04:45
created

FaceMapper   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Test Coverage

Coverage 61.5%

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 197
c 4
b 0
f 0
dl 0
loc 349
ccs 131
cts 213
cp 0.615
rs 10
wmc 21

17 Methods

Rating   Name   Duplication   Size   Complexity  
A find() 0 9 2
A __construct() 0 2 1
A countFaces() 0 19 2
A getOldestCreatedFaceWithoutPerson() 0 19 2
A findFromFile() 0 13 1
A getFaces() 0 10 1
A findDescriptorsBathed() 0 7 1
A deleteUserModel() 0 14 1
A findFromCluster() 0 14 1
A deleteUserFaces() 0 12 1
A findFromPerson() 0 17 1
A unsetPersonsRelationForUser() 0 15 1
A getGroupableFaces() 0 22 1
A findByImage() 0 7 1
A getNonGroupableFaces() 0 24 1
A insertFace() 0 25 2
A removeFromImage() 0 5 1
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
Bug introduced by
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 1
		$qb->setParameter('face_ids', $faceIds, IQueryBuilder::PARAM_INT_ARRAY);
58 1
		return $this->findEntities($qb);
59
	}
60
61
	/**
62
	 * Based on a given fileId, takes all faces that belong to that file
63
	 * and return an array with that.
64
	 *
65
	 * @param string $userId ID of the user that faces belong to
66
	 * @param int $modelId ID of the model that faces belgon to
67
	 * @param int $fileId ID of file for which to search faces.
68
	 *
69
	 * @return Face[]
70
	 */
71
	public function findFromFile(string $userId, int $modelId, int $fileId): array {
72
		$qb = $this->db->getQueryBuilder();
73
		$qb->select('f.id', 'x', 'y', 'width', 'height', 'person', 'confidence', 'creation_time')
74
			->from($this->getTableName(), 'f')
75
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
76
			->where($qb->expr()->eq('i.user', $qb->createParameter('user_id')))
77
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model_id')))
78
			->andWhere($qb->expr()->eq('file', $qb->createParameter('file_id')))
79
			->setParameter('user_id', $userId)
80
			->setParameter('model_id', $modelId)
81
			->setParameter('file_id', $fileId)
82
			->orderBy('confidence', 'DESC');
83
		return $this->findEntities($qb);
84
	}
85
86
	/**
87
	 * Counts all the faces that belong to images of a given user, created using given model
88
	 *
89
	 * @param string $userId User to which faces and associated images belongs to
90
	 * @param int $model Model ID
91
	 * @param bool $onlyWithoutPersons True if we need to count only faces which are not having person associated for it.
92
	 * If false, all faces are counted.
93
	 */
94 14
	public function countFaces(string $userId, int $model, bool $onlyWithoutPersons=false): int {
95 14
		$qb = $this->db->getQueryBuilder();
96 14
		$qb = $qb
97 14
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('f.id') . ')'))
98 14
			->from($this->getTableName(), 'f')
99 14
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
100 14
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
101 14
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')));
102 14
		if ($onlyWithoutPersons) {
103
			$qb = $qb->andWhere($qb->expr()->isNull('person'));
104
		}
105 14
		$query = $qb
106 14
			->setParameter('user', $userId)
107 14
			->setParameter('model', $model);
108 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

108
		$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...
109 14
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
110 14
		$resultStatement->closeCursor();
111
112 14
		return (int)$data[0];
113
	}
114
115
	/**
116
	 * Gets oldest created face from database, for a given user and model, that is not associated with a person.
117
	 *
118
	 * @param string $userId User to which faces and associated images belongs to
119
	 * @param int $model Model ID
120
	 *
121
	 * @return Face Oldest face, if any is found
122
	 * @throws DoesNotExistException If there is no faces in database without person for a given user and model.
123
	 */
124
	public function getOldestCreatedFaceWithoutPerson(string $userId, int $model) {
125
		$qb = $this->db->getQueryBuilder();
126
		$qb
127
			->select('f.id', 'f.creation_time')
128
			->from($this->getTableName(), 'f')
129
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
130
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
131
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
132
			->andWhere($qb->expr()->isNull('person'))
133
			->orderBy('f.creation_time', 'ASC');
134
		$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

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

274
		/** @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...
275 2
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)))
276 2
			->execute();
277
	}
278
279
	/**
280
	 * Deletes all faces from that user.
281
	 *
282
	 * @param string $userId User to drop faces from table.
283
	 *
284
	 * @return void
285
	 */
286 28
	public function deleteUserFaces(string $userId): void {
287 28
		$sub = $this->db->getQueryBuilder();
288 28
		$sub->select(new Literal('1'));
289 28
		$sub->from('facerecog_images', 'i')
290 28
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
291 28
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
292
293 28
		$qb = $this->db->getQueryBuilder();
294 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

294
		/** @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...
295 28
			->where('EXISTS (' . $sub->getSQL() . ')')
296 28
			->setParameter('user', $userId)
297 28
			->execute();
298
	}
299
300
	/**
301
	 * Deletes all faces from that user and model
302
	 *
303
	 * @param string $userId User to drop faces from table.
304
	 * @param int $modelId model to drop faces from table.
305
	 *
306
	 * @return void
307
	 */
308
	public function deleteUserModel(string $userId, $modelId): void {
309
		$sub = $this->db->getQueryBuilder();
310
		$sub->select(new Literal('1'));
311
		$sub->from('facerecog_images', 'i')
312
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
313
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')))
314
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')));
315
316
		$qb = $this->db->getQueryBuilder();
317
		$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

317
		/** @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...
318
			->where('EXISTS (' . $sub->getSQL() . ')')
319
			->setParameter('user', $userId)
320
			->setParameter('model', $modelId)
321
			->execute();
322
	}
323
324
	/**
325
	 * Unset relation beetwen faces and persons from that user in order to reset clustering
326
	 *
327
	 * @param string $userId User to drop fo unset relation.
328
	 *
329
	 * @return void
330
	 */
331
	public function unsetPersonsRelationForUser(string $userId, int $model): void {
332
		$sub = $this->db->getQueryBuilder();
333
		$sub->select(new Literal('1'));
334
		$sub->from('facerecog_images', 'i')
335
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
336
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')))
337
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
338
339
		$qb = $this->db->getQueryBuilder();
340
		$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

340
		/** @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...
341
			->set("person", $qb->createNamedParameter(null))
342
			->where('EXISTS (' . $sub->getSQL() . ')')
343
			->setParameter('model', $model)
344
			->setParameter('user', $userId)
345
			->execute();
346
	}
347
348
	/**
349
	 * Insert one face to database.
350
	 * Note: only reason we are not using (idiomatic) QBMapper method is
351
	 * because "QueryBuilder::PARAM_DATE" cannot be set there
352
	 *
353
	 * @param Face $face Face to insert
354
	 * @param IDBConnection $db Existing connection, if we need to reuse it. Null if we commit immediatelly.
355
	 *
356
	 * @return Face
357
	 */
358 15
	public function insertFace(Face $face, IDBConnection $db = null): Face {
359 15
		if ($db !== null) {
360 1
			$qb = $db->getQueryBuilder();
361
		} else {
362 14
			$qb = $this->db->getQueryBuilder();
363
		}
364
365 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

365
		/** @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...
366 15
			->values([
367 15
				'image' => $qb->createNamedParameter($face->image),
368 15
				'person' => $qb->createNamedParameter($face->person),
369 15
				'x' => $qb->createNamedParameter($face->x),
370 15
				'y' => $qb->createNamedParameter($face->y),
371 15
				'width' => $qb->createNamedParameter($face->width),
372 15
				'height' => $qb->createNamedParameter($face->height),
373 15
				'confidence' => $qb->createNamedParameter($face->confidence),
374 15
				'landmarks' => $qb->createNamedParameter(json_encode($face->landmarks)),
375 15
				'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)),
376 15
				'creation_time' => $qb->createNamedParameter($face->creationTime, IQueryBuilder::PARAM_DATE),
0 ignored issues
show
Bug introduced by
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

376
				'creation_time' => $qb->createNamedParameter($face->creationTime, /** @scrutinizer ignore-type */ IQueryBuilder::PARAM_DATE),
Loading history...
377 15
			])
378 15
			->execute();
379
380 15
		$face->setId($qb->getLastInsertId());
381
382 15
		return $face;
383
	}
384
}