Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new command to install models #189

Merged
merged 11 commits into from
Nov 24, 2019
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ before_script:
- cd apps/$APP_NAME

script:
- ./occ face:setup
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it really doesn't matter, but isn't it more logical to have make test and only after face:setup?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it really doesn't matter, but isn't it more logical to have make test and only after face:setup?

I modified it in case there was any test that depended on the models. Change it along with the necessary tests. 😉

- make test

# Create coverage report
Expand Down
20 changes: 1 addition & 19 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,23 +43,6 @@ endif

# Dependencies of the application

vendor/models/1/mmod_human_face_detector.dat:
mkdir -p vendor/models/1
wget https://github.com/davisking/dlib-models/raw/94cdb1e40b1c29c0bfcaf7355614bfe6da19460e/mmod_human_face_detector.dat.bz2 -O vendor/models/1/mmod_human_face_detector.dat.bz2
bzip2 -d vendor/models/1/mmod_human_face_detector.dat.bz2

vendor/models/1/dlib_face_recognition_resnet_model_v1.dat:
mkdir -p vendor/models/1
wget https://github.com/davisking/dlib-models/raw/2a61575dd45d818271c085ff8cd747613a48f20d/dlib_face_recognition_resnet_model_v1.dat.bz2 -O vendor/models/1/dlib_face_recognition_resnet_model_v1.dat.bz2
bzip2 -d vendor/models/1/dlib_face_recognition_resnet_model_v1.dat.bz2

vendor/models/1/shape_predictor_5_face_landmarks.dat:
mkdir -p vendor/models/1
wget https://github.com/davisking/dlib-models/raw/4af9b776281dd7d6e2e30d4a2d40458b1e254e40/shape_predictor_5_face_landmarks.dat.bz2 -O vendor/models/1/shape_predictor_5_face_landmarks.dat.bz2
bzip2 -d vendor/models/1/shape_predictor_5_face_landmarks.dat.bz2

download-models: vendor/models/1/mmod_human_face_detector.dat vendor/models/1/dlib_face_recognition_resnet_model_v1.dat vendor/models/1/shape_predictor_5_face_landmarks.dat

npm-deps:
npm i

Expand All @@ -73,7 +56,7 @@ vendor/js/lozad.js: npm-deps

javascript-deps: vendor/js/handlebars.js vendor/js/lozad.js

vendor-deps: download-models composer javascript-deps
vendor-deps: composer javascript-deps


# L10N Rules
Expand Down Expand Up @@ -150,7 +133,6 @@ clean: l10n-clean
rm -rf vendor/composer/
rm -rf vendor/doctrine/
rm -rf vendor/js
rm -f vendor/models/1/*
rm -rf vendor/myclabs/
rm -rf vendor/phar-io/
rm -rf vendor/phpdocumentor/
Expand Down
1 change: 1 addition & 0 deletions appinfo/info.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<commands>
<command>OCA\FaceRecognition\Command\BackgroundCommand</command>
<command>OCA\FaceRecognition\Command\ResetAllCommand</command>
<command>OCA\FaceRecognition\Command\SetupCommand</command>
</commands>
<settings>
<admin>OCA\FaceRecognition\Settings\Admin</admin>
Expand Down
11 changes: 10 additions & 1 deletion lib/BackgroundJob/FaceRecognitionContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCP\Files\IRootFolder;

use OCA\FaceRecognition\BackgroundJob\FaceRecognitionLogger;
use OCA\FaceRecognition\Service\ModelService;

/**
* Simple class holding all information that tasks might need, so they can do their job.
Expand All @@ -48,6 +49,9 @@ class FaceRecognitionContext {
/** @var IConfig */
public $config;

/** @var ModelService */
public $modelService;

/** @var FaceRecognitionLogger */
public $logger;

