Issues (125)

lib/Command/MigrateCommand.php (5 issues)

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 OCA\FaceRecognition\Helper\CommandLock;
49
50
use OCP\Image as OCP_Image;
51
52
class MigrateCommand extends Command {
53
54
	/** @var FaceManagementService */
55
	protected $faceManagementService;
56
57
	/** @var FileService */
58
	protected $fileService;
59
60
	/** @var IUserManager */
61
	protected $userManager;
62
63
	/** @var ModelManager */
64
	protected $modelManager;
65
66
	/** @var FaceMapper */
67
	protected $faceMapper;
68
69
	/** @var ImageMapper Image mapper*/
70
	protected $imageMapper;
71
72
	/** @var OutputInterface $output */
73
	protected $output;
74
75
	/**
76
	 * @param FaceManagementService $faceManagementService
77
	 * @param IUserManager $userManager
78
	 */
79
	public function __construct(FaceManagementService $faceManagementService,
80
	                            FileService           $fileService,
81
	                            IUserManager          $userManager,
82
	                            ModelManager          $modelManager,
83
	                            FaceMapper            $faceMapper,
84
	                            ImageMapper           $imageMapper)
85
	{
86
		parent::__construct();
87
88
		$this->faceManagementService = $faceManagementService;
89
		$this->fileService           = $fileService;
90
		$this->userManager           = $userManager;
91
		$this->modelManager          = $modelManager;
92
		$this->faceMapper            = $faceMapper;
93
		$this->imageMapper           = $imageMapper;
94
	}
95
96
	/**
97
	 * @return void
98
	 */
99
	protected function configure() {
100
		$this
101
			->setName('face:migrate')
102
			->setDescription(
103
				'Migrate the faces found in a model and analyze with the current model.')
104
			->addOption(
105
				'model_id',
106
				'm',
107
				InputOption::VALUE_REQUIRED,
108
				'The identifier number of the model to migrate',
109
				null
110
			)
111
			->addOption(
112
				'user_id',
113
				'u',
114
				InputOption::VALUE_REQUIRED,
115
				'Migrate data for a given user only. If not given, migrate everything for all users.',
116
				null
117
			);
118
	}
119
120
	/**
121
	 * @param InputInterface $input
122
	 * @param OutputInterface $output
123
	 *
124
	 * @return int
125
	 *
126
	 */
127
	protected function execute(InputInterface $input, OutputInterface $output): int {
128
		$this->output = $output;
129
130
		/**
131
		 * Check the considerations of the models to migrate.
132
		 */
133
		$modelId = $input->getOption('model_id');
134
		if (is_null($modelId)) {
135
			$output->writeln("You must indicate the ID of the model to migrate");
136
			return 1;
137
		}
138
139
		$model = $this->modelManager->getModel($modelId);
0 ignored issues
show
$modelId of type boolean|string|string[] is incompatible with the type integer expected by parameter $version of OCA\FaceRecognition\Model\ModelManager::getModel(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

139
		$model = $this->modelManager->getModel(/** @scrutinizer ignore-type */ $modelId);
Loading history...
140
		if (is_null($model)) {
141
			$output->writeln("Invalid model Id");
142
			return 1;
143
		}
144
145
		if (!$model->isInstalled()) {
146
			$output->writeln("The model <$modelId> is not installed");
147
			return 1;
148
		}
149
150
		$currentModel = $this->modelManager->getCurrentModel();
151
		$currentModelId = (!is_null($currentModel)) ? $currentModel->getId() : -1;
152
153
		if ($currentModelId === $modelId) {
0 ignored issues
show
The condition $currentModelId === $modelId is always false.
Loading history...
154
			$output->writeln("The proposed model <$modelId> to migrate must be other than the current one <$currentModelId>");
155
			return 1;
156
		}
157
158
		/**
159
		 * Check the user if it is provided.
160
		 */
161
		$userId = $input->getOption('user_id');
162
		if ($userId !== null) {
163
			$user = $this->userManager->get($userId);
0 ignored issues
show
It seems like $userId can also be of type string[]; however, parameter $uid of OCP\IUserManager::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

163
			$user = $this->userManager->get(/** @scrutinizer ignore-type */ $userId);
Loading history...
164
			if ($user === null) {
165
				$output->writeln("User with id <$userId> is unknown.");
166
				return 1;
167
			}
168
		}
169
170
		/**
171
		 * Get user to migrate
172
		 */
173
		$userIds = $this->getEligiblesUserId($userId);
174
175
		/**
176
		 * Check that any user have data in the current model
177
		 */
178
		foreach ($userIds as $mUserId) {
179
			if ($this->faceManagementService->hasDataForUser($mUserId, $currentModelId)) {
180
				$output->writeln("The user <$mUserId> in current model <$currentModelId> already has data. You cannot migrate to a used model.");
181
				return 1;
182
			}
183
		}
184
185
		// Get lock to avoid potential errors.
186
		//
187
		$lock = CommandLock::Lock("face:migrate");
0 ignored issues
show
Are you sure the assignment to $lock is correct as OCA\FaceRecognition\Help...k::Lock('face:migrate') targeting OCA\FaceRecognition\Helper\CommandLock::lock() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
188
		if (!$lock) {
0 ignored issues
show
$lock is of type null, thus it always evaluated to false.
Loading history...
189
			$output->writeln("Another command ('". CommandLock::IsLockedBy().  "') is already running that prevents it from continuing.");
190
			return 1;
191
		}
192
193
		/**
194
		 * Open the model and migrate to users.
195
		 */
196
		$currentModel->open();
197
198
		foreach ($userIds as $mUserId) {
199
			$this->migrateUser($currentModel, $modelId, $mUserId);
200
		}
201
202
		$output->writeln("The faces migration is done. Remember that you must recreate the clusters with the background_job command");
203
204
		// Release obtained lock
205
		//
206
		CommandLock::Unlock($lock);
207
208
		return 0;
209
	}
210
211
	/**
212
	 * @return void
213
	 */
214
	private function migrateUser(IModel $currentModel, int $oldModelId, $userId) {
215
		if (!$this->faceManagementService->hasDataForUser($userId, $oldModelId)) {
216
			$this->output->writeln("User <$userId> has no data in model <$oldModelId> to migrate.");
217
			return;
218
		}
219
220
		$this->output->writeln("Will be migrated <$userId> from model <$oldModelId>");
221
222
		$currentModelId = $currentModel->getId();
223
		$oldImages = $this->imageMapper->findAll($userId, $oldModelId);
224
225
		$progressBar = new ProgressBar($this->output, count($oldImages));
226
		$progressBar->start();
227
228
		foreach ($oldImages as $oldImage) {
229
			$newImage = $this->migrateImage($oldImage, $userId, $currentModelId);
230
			$oldFaces = $this->faceMapper->findFromFile($userId, $oldModelId, $newImage->getFile());
231
			if (count($oldFaces) > 0) {
232
				$filePath = $this->getImageFilePath($newImage);
233
				if ($filePath === null)
234
					continue;
235
236
				foreach ($oldFaces as $oldFace) {
237
					$this->migrateFace($currentModel, $oldFace, $newImage, $filePath);
238
				}
239
				$this->fileService->clean();
240
			}
241
			$progressBar->advance(1);
242
		}
243
244
		$progressBar->finish();
245
246
		$this->output->writeln("Done");
247
	}
248
249
	private function migrateImage($oldImage, string $userId, int $modelId): Image {
250
		$image = new Image();
251
252
		$image->setUser($userId);
253
		$image->setFile($oldImage->getFile());
254
		$image->setModel($modelId);
255
		$image->setIsProcessed($oldImage->getIsProcessed());
256
		$image->setError($oldImage->getError());
257
		$image->setLastProcessedTime($oldImage->getLastProcessedTime());
258
		$image->setProcessingDuration($oldImage->getProcessingDuration());
259
260
		return $this->imageMapper->insert($image);
261
	}
262
263
	/**
264
	 * @param string $filePath
265
	 */
266
	private function migrateFace(IModel $model, Face $oldFace, Image $image, string $filePath): void {
267
		// Get the rectangle and the confidence of the original face.
268
		$faceRect = $this->getFaceRect($oldFace);
269
270
		// Get the landmarks and descriptor with the new model.
271
		$faceAssoc = $model->compute($filePath, $faceRect);
272
273
		// Convert the assoc array into an Face object and insert on database.
274
		$face = Face::fromModel($image->getId(), $faceAssoc);
275
		$this->faceMapper->insertFace($face);
276
	}
277
278
	private function getImageFilePath(Image $image): ?string {
279
		$file = $this->fileService->getFileById($image->getFile(), $image->getUser());
280
		if (empty($file)) {
281
			return null;
282
		}
283
284
		$localPath = $this->fileService->getLocalFile($file);
285
286
		$image = new OCP_Image();
287
		$image->loadFromFile($localPath);
288
		if ($image->getOrientation() > 1) {
289
			$tempPath = $this->fileService->getTemporaryFile();
290
			$image->fixOrientation();
291
			$image->save($tempPath, 'image/png');
292
			return $tempPath;
293
		}
294
295
		return $localPath;
296
	}
297
298
	private function getFaceRect(Face $face): array {
299
		$rect = [];
300
		$rect['left']   = (int)$face->getX();
301
		$rect['right']  = (int)$face->getX() + $face->getWidth();
302
		$rect['top']    = (int)$face->getY();
303
		$rect['bottom'] = (int)$face->getY() + $face->getHeight();
304
		$rect['detection_confidence'] = $face->getConfidence();
305
		return $rect;
306
	}
307
308
	/**
309
	 * Get an array with the eligibles users taking into account the user argument,
310
	 * or all users.
311
	 */
312
	private function getEligiblesUserId(string $userId = null): array {
313
		$eligible_users = array();
314
		if (is_null($userId)) {
315
			$this->userManager->callForAllUsers(function (IUser $user) use (&$eligible_users) {
316
				$eligible_users[] = $user->getUID();
317
			});
318
		} else {
319
			$eligible_users[] = $userId;
320
		}
321
		return $eligible_users;
322
	}
323
324
}
325