Passed
Push — duplicates ( f4ec2b )
by Matias
05:33
created

SearchCommand   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 149
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 85
c 1
b 0
f 0
dl 0
loc 149
ccs 0
cts 65
cp 0
rs 10
wmc 22

4 Methods

Rating   Name   Duplication   Size   Complexity  
A configure() 0 17 1
A __construct() 0 13 1
A execute() 0 31 5
C searchDuplicates() 0 49 15
1
<?php
2
/**
3
 * @copyright Copyright (c) 2021, Matias De lellis <[email protected]>
4
 *
5
 * @author Matias De lellis <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
namespace OCA\FaceRecognition\Command;
24
25
use Symfony\Component\Console\Command\Command;
26
use Symfony\Component\Console\Helper\Table;
27
use Symfony\Component\Console\Input\InputOption;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Output\OutputInterface;
30
31
use OCP\IUser;
32
use OCP\IUserManager;
33
34
use OCA\FaceRecognition\Db\ImageMapper;
35
use OCA\FaceRecognition\Db\FaceMapper;
36
37
use OCA\FaceRecognition\Service\FileService;
38
use OCA\FaceRecognition\Service\SettingsService;
39
40
class SearchCommand extends Command {
41
42
	/** @var IUserManager */
43
	protected $userManager;
44
45
	/** @var ImageMapper */
46
	protected $imageMapper;
47
48
	/** @var FaceMapper */
49
	protected $faceMapper;
50
51
	/** @var FileService */
52
	private $fileService;
53
54
	/** @var SettingsService */
55
	private $settingsService;
56
57
	/**
58
	 * @param IUserManager $userManager
59
	 * @param ImageMapper $imageMapper
60
	 * @param FaceMapper $faceMapper
61
	 * @param SettingsService $settingsService
62
	 * @param FileService $fileService
63
	 */
64
	public function __construct(IUserManager    $userManager,
65
	                            ImageMapper     $imageMapper,
66
	                            FaceMapper      $faceMapper,
67
	                            FileService     $fileService,
68
	                            SettingsService $settingsService)
69
	{
70
		parent::__construct();
71
72
		$this->userManager     = $userManager;
73
		$this->imageMapper     = $imageMapper;
74
		$this->faceMapper      = $faceMapper;
75
		$this->fileService     = $fileService;
76
		$this->settingsService = $settingsService;
77
	}
78
79
	/**
80
	 * @return void
81
	 */
82
	protected function configure() {
83
		$this
84
			->setName('face:search')
85
			->setDescription('Search for additional information thanks to the processed data')
86
			->addOption(
87
				'duplicates',
88
				null,
89
				InputOption::VALUE_NONE,
90
				'Search for duplicate images thanks to finding exactly the same faces',
91
				null
92
			)
93
			->addOption(
94
				'user_id',
95
				'u',
96
				InputOption::VALUE_REQUIRED,
97
				'Search for a given user only. If not given, search for all users.',
98
				null
99
			);
100
	}
101
102
	/**
103
	 * @param InputInterface $input
104
	 * @param OutputInterface $output
105
	 * @return int
106
	 */
107
	protected function execute(InputInterface $input, OutputInterface $output) {
108
		if (version_compare(phpversion('pdlib'), '1.0.2', '<')) {
109
			$output->writeln("The version of pdlib is very old. pdlib >= 1.0.2 is recommended");
110
			return 1;
111
		}
112
113
		if (!$input->getOption('duplicates')) {
114
			$output->writeln("You must indicate that you want to search.");
115
			return 1;
116
		}
117
118
		$users = array();
119
		$userId = $input->getOption('user_id');
120
		if (!is_null($userId)) {
121
			if ($this->userManager->get($userId) === null) {
122
				$output->writeln("User with id <$userId> in unknown.");
123
				return 1;
124
			}
125
			else {
126
				$users[] = $userId;
127
			}
128
		}
129
		else {
130
			$this->userManager->callForAllUsers(function (IUser $iUser) use (&$users)  {
131
				$users[] = $iUser->getUID();
132
			});
133
		}
134
135
		$this->searchDuplicates($output, $users);
136
137
		return 0;
138
	}
139
140
	private function searchDuplicates (OutputInterface $output, array $users): void {
141
		$sensitivity = 0.1;
142
		$min_confidence = $this->settingsService->getMinimumConfidence();
143
		$min_face_size = $this->settingsService->getMinimumFaceSize();
144
		$modelId = $this->settingsService->getCurrentFaceModel();
145
146
		foreach ($users as $user) {
147
			$duplicates = array();
148
			$faces = $this->faceMapper->getFaces($user, $modelId);
149
			$faces_count = count($faces);
150
			for ($i = 0; $i < $faces_count; $i++) {
151
				$face1 = $faces[$i];
152
				if ((!$face1->isGroupable) ||
153
				    ($face1->confidence < $min_confidence) ||
154
				    (max($face1->height(), $face1->width()) < $min_face_size)) {
155
					continue;
156
				}
157
				for ($j = $i+1; $j < $faces_count; $j++) {
158
					$face2 = $faces[$j];
159
					if ((!$face2->isGroupable) ||
160
					    ($face2->confidence < $min_confidence) ||
161
					    (max($face2->height(), $face2->width()) < $min_face_size)) {
162
						continue;
163
					}
164
					$distance = dlib_vector_length($face1->descriptor, $face2->descriptor);
0 ignored issues
show
Bug introduced by
The function dlib_vector_length was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

164
					$distance = /** @scrutinizer ignore-call */ dlib_vector_length($face1->descriptor, $face2->descriptor);
Loading history...
165
					if ($distance < $sensitivity) {
166
						if (!isset($duplicates[$face1->getImage()])) {
167
							$duplicates[$face1->getImage()] = array();
168
						}
169
						if (!isset($duplicates[$face1->getImage()][$face2->getImage()])) {
170
							$duplicates[$face1->getImage()][$face2->getImage()] = 0;
171
						}
172
						$duplicates[$face1->getImage()][$face2->getImage()]++;
173
					}
174
				}
175
			}
176
177
			foreach ($duplicates as $image1 => $duplicate) {
178
				$image = $this->imageMapper->find($user, $image1);
179
				$file1 = $this->fileService->getFileById($image->getFile(), $user);
180
				$imagePath1 = $this->fileService->getLocalFile($file1);
0 ignored issues
show
Bug introduced by
It seems like $file1 can also be of type null; however, parameter $file of OCA\FaceRecognition\Serv...Service::getLocalFile() does only seem to accept OCP\Files\File, 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

180
				$imagePath1 = $this->fileService->getLocalFile(/** @scrutinizer ignore-type */ $file1);
Loading history...
181
				foreach ($duplicate as $image2 => $score) {
182
					$image = $this->imageMapper->find($user, $image2);
183
					$file2 = $this->fileService->getFileById($image->getFile(), $user);
184
					$imagePath2 = $this->fileService->getLocalFile($file2);
185
					$output->writeln("");
186
					$output->writeln("Duplicate: " . $score . " matches");
187
					$output->writeln(" File 1: " . $imagePath1);
188
					$output->writeln(" File 2: " . $imagePath2);
189
				}
190
			}
191
		}
192
193
	}
194
195
}
196