Passed
Pull Request — master (#232)
by Matias
05:40 queued 04:10
created

FaceMapper   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 223
Duplicated Lines 0 %

Test Coverage

Coverage 58.16%

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 130
c 10
b 0
f 0
dl 0
loc 223
ccs 82
cts 141
cp 0.5816
rs 10
wmc 15

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 2 1
A getPersonOnFile() 0 19 1
A countFaces() 0 19 2
A getOldestCreatedFaceWithoutPerson() 0 19 2
A find() 0 6 1
A getFaces() 0 10 1
A deleteUserFaces() 0 12 1
A unsetPersonsRelationForUser() 0 15 1
A findByImage() 0 7 1
A findFacesFromPerson() 0 14 1
A removeFromImage() 0 5 1
A insertFace() 0 25 2
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 1
	public function __construct(IDBConnection $db) {
36 1
		parent::__construct($db, 'facerecog_faces', '\OCA\FaceRecognition\Db\Face');
37 1
	}
38
39 1
	public function find (int $faceId) {
40 1
		$qb = $this->db->getQueryBuilder();
41 1
		$qb->select('id', 'image', 'person', 'left', 'right', 'top', 'bottom', 'descriptor')
42 1
			->from($this->getTableName(), 'f')
43 1
			->andWhere($qb->expr()->eq('id', $qb->createNamedParameter($faceId)));
44 1
		return $this->findEntity($qb);
45
	}
46
47
	/**
48
	 * Counts all the faces that belong to images of a given user, created using given model
49
	 *
50
	 * @param string $userId User to which faces and associated images belongs to
51
	 * @param int $model Model ID
52
	 * @param bool $onlyWithoutPersons True if we need to count only faces which are not having person associated for it.
53
	 * If false, all faces are counted.
54
	 */
55 14
	public function countFaces(string $userId, int $model, bool $onlyWithoutPersons=false): int {
56 14
		$qb = $this->db->getQueryBuilder();
57
		$qb = $qb
58 14
			->select($qb->createFunction('COUNT(' . $qb->getColumnName('f.id') . ')'))
59 14
			->from($this->getTableName(), 'f')
60 14
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
61 14
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
62 14
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')));
63 14
		if ($onlyWithoutPersons) {
64
			$qb = $qb->andWhere($qb->expr()->isNull('person'));
65
		}
66
		$query = $qb
67 14
			->setParameter('user', $userId)
68 14
			->setParameter('model', $model);
69 14
		$resultStatement = $query->execute();
70 14
		$data = $resultStatement->fetch(\PDO::FETCH_NUM);
71 14
		$resultStatement->closeCursor();
72
73 14
		return (int)$data[0];
74
	}
75
76
	/**
77
	 * Gets oldest created face from database, for a given user and model, that is not associated with a person.
78
	 *
79
	 * @param string $userId User to which faces and associated images belongs to
80
	 * @param int $model Model ID
81
	 *
82
	 * @return Face Oldest face, if any is found
83
	 * @throws DoesNotExistException If there is no faces in database without person for a given user and model.
84
	 */
85
	public function getOldestCreatedFaceWithoutPerson(string $userId, int $model) {
86
		$qb = $this->db->getQueryBuilder();
87
		$qb
88
			->select('f.id', 'f.creation_time')
89
			->from($this->getTableName(), 'f')
90
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
91
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
92
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)))
93
			->andWhere($qb->expr()->isNull('person'))
94
			->orderBy('f.creation_time', 'ASC');
95
		$cursor = $qb->execute();
96
		$row = $cursor->fetch();
97
		if($row === false) {
98
			$cursor->closeCursor();
99
			throw new DoesNotExistException("No faces found and we should have at least one");
100
		}
101
		$face = $this->mapRowToEntity($row);
102
		$cursor->closeCursor();
103
		return $face;
104
	}
105
106 17
	public function getFaces(string $userId, $model): array {
107 17
		$qb = $this->db->getQueryBuilder();
108 17
		$qb->select('f.id', 'f.person', 'f.confidence', 'f.descriptor')
109 17
			->from($this->getTableName(), 'f')
110 17
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
111 17
			->where($qb->expr()->eq('user', $qb->createParameter('user')))
112 17
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
113 17
			->setParameter('user', $userId)
114 17
			->setParameter('model', $model);
115 17
		return $this->findEntities($qb);
116
	}
117
118 13
	public function findFacesFromPerson(string $userId, int $personId, int $model, $limit = null, $offset = null): array {
119 13
		$qb = $this->db->getQueryBuilder();
120 13
		$qb->select('f.id', 'f.image', 'f.person')
121 13
			->from($this->getTableName(), 'f')
122 13
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
123 13
			->where($qb->expr()->eq('user', $qb->createNamedParameter($userId)))
124 13
			->andWhere($qb->expr()->eq('person', $qb->createNamedParameter($personId)))
125 13
			->andWhere($qb->expr()->eq('model', $qb->createNamedParameter($model)));
126
127 13
		$qb->setMaxResults($limit);
128 13
		$qb->setFirstResult($offset);
129
130 13
		$faces = $this->findEntities($qb);
131 13
		return $faces;
132
	}
