Passed
Pull Request — master (#616)
by Matias
05:31 queued 03:30
created

FaceMapper   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 330
Duplicated Lines 0 %

Test Coverage

Coverage 50%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 185
dl 0
loc 330
ccs 82
cts 164
cp 0.5
rs 10
c 2
b 0
f 0
wmc 20

16 Methods

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

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

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

255
		/** @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...
256
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)))
257
			->execute();
258
	}
259
260
	/**
261
	 * Deletes all faces from that user.
262
	 *
263
	 * @param string $userId User to drop faces from table.
264
	 *
265
	 * @return void
266
	 */
267
	public function deleteUserFaces(string $userId): void {
268
		$sub = $this->db->getQueryBuilder();
269
		$sub->select(new Literal('1'));
270
		$sub->from('facerecog_images', 'i')
271
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
272
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
273
274
		$qb = $this->db->getQueryBuilder();
275
		$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

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

298
		/** @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...
299 15
			->where('EXISTS (' . $sub->getSQL() . ')')
300 15
			->setParameter('user', $userId)
301 1
			->setParameter('model', $modelId)
302
			->execute();
303 14
	}
304
305
	/**
306 15
	 * Unset relation beetwen faces and persons from that user in order to reset clustering
307 15
	 *
308 15
	 * @param string $userId User to drop fo unset relation.
309 15
	 *
310 15
	 * @return void
311 15
	 */
312 15
	public function unsetPersonsRelationForUser(string $userId, int $model): void {
313 15
		$sub = $this->db->getQueryBuilder();
314 15
		$sub->select(new Literal('1'));
315 15
		$sub->from('facerecog_images', 'i')
316 15
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
317 15
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')))
318 15
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
319 15
320
		$qb = $this->db->getQueryBuilder();
321 15
		$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

321
		/** @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...
322
			->set("person", $qb->createNamedParameter(null))
323 15
			->where('EXISTS (' . $sub->getSQL() . ')')
324
			->setParameter('model', $model)
325
			->setParameter('user', $userId)
326
			->execute();
327
	}
328
329
	/**
330
	 * Insert one face to database.
331
	 * Note: only reason we are not using (idiomatic) QBMapper method is
332
	 * because "QueryBuilder::PARAM_DATE" cannot be set there
333
	 *
334
	 * @param Face $face Face to insert
335
	 * @param IDBConnection $db Existing connection, if we need to reuse it. Null if we commit immediatelly.
336
	 *
337
	 * @return Face
338
	 */
339
	public function insertFace(Face $face, IDBConnection $db = null): Face {
340
		if ($db !== null) {
341
			$qb = $db->getQueryBuilder();
342
		} else {
343
			$qb = $this->db->getQueryBuilder();
344
		}
345
346
		$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

346
		/** @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...
347
			->values([
348
				'image' => $qb->createNamedParameter($face->image),
349
				'person' => $qb->createNamedParameter($face->person),
350
				'x' => $qb->createNamedParameter($face->x),
351
				'y' => $qb->createNamedParameter($face->y),
352
				'width' => $qb->createNamedParameter($face->width),
353
				'height' => $qb->createNamedParameter($face->height),
354
				'confidence' => $qb->createNamedParameter($face->confidence),
355
				'landmarks' => $qb->createNamedParameter(json_encode($face->landmarks)),
356
				'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)),
357
				'creation_time' => $qb->createNamedParameter($face->creationTime, IQueryBuilder::PARAM_DATE),
358
			])
359
			->execute();
360
361
		$face->setId($qb->getLastInsertId());
362
363
		return $face;
364
	}
365
}