Passed
Push — static-analysis ( fbc3f8...967946 )
by Matias
05:46
created

MigrateCommand   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 6
Bugs 1 Features 2
Metric Value
eloc 124
c 6
b 1
f 2
dl 0
loc 258
ccs 0
cts 110
cp 0
rs 10
wmc 27

9 Methods

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