133
134
	public function getPersonOnFile(string $userId, int $personId, int $fileId, int $model): array {
135
		$qb = $this->db->getQueryBuilder();
136
		$qb->select('f.id', 'left', 'right', 'top', 'bottom')
137
			->from($this->getTableName(), 'f')
138
			->innerJoin('f', 'facerecog_persons' ,'p', $qb->expr()->eq('f.person', 'p.id'))
139
			->innerJoin('f', 'facerecog_images' ,'i', $qb->expr()->eq('f.image', 'i.id'))
140
			->where($qb->expr()->eq('p.user', $qb->createParameter('user')))
141
			->andWhere($qb->expr()->eq('person', $qb->createParameter('person')))
142
			->andWhere($qb->expr()->eq('file', $qb->createParameter('file_id')))
143
			->andWhere($qb->expr()->eq('model', $qb->createParameter('model')))
144
			->andWhere($qb->expr()->eq('p.is_valid', $qb->createParameter('is_valid')))
145
			->setParameter('user', $userId)
146
			->setParameter('person', $personId)
147
			->setParameter('file_id', $fileId)
148
			->setParameter('model', $model)
149
			->setParameter('is_valid', true)
150
			->orderBy('confidence', 'DESC');
151
		$faces = $this->findEntities($qb);
152
		return $faces;
153
	}
154
155
	/**
156
	 * Finds all faces contained in one image
157
	 * Note that this is independent of any Model
158
	 *
159
	 * @param int $imageId Image for which to find all faces for
160
	 */
161
	public function findByImage(int $imageId) {
162
		$qb = $this->db->getQueryBuilder();
163
		$qb->select('id', 'image', 'person')
164
			->from($this->getTableName())
165
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)));
166
		$faces = $this->findEntities($qb);
167
		return $faces;
168
	}
169
170
	/**
171
	 * Removes all faces contained in one image.
172
	 * Note that this is independent of any Model
173
	 *
174
	 * @param int $imageId Image for which to delete faces for
175
	 */
176 2
	public function removeFromImage(int $imageId) {
177 2
		$qb = $this->db->getQueryBuilder();
178 2
		$qb->delete($this->getTableName())
179 2
			->where($qb->expr()->eq('image', $qb->createNamedParameter($imageId)))
180 2
			->execute();
181 2
	}
182
183
	/**
184
	 * Deletes all faces from that user.
185
	 *
186
	 * @param string $userId User to drop faces from table.
187
	 */
188 28
	public function deleteUserFaces(string $userId) {
189 28
		$sub = $this->db->getQueryBuilder();
190 28
		$sub->select(new Literal('1'));
191 28
		$sub->from('facerecog_images', 'i')
192 28
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
193 28
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
194
195 28
		$qb = $this->db->getQueryBuilder();
196 28
		$qb->delete($this->getTableName())
197 28
			->where('EXISTS (' . $sub->getSQL() . ')')
198 28
			->setParameter('user', $userId)
199 28
			->execute();
200 28
	}
201
202
	/**
203
	 * Unset relation beetwen faces and persons from that user in order to reset clustering
204
	 *
205
	 * @param string $userId User to drop fo unset relation.
206
	 */
207
	public function unsetPersonsRelationForUser(string $userId, int $model) {
208
		$sub = $this->db->getQueryBuilder();
209
		$sub->select(new Literal('1'));
210
		$sub->from('facerecog_images', 'i')
211
			->where($sub->expr()->eq('i.id', '*PREFIX*' . $this->getTableName() .'.image'))
212
			->andWhere($sub->expr()->eq('i.model', $sub->createParameter('model')))
213
			->andWhere($sub->expr()->eq('i.user', $sub->createParameter('user')));
214
215
		$qb = $this->db->getQueryBuilder();
216
		$qb->update($this->getTableName())
217
			->set("person", $qb->createNamedParameter(null))
218
			->where('EXISTS (' . $sub->getSQL() . ')')
219
			->setParameter('model', $model)
220
			->setParameter('user', $userId)
221
			->execute();
222
	}
223
224
	/**
225
	 * Insert one face to database.
226
	 * Note: only reason we are not using (idiomatic) QBMapper method is
227
	 * because "QueryBuilder::PARAM_DATE" cannot be set there
228
	 *
229
	 * @param Face $face Face to insert
230
	 * @param IDBConnection $db Existing connection, if we need to reuse it. Null if we commit immediatelly.
231
	 */
232 15
	public function insertFace(Face $face, IDBConnection $db = null) {
233 15
		if ($db !== null) {
234 1
			$qb = $db->getQueryBuilder();
235
		} else {
236 14
			$qb = $this->db->getQueryBuilder();
237
		}
238
239 15
		$qb->insert($this->getTableName())
240 15
			->values([
241 15
				'image' => $qb->createNamedParameter($face->image),
242 15
				'person' => $qb->createNamedParameter($face->person),
243 15
				'left' => $qb->createNamedParameter($face->left),
244 15
				'right' => $qb->createNamedParameter($face->right),
245 15
				'top' => $qb->createNamedParameter($face->top),
246 15
				'bottom' => $qb->createNamedParameter($face->bottom),
247 15
				'confidence' => $qb->createNamedParameter($face->confidence),
248 15
				'landmarks' => $qb->createNamedParameter(json_encode($face->landmarks)),
249 15
				'descriptor' => $qb->createNamedParameter(json_encode($face->descriptor)),
250 15
				'creation_time' => $qb->createNamedParameter($face->creationTime, IQueryBuilder::PARAM_DATE),
251
			])
252 15
			->execute();
253
254 15
		$face->setId((int) $qb->getLastInsertId());
255
256 15
		return $face;
257
	}
258
}