Completed
Push — master ( b2b3ad...48dda4 )
by Matias
16s queued 12s
created

MigrateCommand   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 244
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 4
Bugs 0 Features 2
Metric Value
eloc 124
c 4
b 0
f 2
dl 0
loc 244
ccs 0
cts 157
cp 0
rs 10
wmc 26

9 Methods

Rating   Name   Duplication   Size   Complexity  
A getFaceRect() 0 8 1
A migrateFace() 0 12 1
B execute() 0 68 11
A getEligiblesUserId() 0 10 2
A migrateUser() 0 30 5
A configure() 0 18 1
A __construct() 0 15 1
A migrateImage() 0 12 1
A getImageFilePath() 0 18 3
1
<?php
2
/**
3
 * @copyright Copyright (c) 2020, Matias De lellis <[email protected]>
4
 * @copyright Copyright (c) 2019, Branko Kokanovic <[email protected]>
5
 *
6
 * @author Branko Kokanovic <[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
namespace OCA\FaceRecognition\Command;
25
26
use OCP\IUserManager;
27
use OCP\IUser;
28
29
use Symfony\Component\Console\Command\Command;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Command\Command 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...
30
use Symfony\Component\Console\Input\InputOption;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputOption 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...
31
use Symfony\Component\Console\Input\InputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Input\InputInterface 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...
32
use Symfony\Component\Console\Output\OutputInterface;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Output\OutputInterface 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...
33
34
use Symfony\Component\Console\Helper\ProgressBar;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Helper\ProgressBar 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...
35
36
use OCA\FaceRecognition\Db\Face;
37
use OCA\FaceRecognition\Db\FaceMapper;
38
39
use OCA\FaceRecognition\Db\Image;
40
use OCA\FaceRecognition\Db\ImageMapper;
41
42
use OCA\FaceRecognition\Model\ModelManager;
43
44
use OCA\FaceRecognition\Service\FaceManagementService;
45
use OCA\FaceRecognition\Service\FileService;
46
47
use OCP\Image as OCP_Image;
48
49
class MigrateCommand extends Command {
50
51
	/** @var FaceManagementService */
52
	protected $faceManagementService;
53
54
	/** @var FileService */
55
	protected $fileService;
56
57
	/** @var IUserManager */
58
	protected $userManager;
59
60
	/** @var ModelManager */
61
	protected $modelManager;
62
63
	/** @var FaceMapper */
64
	protected $faceMapper;
65
66
	/** @var ImageMapper Image mapper*/
67
	protected $imageMapper;
68
69
	/** @var OutputInterface $output */
70
	protected $output;
71
72
	/**
73
	 * @param FaceManagementService $faceManagementService
74
	 * @param IUserManager $userManager
75
	 */
76
	public function __construct(FaceManagementService $faceManagementService,
77
	                            FileService           $fileService,
78
	                            IUserManager          $userManager,
79
	                            ModelManager          $modelManager,
80
	                            FaceMapper            $faceMapper,
81
	                            ImageMapper           $imageMapper)
82
	{
83
		parent::__construct();
84
85
		$this->faceManagementService = $faceManagementService;
86
		$this->fileService           = $fileService;
87
		$this->userManager           = $userManager;
88
		$this->modelManager          = $modelManager;
89
		$this->faceMapper            = $faceMapper;
90
		$this->imageMapper           = $imageMapper;
91
	}
92
93
	protected function configure() {
94
		$this
95
			->setName('face:migrate')
96
			->setDescription(
97
				'Migrate the faces found in a model and analyze with the current model.')
98
			->addOption(
99
				'model_id',
100
				'm',
101
				InputOption::VALUE_REQUIRED,
102
				'The identifier number of the model to migrate',
103
				null
104
			)
105
			->addOption(
106
				'user_id',
107
				'u',
108
				InputOption::VALUE_REQUIRED,
109
				'Migrate data for a given user only. If not given, migrate everything for all users.',
110
				null
111
			);
112
	}
113
114
	/**
115
	 * @param InputInterface $input
116
	 * @param OutputInterface $output
117
	 * @return int
118
	 */