Expand All @@ -63,11 +67,16 @@ class FaceRecognitionContext {
/** @var bool True if we are running from command, false if we are running as background job */
private $isRunningThroughCommand;

public function __construct(IAppManager $appManager, IUserManager $userManager, IRootFolder $rootFolder, IConfig $config) {
public function __construct(IAppManager $appManager,
IUserManager $userManager,
IRootFolder $rootFolder,
IConfig $config,
ModelService $modelService) {
$this->appManager = $appManager;
$this->userManager = $userManager;
$this->rootFolder = $rootFolder;
$this->config = $config;
$this->modelService = $modelService;
$this->isRunningThroughCommand = false;
}

Expand Down
7 changes: 4 additions & 3 deletions lib/BackgroundJob/Tasks/CheckRequirementsTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function execute(FaceRecognitionContext $context) {
$this->setContext($context);
$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID));

$req = new Requirements($context->appManager, $model);
$req = new Requirements($context->modelService, $model);

if (!$req->pdlibLoaded()) {
$error_message = "PDLib is not loaded. Cannot continue";
Expand All @@ -70,9 +70,10 @@ public function execute(FaceRecognitionContext $context) {

if (!$req->modelFilesPresent()) {
$error_message =
"Files of model with ID ' . $model . ' are not present in models/ directory.\n" .
"Files of model with ID " . $model . " are not present in models/ directory.\n" .
"Please contact administrator to change models you are using for face recognition\n" .
"or reinstall application. File an issue here if that doesn\'t help: https://github.com/matiasdelellis/facerecognition/issues";
"or reinstall them with the 'occ face:setup' command. \n" .
"Fill an issue here if that doesn't help: https://github.com/matiasdelellis/facerecognition/issues";
$this->logInfo($error_message);
return false;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/BackgroundJob/Tasks/ImageProcessingTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public function execute(FaceRecognitionContext $context) {
$this->setContext($context);

$model = intval($this->config->getAppValue('facerecognition', 'model', AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID));
$requirements = new Requirements($context->appManager, $model);
$requirements = new Requirements($context->modelService, $model);

$dataDir = rtrim($context->config->getSystemValue('datadirectory', \OC::$SERVERROOT.'/data'), '/');
$images = $context->propertyBag['images'];
Expand Down
211 changes: 211 additions & 0 deletions lib/Command/SetupCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
<?php
/**
* @copyright Copyright (c) 2019, Matias De lellis <mati86dl@gmail.com>
*
* @author Matias De lellis <mati86dl@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\FaceRecognition\Command;

use OCP\ITempManager;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

use OCA\FaceRecognition\Helper\Requirements;
use OCA\FaceRecognition\Migration\AddDefaultFaceModel;
use OCA\FaceRecognition\Service\ModelService;

class SetupCommand extends Command {

/** @var ModelService */
protected $modelService;

/** @var ITempManager */
protected $tempManager;

/** @var OutputInterface */
protected $logger;

/* @var string */
protected $tempFolder;

/*
* Model 1
*/
private $modelVersion = AddDefaultFaceModel::DEFAULT_FACE_MODEL_ID;

private $detectorModelUrl = 'https://github.com/davisking/dlib-models/raw/94cdb1e40b1c29c0bfcaf7355614bfe6da19460e/mmod_human_face_detector.dat.bz2';
private $detectorModel = 'mmod_human_face_detector.dat';

private $resnetModelUrl = 'https://github.com/davisking/dlib-models/raw/2a61575dd45d818271c085ff8cd747613a48f20d/dlib_face_recognition_resnet_model_v1.dat.bz2';
private $resnetModel = 'dlib_face_recognition_resnet_model_v1.dat';

private $predictorModelUrl = 'https://github.com/davisking/dlib-models/raw/4af9b776281dd7d6e2e30d4a2d40458b1e254e40/shape_predictor_5_face_landmarks.dat.bz2';
private $predictorModel = 'shape_predictor_5_face_landmarks.dat';

/**
* @param ModelService $modelService
* @param ITempManager $tempManager
*/
public function __construct(ModelService $modelService,
ITempManager $tempManager) {
parent::__construct();

$this->modelService = $modelService;
$this->tempManager = $tempManager;
}

protected function configure() {
$this
->setName('face:setup')
->setDescription('Download and Setup the model 1 used for the analysis');
}

/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output) {

$this->logger = $output;

$requirements = new Requirements($this->modelService, $this->modelVersion);
if ($requirements->modelFilesPresent()) {
$this->logger->writeln('The models are already installed');
return 0;
}

$this->modelService->useModelVersion($this->modelVersion);

$this->tempFolder = $this->tempManager->getTemporaryFolder('/facerecognition/');

$this->downloadModel ($this->detectorModelUrl);
matiasdelellis marked this conversation as resolved.
Show resolved Hide resolved
$this->bunzip2 ($this->getDownloadedFile($this->detectorModelUrl), $this->modelService->getModelPath($this->detectorModel));

$this->downloadModel ($this->resnetModelUrl);
$this->bunzip2 ($this->getDownloadedFile($this->resnetModelUrl), $this->modelService->getModelPath($this->resnetModel));

$this->downloadModel ($this->predictorModelUrl);
$this->bunzip2 ($this->getDownloadedFile($this->predictorModelUrl), $this->modelService->getModelPath($this->predictorModel));

$this->logger->writeln('Install models successfully done');

$this->tempManager->clean();

return 0;
}

/**
* Downloads the facereconition model to an temp folder.
*
* @throws \Exception
*/
private function downloadModel(string $url) {
$this->logger->writeln('Downloading: ' . $url . ' on ' . $this->getDownloadedFile($url));

$fp = fopen($this->getDownloadedFile($url), 'w+');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
CURLOPT_USERAGENT => 'Nextcloud Facerecognition Installer',
]);

if(curl_exec($ch) === false) {
throw new \Exception('Curl error: ' . curl_error($ch));
}

$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode !== 200) {
$statusCodes = [
400 => 'Bad request',
401 => 'Unauthorized',
403 => 'Forbidden',
404 => 'Not Found',
500 => 'Internal Server Error',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
];

$message = 'Download failed';
if(isset($statusCodes[$httpCode])) {
$message .= ' - ' . $statusCodes[$httpCode] . ' (HTTP ' . $httpCode . ')';
} else {
$message .= ' - HTTP status code: ' . $httpCode;
}

$curlErrorMessage = curl_error($ch);
if(!empty($curlErrorMessage)) {
$message .= ' - curl error message: ' . $curlErrorMessage;
}
$message .= ' - URL: ' . htmlentities($url);

throw new \Exception($message);
}

$info = curl_getinfo($ch);
$this->logger->writeln("Download ".$info['size_download']." bytes");

curl_close($ch);
fclose($fp);
}

/**
* @param string $in
* @param string $out
* @desc uncompressing the file with the bzip2-extension
*
* @throws \Exception
*/
private function bunzip2 ($in, $out) {
$this->logger->writeln('Decompresing: '.$in. ' on '.$out);
$this->logger->writeln('');

if (!file_exists ($in) || !is_readable ($in))
throw new \Exception('The file '.$in.' not exists or is not readable');
if ((!file_exists ($out) && !is_writeable (dirname ($out)) || (file_exists($out) && !is_writable($out)) ))
throw new \Exception('The file '.$out.' exists or is not writable');

$in_file = bzopen ($in, "r");
$out_file = fopen ($out, "w");

while ($buffer = bzread ($in_file, 4096)) {
if($buffer === FALSE)
throw new \Exception('Read problem: ' . bzerrstr($in_file));
if(bzerrno($in_file) !== 0)
throw new \Exception('Compression problem: '. bzerrstr($in_file));
fwrite ($out_file, $buffer, 4096);
}

bzclose ($in_file);
fclose ($out_file);
}

private function getDownloadedFile (string $url): string {
$file = $this->tempFolder . basename($url);
return $file;
}

}
Loading