* @copyright Copyright (c) 2018, Branko Kokanovic * * @author Branko Kokanovic * * @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 . * */ namespace OCA\FaceRecognition\Tests\Integration; use OC; use OC\Files\View; use OCP\IConfig; use OCP\IUser; use OCP\AppFramework\App; use OCP\AppFramework\IAppContainer; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionContext; use OCA\FaceRecognition\BackgroundJob\FaceRecognitionLogger; use OCA\FaceRecognition\BackgroundJob\Tasks\AddMissingImagesTask; use OCA\FaceRecognition\BackgroundJob\Tasks\ImageProcessingTask; use OCA\FaceRecognition\Db\Image; use OCA\FaceRecognition\Model\ModelManager; use Test\TestCase; class ImageProcessingTaskTest extends IntegrationTestCase { public function setUp(): void { parent::setUp(); // Since test is changing this values, try to preserve old values (this is best effort) $this->originalMinImageSize = intval($this->config->getAppValue('facerecognition', 'min_image_size', '512')); $this->originalMaxImageArea = intval($this->config->getAppValue('facerecognition', 'max_image_area', 0)); $this->config->setAppValue('facerecognition', 'min_image_size', 1); $this->config->setAppValue('facerecognition', 'max_image_area', 200 * 200); // Install models needed to test $model = $this->container->query('OCA\FaceRecognition\Model\DlibCnnModel\DlibCnn5Model'); $model->install(); } public function tearDown(): void { $this->config->setAppValue('facerecognition', 'min_image_size', $this->originalMinImageSize); $this->config->setAppValue('facerecognition', 'max_image_area', $this->originalMaxImageArea); parent::tearDown(); } /** * Tests when image cannot be loaded at all * (tests whether image is declared as processed and error is added to it) */ public function testInvalidImage() { $image = $this->genericTestImageProcessing('bogus image data', true, 0); // Invalid image should have 0 as processing duration $this->assertEquals(0, $image->getProcessingDuration()); } /** * Tests that small images are skipped during processing */ public function testImageTooSmallToProcess() { $this->config->setAppValue('facerecognition', 'min_image_size', 10000); $imgData = file_get_contents(\OC::$SERVERROOT . '/apps/facerecognition/tests/assets/lenna.jpg'); $image = $this->genericTestImageProcessing($imgData, false, 0); } /** * Test when there is no faces on image * (image should be declared as processed, but 0 faces should be associated with it) */ public function testNoFacesFound() { $imgData = file_get_contents(\OC::$SERVERROOT . '/apps/facerecognition/tests/assets/black.jpg'); $image = $this->genericTestImageProcessing($imgData, false, 0); } /** * Regular positive test that find one face in image */ public function testFindFace() { $imgData = file_get_contents(\OC::$SERVERROOT . '/apps/facerecognition/tests/assets/lenna.jpg'); $image = $this->genericTestImageProcessing($imgData, false, 1); // Check exact values for face boundaries (might need to update when we bump dlib/pdlib versions) $faceMapper = $this->container->query('OCA\FaceRecognition\Db\FaceMapper'); $face = $faceMapper->getFaces($this->user->getUID(), ModelManager::DEFAULT_FACE_MODEL_ID)[0]; $face = $faceMapper->find($face->getId()); $this->assertEquals(49, $face->getX()); $this->assertEquals(62, $face->getY()); $this->assertEquals(75, $face->getWidth()); $this->assertEquals(75, $face->getHeight()); } /** * Helper function that asserts in generic fashion whatever necessary. * * @param string|resource $imgData Image data that will be analyzed * @param bool $expectingError True if we should assert that error is found, false if we should assert there is no error * @param int $expectedFacesCount Number of faces that we should assert that should be found in processed image * * @return Image One found image */ private function genericTestImageProcessing($imgData, $expectingError, $expectedFacesCount) { $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); $faceMapper = $this->container->query('OCA\FaceRecognition\Db\FaceMapper'); $this->doImageProcessing($imgData); // Check that there is no unprocessed images $this->assertEquals(0, count($imageMapper->findImagesWithoutFaces($this->user, ModelManager::DEFAULT_FACE_MODEL_ID))); // Check image fields after processing $images = $imageMapper->findImages($this->user->getUID(), ModelManager::DEFAULT_FACE_MODEL_ID); $this->assertEquals(1, count($images)); $image = $imageMapper->find($this->user->getUID(), $images[0]->getId()); $this->assertTrue(is_null($image->getError()) xor $expectingError); $this->assertTrue($image->getIsProcessed()); $this->assertNotNull(0, $image->getProcessingDuration()); $this->assertNotNull($image->getLastProcessedTime()); // Check number of found faces $this->assertEquals($expectedFacesCount, count($faceMapper->getFaces($this->user->getUID(), ModelManager::DEFAULT_FACE_MODEL_ID))); return $image; } /** * Helper method to set up and do image processing * * @param string|resource $imgData Image data that will be analyzed * @param IUser|null $contextUser Optional user to process images for. * If not given, images for all users will be processed. */ private function doImageProcessing($imgData, $contextUser = null) { // Create ImageProcessingTask $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); $settingsService = $this->container->query('OCA\FaceRecognition\Service\SettingsService'); $modelManager = $this->container->query('OCA\FaceRecognition\Model\ModelManager'); $lockingProvider = $this->container->query('OCP\Lock\ILockingProvider'); $imageProcessingTask = new ImageProcessingTask($imageMapper, $fileService, $settingsService, $modelManager, $lockingProvider); $this->assertNotEquals("", $imageProcessingTask->description()); // Set user for which to do processing, if any $this->context->user = $contextUser; // Upload file $this->loginAsUser($this->user->getUID()); $view = new View('/' . $this->user->getUID() . '/files'); $view->file_put_contents("foo1.jpg", $imgData); // Scan it, so it is in database, ready to be processed $this->doMissingImageScan($this->user); $this->context->propertyBag['images'] = $imageMapper->findImagesWithoutFaces($this->user, ModelManager::DEFAULT_FACE_MODEL_ID); $this->assertEquals(1, count($this->context->propertyBag['images'])); // Since this task returns generator, iterate until it is done $generator = $imageProcessingTask->execute($this->context); foreach ($generator as $_) { } $this->assertEquals(true, $generator->getReturn()); } /** * Helper method to set up and do scanning * * @param IUser|null $contextUser Optional user to scan for. If not given, images for all users will be scanned. */ private function doMissingImageScan($contextUser = null) { // Reset config that full scan is done, to make sure we are scanning again $this->config->setUserValue($this->user->getUID(), 'facerecognition', AddMissingImagesTask::FULL_IMAGE_SCAN_DONE_KEY, 'false'); $imageMapper = $this->container->query('OCA\FaceRecognition\Db\ImageMapper'); $fileService = $this->container->query('OCA\FaceRecognition\Service\FileService'); $settingsService = $this->container->query('OCA\FaceRecognition\Service\SettingsService'); $addMissingImagesTask = new AddMissingImagesTask($imageMapper, $fileService, $settingsService); // Set user for which to do scanning, if any $this->context->user = $contextUser; // Since this task returns generator, iterate until it is done $generator = $addMissingImagesTask->execute($this->context); foreach ($generator as $_) { } $this->assertEquals(true, $generator->getReturn()); } }