119
	protected function execute(InputInterface $input, OutputInterface $output) {
120
		$this->output = $output;
121
122
		/**
123
		 * Check the considerations of the models to migrate.
124
		 */
125
		$modelId = $input->getOption('model_id');
126
		if (is_null($modelId)) {
127
			$output->writeln("You must indicate the ID of the model to migrate");
128
			return 1;
129
		}
130
131
		$model = $this->modelManager->getModel($modelId);
132
		if (is_null($model)) {
133
			$output->writeln("Invalid model Id");
134
			return 1;
135
		}
136
137
		if (!$model->isInstalled()) {
138
			$output->writeln("The model <$modelId> is not installed");
139
			return 1;
140
		}
141
142
		$currentModel = $this->modelManager->getCurrentModel();
143
		$currentModelId = (!is_null($currentModel)) ? $currentModel->getId() : -1;
144
145
		if ($currentModelId === $modelId) {
146
			$output->writeln("The proposed model <$modelId> to migrate must be other than the current one <$currentModelId>");
147
			return 1;
148
		}
149
150
		/**
151
		 * Check the user if it is provided.
152
		 */
153
		$userId = $input->getOption('user_id');
154
		if ($userId !== null) {
155
			$user = $this->userManager->get($userId);
156
			if ($user === null) {
157
				$output->writeln("User with id <$userId> is unknown.");
158
				return 1;
159
			}
160
		}
161
162
		/**
163
		 * Get user to migrate
164
		 */
165
		$userIds = $this->getEligiblesUserId($userId);
166
167
		/**
168
		 * Check that any user have data in the current model
169
		 */
170
		foreach ($userIds as $mUserId) {
171
			if ($this->faceManagementService->hasDataForUser($mUserId, $currentModelId)) {
172
				$output->writeln("The user <$mUserId> in current model <$currentModelId> already has data. You cannot migrate to a used model.");
173
				return 1;
174
			}
175
		}
176
177
		/**
178
		 * Open the model and migrate to users.
179
		 */
180
		$currentModel->open();
181
182
		foreach ($userIds as $mUserId) {
183
			$this->migrateUser($currentModel, $modelId, $mUserId);
184
		}
185
186
		$output->writeln("The faces migration is done. Remember that you must recreate the clusters with the background_job command");
187
	}
188
189
	private function migrateUser($currentModel, $oldModelId, $userId) {
190
		if (!$this->faceManagementService->hasDataForUser($userId, $oldModelId)) {
191
			$this->output->writeln("User <$userId> has no data in model <$oldModelId> to migrate.");
192
			return;
193
		}
194
195
		$this->output->writeln("Will be migrated <$userId> from model <$oldModelId>");
196
197
		$currentModelId = $currentModel->getId();
198
		$oldImages = $this->imageMapper->findAll($userId, $oldModelId);
199
200
		$progressBar = new ProgressBar($this->output, count($oldImages));
201
		$progressBar->start();
202
203
		foreach ($oldImages as $oldImage) {
204
			$newImage = $this->migrateImage($oldImage, $userId, $currentModelId);
205
			$oldFaces = $this->faceMapper->findFromFile($userId, $oldModelId, $newImage->getFile());
206
			if (count($oldFaces) > 0) {
207
				$filePath = $this->getImageFilePath($newImage);
208
				foreach ($oldFaces as $oldFace) {
209
					$this->migrateFace($currentModel, $oldFace, $newImage, $filePath);
210
				}
211
				$this->fileService->clean();
212
			}
213
			$progressBar->advance(1);
214
		}
215
216
		$progressBar->finish();
217
218
		$this->output->writeln("Done");
219
	}
220
221
	private function migrateImage($oldImage, $userId, $modelId): Image {
222
		$image = new Image();
223
224
		$image->setUser($userId);
225
		$image->setFile($oldImage->getFile());
226
		$image->setModel($modelId);
227
		$image->setIsProcessed($oldImage->getIsProcessed());
228
		$image->setError($oldImage->getError());
229
		$image->setLastProcessedTime($oldImage->getLastProcessedTime());
230
		$image->setProcessingDuration($oldImage->getProcessingDuration());
231
232
		return $this->imageMapper->insert($image);
233
	}
234
235
	private function migrateFace($model, $oldFace, $image, $filePath) {
236
		$faceRect = $this->getFaceRect($oldFace);
237
238
		$face = Face::fromModel($image->getId(), $faceRect);
239
240
		$landmarks = $model->detectLandmarks($filePath, $faceRect);
241
		$descriptor = $model->computeDescriptor($filePath, $landmarks);
242
243
		$face->landmarks = $landmarks['parts'];
244
		$face->descriptor = $descriptor;
245
246
		$this->faceMapper->insertFace($face);
247
	}
248
249
	private function getImageFilePath(Image $image): ?string {
250
		$file = $this->fileService->getFileById($image->getFile(), $image->getUser());
251
		if (empty($file)) {
252
			return null;
253
		}
254
255
		$localPath = $this->fileService->getLocalFile($file);
256
257
		$image = new OCP_Image();
258
		$image->loadFromFile($localPath);
259
		if ($image->getOrientation() > 1) {
260
			$tempPath = $this->fileService->getTemporaryFile();
261
			$image->fixOrientation();
262
			$image->save($tempPath, 'image/png');
263
			return $tempPath;
264
		}
265
266
		return $localPath;
267
	}
268
269
	private function getFaceRect(Face $face): array {
270
		$rect = [];
271
		$rect['left'] = (int)$face->getLeft();
272
		$rect['right'] = (int)$face->getRight();
273
		$rect['top'] =  (int)$face->getTop();
274
		$rect['bottom'] = (int)$face->getBottom();
275
		$rect['detection_confidence'] = $face->getConfidence();
276
		return $rect;
277
	}
278
279
	/**
280
	 * Get an array with the eligibles users taking into account the user argument,
281
	 * or all users.
282
	 */
283
	private function getEligiblesUserId(string $userId = null): array {
284
		$eligible_users = array();
285
		if (is_null($userId)) {
286
			$this->userManager->callForAllUsers(function (IUser $user) use (&$eligible_users) {
287
				$eligible_users[] = $user->getUID();
288
			});
289
		} else {
290
			$eligible_users[] = $userId;
291
		}
292
		return $eligible_users;
293
	}
294
295
}
296