From af357960f9b9207ab798a4aacd78c605ffae00d1 Mon Sep 17 00:00:00 2001 From: Christoph Bach Date: Wed, 12 Jun 2024 23:28:14 +0200 Subject: [PATCH 1/8] implement basic ifdo metadata parsing --- src/Converter.php | 87 +++++++++++ src/IfdoParser.php | 56 +++++++ src/ImageIfdoParser.php | 41 ----- src/MetadataIfdoServiceProvider.php | 23 ++- src/VideoIfdoParser.php | 37 +---- tests/IfdoParserTest.php | 31 ++++ tests/files/image-ifdo.json | 231 ++++++++++++++++++++++++++++ 7 files changed, 417 insertions(+), 89 deletions(-) create mode 100644 src/Converter.php create mode 100644 src/IfdoParser.php delete mode 100644 src/ImageIfdoParser.php create mode 100644 tests/IfdoParserTest.php create mode 100644 tests/files/image-ifdo.json diff --git a/src/Converter.php b/src/Converter.php new file mode 100644 index 0000000..b42287a --- /dev/null +++ b/src/Converter.php @@ -0,0 +1,87 @@ +acquisitionFormat = $acquisitionFormat; + } + + public function convert($ifdo) + { + $data = new VolumeMetadata($this->mediaType()); + foreach ($ifdo->getImageSetItems() as $name => $items) + { + if ( ! is_array($items)) + { + $items = [$items]; + } + + foreach ($items as $item) + { + $takenAt = $item['image-datetime'] ?? null; + $params = [ + 'name' => $name, + 'lat' => $this->maybeCastToFloat($item['image-latitude'] ?? null), + 'lng' => $this->maybeCastToFloat($item['image-longitude'] ?? null), + 'takenAt' => $takenAt, + 'area' => $this->maybeCastToFloat($item['image-area-square-meter'] ?? null), + 'distanceToGround' => $this->maybeCastToFloat($item['image-meters-above-ground'] ?? null), + 'gpsAltitude' => $this->maybeCastToFloat($item['image-altitude'] ?? null), + 'yaw' => $this->maybeCastToFloat($item['image-camera-yaw-degrees'] ?? null), + ]; + + if ( ! is_null($fileData = $data->getFile($name)) && ! is_null($takenAt)) + { + $fileData->addFrame(...$params); + } + else + { + $class = $this->metadataClass(); + $fileData = new $class(...$params); + $data->addFile($fileData); + } + + } + } + + return $data; + } + + /** + * Cast the value to float if it is not null or an empty string. + */ + protected function maybeCastToFloat(?string $value): ?float + { + return (is_null($value) || $value === '') ? null : floatval($value); + } + + private function mediaType() + { + switch ($this->acquisitionFormat) + { + case 'photo': + return MediaType::image(); + case 'video': + return MediaType::video(); + } + } + + private function metadataClass() + { + switch ($this->acquisitionFormat) + { + case 'photo': + return 'Biigle\Services\MetadataParsing\ImageMetadata'; + case 'video': + return 'Biigle\Services\MetadataParsing\VideoMetadata'; + } + } + +} diff --git a/src/IfdoParser.php b/src/IfdoParser.php new file mode 100644 index 0000000..f8d0eb7 --- /dev/null +++ b/src/IfdoParser.php @@ -0,0 +1,56 @@ +ifdo) + { + $file = parent::getFileObject(); + $this->ifdo = Ifdo::fromFile($file->getRealPath()); + } + + return $this->ifdo; + } + + /** + * {@inheritdoc} + */ + public function recognizesFile(): bool + { + return $this->getIfdo()->isValid(); + } + + /** + * {@inheritdoc} + */ + public function getMetadata(): VolumeMetadata + { + $converter = new Converter($this->getIfdo()->getImageSetHeader()['image-acquisition']); + return $converter->convert($this->getIfdo()); + } +} diff --git a/src/ImageIfdoParser.php b/src/ImageIfdoParser.php deleted file mode 100644 index 28c05eb..0000000 --- a/src/ImageIfdoParser.php +++ /dev/null @@ -1,41 +0,0 @@ -loadViewsFrom(__DIR__.'/resources/views', 'metadata-ifdo'); + $this->loadViewsFrom(__DIR__ . '/resources/views', 'metadata-ifdo'); - ParserFactory::extend(ImageIfdoParser::class, 'image'); + ParserFactory::extend(IfdoParser::class, 'image'); ParserFactory::extend(VideoIfdoParser::class, 'video'); $modules->register('metadata-ifdo', [ 'viewMixins' => [ @@ -29,10 +28,10 @@ public function boot(Modules $modules) } /** - * Register the service provider. - * - * @return void - */ + * Register the service provider. + * + * @return void + */ public function register() { // diff --git a/src/VideoIfdoParser.php b/src/VideoIfdoParser.php index 6c089c4..3664497 100644 --- a/src/VideoIfdoParser.php +++ b/src/VideoIfdoParser.php @@ -1,41 +1,6 @@ getMetadata(); + $this->assertEquals(MediaType::imageId(), $data->type->id); + $this->assertNull($data->name); + $this->assertNull($data->url); + $this->assertNull($data->handle); + $this->assertCount(2, $data->getFiles()); + $file = $data->getFiles()->last(); + $this->assertEquals('SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG', $file->name); + $this->assertEquals('2019-04-06 05:27:26.000000', $file->takenAt); + $this->assertEquals(-117.0214286, $file->lng); + $this->assertEquals(11.8582192, $file->lat); + $this->assertEquals(-4129.6, $file->gpsAltitude); + $this->assertEquals(2.1, $file->distanceToGround); + $this->assertEquals(5.1, $file->area); + $this->assertEquals(21, $file->yaw); + } +} diff --git a/tests/files/image-ifdo.json b/tests/files/image-ifdo.json new file mode 100644 index 0000000..e1010a2 --- /dev/null +++ b/tests/files/image-ifdo.json @@ -0,0 +1,231 @@ +{ + "image-set-header": { + "image-set-ifdo-version": "v2.1.0", + "image-altitude-meters": -4094.5, + "image-coordinate-reference-system": "EPSG:4326", + "image-coordinate-uncertainty-meters": 4.74, + "image-set-ifdo-version": "v2.1.0", + "image-datetime": "2019-04-06 04:29:27.000000", + "image-acquisition": "photo", + "image-area-square-meter": 5, + "image-capture-mode": "mixed", + "image-coordinate-reference-system": "EPSG:4326", + "image-deployment": "survey", + "image-event": "SO268-2_100-1_OFOS", + "image-illumination": "artificial light", + "image-latitude": 11.8581802, + "image-license": "CC-BY", + "image-longitude": -117.0214864, + "image-marine-zone": "seafloor", + "image-meters-above-ground": 2, + "image-navigation": "beacon", + "image-project": "SO268", + "image-quality": "raw", + "image-resolution": "mm", + "image-scale-reference": "laser marker", + "image-set-data-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@data", + "image-set-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450", + "image-set-metadata-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@metadata", + "image-set-name": "SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS", + "image-set-uuid": "d7546c4b-307f-4d42-8554-33236c577450", + "image-spectral-resolution": "rgb", + "image-copyright": "(c) GEOMAR Helmholtz Centre for Ocean Research Kiel. Contact: presse@geomar.de", + "image-abstract": "Image Abstract", + "image-set-handle": "https://hdl.handle.net/someHandle", + "image-license": { + "name": "CC-BY", + "uri": "https://creativecommons.org/licenses/by/4.0/legalcode" + }, + "image-project": { + "name": "Test Project", + "uri": "https://doi.org/example" + }, + "image-event": { + "name": "SO268-1_21-1_OFOS", + "uri": "https://portal.geomar.de/metadata/event/show/1603320?leg=348623" + }, + "image-platform": { + "name": "SO_PFM-01_OFOS", + "uri": "https://dm.pages.geomar.de/equipment/equipment/SO/PFM/SO_PFM-1_OFOS_Isitec/" + }, + "image-sensor": { + "name": "SO_CAM-1_Photo_OFOS", + "uri": "https://dm.pages.geomar.de/equipment/equipment/SO/CAM/SO_CAM-1_Photo_OFOS/" + }, + "image-pi": { + "name": "Timm Schoening", + "uri":"https://orcid.org/0000-0002-0035-3282" + }, + "image-context": { + "name": "Text Context", + "uri": "https://example.com" + }, + "image-creators": [ + { + "name": "Timm Schoening", + "uri": "https://orcid.org/0000-0002-0035-3282" + } + ], + "image-annotation-labels": [ + { + "id": "1", + "name": "Laser Point" + }, + { + "id": "2", + "name": "Animal" + }, + { + "id": "3", + "name": "Habitat" + }, + { + "id": "4", + "name": "Trash" + } + ] + }, + "image-set-items": { + "SO268-2_100-1_OFOS_SO_CAM-1_20190406_042927.JPG": { + "image-datetime": "2019-04-06 04:29:27.000000", + "image-uuid": "61056dd9-8230-42df-bf7a-395b03d09f98", + "image-hash-sha256": "0193d337cc4ed6f64ee7e6bb4ccb811b9714c0007632a64abc944ec420e81e45", + "image-handle": "https://example.com/61056dd9-8230-42df-bf7a-395b03d09f98", + "image-depth": 2248, + "image-camera-yaw-degrees": 20 + }, + "SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG": [ + { + "image-datetime": "2019-04-06 05:27:26.000000", + "image-uuid": "884dcc58-3ade-4d47-af11-2701356206c0", + "image-hash-sha256": "cc877a4f71cbc24ce0859fe9449a862d5b65dd1ca8e741015626e15d4b292da9", + "image-handle": "https://example.com/884dcc58-3ade-4d47-af11-2701356206c0", + "image-altitude": -4129.6, + "image-latitude": 11.8582192, + "image-longitude": -117.0214286, + "image-area-square-meter": 5.1, + "image-meters-above-ground": 2.1, + "image-camera-yaw-degrees": 21, + "image-annotations": [ + { + "shape": "single-pixel", + "coordinates": [[ + 3632.34, + 1736.22 + ]], + "labels": [ + { + "label": "1", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:52.000000Z" + } + ] + }, + { + "shape": "single-pixel", + "coordinates": [[ + 2857.97, + 2182.47 + ]], + "labels": [ + { + "label": "2", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:53.000000Z" + } + ] + }, + { + "shape": "single-pixel", + "coordinates": [[ + 3645.47, + 2615.59 + ]], + "labels": [ + { + "label": "3", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:54.000000Z" + } + ] + }, + { + "shape": "rectangle", + "coordinates": [[ + 1251.9, + 1604.93, + 963.15, + 2031.5, + 1195.92, + 2189.07, + 1484.67, + 1762.5 + ]], + "labels": [ + { + "label": "4", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:59.000000Z" + } + ] + }, + { + "shape": "circle", + "coordinates": [[ + 2372.34, + 3160.28, + 220.11 + ]], + "labels": [ + { + "label": "1", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:02.000000Z" + } + ] + }, + { + "shape": "polyline", + "coordinates": [[ + 4268.91, + 3678.72, + 5207.34, + 3652.47, + 5443.59, + 2871.53 + ]], + "labels": [ + { + "label": "3", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:07.000000Z" + } + ] + }, + { + "shape": "polygon", + "coordinates": [[ + 4715.16, + 1552.47, + 5627.34, + 1703.41, + 5666.72, + 948.72, + 4918.59, + 699.34, + 4715.16, + 1552.47 + ]], + "labels": [ + { + "label": "4", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:14.000000Z" + } + ] + } + ] + } + ] + } +} From a505bd86171dfec7d8296606fc01485ec2b689ef Mon Sep 17 00:00:00 2001 From: Christoph Bach Date: Thu, 25 Jul 2024 18:03:16 +0200 Subject: [PATCH 2/8] add annotation parsing --- src/Converter.php | 121 +- src/MetadataIfdoServiceProvider.php | 2 +- src/VideoIfdoParser.php | 6 - tests/IfdoParserTest.php | 29 + tests/MetadataIfdoServiceProviderTest.php | 6 +- tests/files/image-ifdo.json | 270 ++-- tests/files/video-example-1.json | 1447 +++++++++++++++++++++ 7 files changed, 1736 insertions(+), 145 deletions(-) delete mode 100644 src/VideoIfdoParser.php create mode 100644 tests/files/video-example-1.json diff --git a/src/Converter.php b/src/Converter.php index b42287a..4565205 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -2,11 +2,18 @@ namespace Biigle\Modules\MetadataIfdo; use Biigle\MediaType; +use Biigle\Services\MetadataParsing\Label; +use Biigle\Services\MetadataParsing\LabelAndUser; +use Biigle\Services\MetadataParsing\User; use Biigle\Services\MetadataParsing\VolumeMetadata; +use Biigle\Shape; +use Illuminate\Support\Arr; class Converter { private $acquisitionFormat = ''; + private $annotators = []; + private $labels = []; public function __construct($acquisitionFormat) { @@ -15,10 +22,13 @@ public function __construct($acquisitionFormat) public function convert($ifdo) { + $this->annotators = $this->extractIfdoAnnotators($ifdo->getImageSetHeader()['image-annotation-creators']); + $this->labels = $this->extractIfdoLabels($ifdo->getImageSetHeader()['image-annotation-labels']); + $data = new VolumeMetadata($this->mediaType()); foreach ($ifdo->getImageSetItems() as $name => $items) { - if ( ! is_array($items)) + if ( ! array_is_list($items)) { $items = [$items]; } @@ -39,12 +49,18 @@ public function convert($ifdo) if ( ! is_null($fileData = $data->getFile($name)) && ! is_null($takenAt)) { - $fileData->addFrame(...$params); + $fileData->addFrame(...Arr::except($params, ['name'])); } else { $class = $this->metadataClass(); $fileData = new $class(...$params); + + if (isset($item['image-annotations'])) + { + $this->addAnnotations($fileData, $item['image-annotations']); + } + $data->addFile($fileData); } @@ -62,6 +78,97 @@ protected function maybeCastToFloat(?string $value): ?float return (is_null($value) || $value === '') ? null : floatval($value); } + private function addAnnotations($fileData, $ifdoAnnotations) + { + foreach ($ifdoAnnotations as $ifdoAnnotation) + { + $fileData->addAnnotation($this->ifdoToBiigleAnnotation($ifdoAnnotation)); + } + } + + private function ifdoToBiigleAnnotation($ifdoAnnotation) + { + $class = $this->annotationClass(); + $params = [ + $this->ifdoShapeToBiigleShape($ifdoAnnotation['shape']), + $ifdoAnnotation['coordinates'], + $this->ifdoLabelsToBiigleLabelAndUsers($ifdoAnnotation['labels']), + ]; + + if (isset($ifdoAnnotation['frames'])) + { + $params[] = $ifdoAnnotation['frames']; + } + + $annotation = new $class(...$params); + + return $annotation; + } + + private function ifdoLabelsToBiigleLabelAndUsers($ifdoLabels) + { + return array_map([$this, 'ifdoLabelToBiigleLabelAndUser'], $ifdoLabels); + } + + private function ifdoLabelToBiigleLabelAndUser($ifdoLabel) + { + return new LabelAndUser( + $this->labelForId($ifdoLabel['label']), + $this->userForId($ifdoLabel['annotator']) + ); + } + + private function extractIfdoLabels($ifdoLabels) + { + $labels = []; + foreach ($ifdoLabels as $ifdoLabel) + { + $labels[$ifdoLabel['id']] = new Label($ifdoLabel['id'], $ifdoLabel['name']); + } + return $labels; + } + + private function extractIfdoAnnotators($ifdoAnnotators) + { + $annotators = []; + foreach ($ifdoAnnotators as $ifdoAnnotator) + { + $annotators[$ifdoAnnotator['id']] = new User($ifdoAnnotator['id'], $ifdoAnnotator['name']); + } + return $annotators; + } + + private function userForId($id) + { + return $this->annotators[$id]; + } + + private function labelForId($id) + { + return $this->labels[$id]; + } + + private function ifdoShapeToBiigleShape($shape) + { + switch ($shape) + { + case 'single-pixel': + return Shape::point(); + case 'polyline': + return Shape::line(); + case 'polygon': + return Shape::polygon(); + case 'circle': + return Shape::circle(); + case 'rectangle': + return Shape::rectangle(); + case 'ellipse': + return Shape::ellipse(); + case 'whole-frame': + return Shape::wholeFrame(); + } + } + private function mediaType() { switch ($this->acquisitionFormat) @@ -84,4 +191,14 @@ private function metadataClass() } } + private function annotationClass() + { + switch ($this->acquisitionFormat) + { + case 'photo': + return 'Biigle\Services\MetadataParsing\ImageAnnotation'; + case 'video': + return 'Biigle\Services\MetadataParsing\VideoAnnotation'; + } + } } diff --git a/src/MetadataIfdoServiceProvider.php b/src/MetadataIfdoServiceProvider.php index f4d1078..11c6074 100644 --- a/src/MetadataIfdoServiceProvider.php +++ b/src/MetadataIfdoServiceProvider.php @@ -18,7 +18,7 @@ public function boot(Modules $modules) $this->loadViewsFrom(__DIR__ . '/resources/views', 'metadata-ifdo'); ParserFactory::extend(IfdoParser::class, 'image'); - ParserFactory::extend(VideoIfdoParser::class, 'video'); + ParserFactory::extend(IfdoParser::class, 'video'); $modules->register('metadata-ifdo', [ 'viewMixins' => [ 'metadataParsers', diff --git a/src/VideoIfdoParser.php b/src/VideoIfdoParser.php deleted file mode 100644 index 3664497..0000000 --- a/src/VideoIfdoParser.php +++ /dev/null @@ -1,6 +0,0 @@ -assertEquals(2.1, $file->distanceToGround); $this->assertEquals(5.1, $file->area); $this->assertEquals(21, $file->yaw); + + $this->assertCount(7, $file->getAnnotations()); + $annotation = array_pop($file->annotations); + + $this->assertEquals(3, $annotation->shape->id); + $this->assertEquals('Hans Wurst', $annotation->labels[0]->user->name); + } + + public function testGetVideoMetadata() + { + $file = new File(__DIR__ . "/files/video-example-1.json"); + $parser = new IfdoParser($file); + $data = $parser->getMetadata(); + $this->assertEquals(MediaType::videoId(), $data->type->id); + $this->assertNull($data->name); + $this->assertNull($data->url); + $this->assertNull($data->handle); + $this->assertCount(1, $data->getFiles()); + $file = $data->getFiles()->last(); + $this->assertEquals('SO242_2_163-1_LowerHD.mp4', $file->name); + $this->assertEquals('2019-04-06 04:29:27.000000', $file->takenAt); + $this->assertEquals(-117.0214286, $file->lng); + $this->assertEquals(11.8582192, $file->lat); + $this->assertEquals(-4129.6, $file->gpsAltitude); + $this->assertEquals(2.1, $file->distanceToGround); + $this->assertEquals(5.1, $file->area); + $this->assertEquals(21, $file->yaw); + + // var_dump($file); } } diff --git a/tests/MetadataIfdoServiceProviderTest.php b/tests/MetadataIfdoServiceProviderTest.php index 1c8c390..6809b75 100644 --- a/tests/MetadataIfdoServiceProviderTest.php +++ b/tests/MetadataIfdoServiceProviderTest.php @@ -1,11 +1,11 @@ markTestIncomplete('implement metadata parser'); - $file = new File(__DIR__."/files/image-ifdo.json"); + $file = new File(__DIR__ . "/files/image-ifdo.json"); $parser = ParserFactory::getParserForFile($file, 'image'); $this->assertInstanceOf(ImageIfdoParser::class, $parser); } @@ -28,7 +28,7 @@ public function testGetVideoIfdo() { $this->markTestIncomplete('implement metadata parser'); - $file = new File(__DIR__."/files/video-ifdo.json"); + $file = new File(__DIR__ . "/files/video-ifdo.json"); $parser = ParserFactory::getParserForFile($file, 'video'); $this->assertInstanceOf(VideoIfdoParser::class, $parser); } diff --git a/tests/files/image-ifdo.json b/tests/files/image-ifdo.json index e1010a2..6abc626 100644 --- a/tests/files/image-ifdo.json +++ b/tests/files/image-ifdo.json @@ -83,6 +83,12 @@ "id": "4", "name": "Trash" } + ], + "image-annotation-creators": [ + { + "id": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "name": "Hans Wurst" + } ] }, "image-set-items": { @@ -94,138 +100,136 @@ "image-depth": 2248, "image-camera-yaw-degrees": 20 }, - "SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG": [ - { - "image-datetime": "2019-04-06 05:27:26.000000", - "image-uuid": "884dcc58-3ade-4d47-af11-2701356206c0", - "image-hash-sha256": "cc877a4f71cbc24ce0859fe9449a862d5b65dd1ca8e741015626e15d4b292da9", - "image-handle": "https://example.com/884dcc58-3ade-4d47-af11-2701356206c0", - "image-altitude": -4129.6, - "image-latitude": 11.8582192, - "image-longitude": -117.0214286, - "image-area-square-meter": 5.1, - "image-meters-above-ground": 2.1, - "image-camera-yaw-degrees": 21, - "image-annotations": [ - { - "shape": "single-pixel", - "coordinates": [[ - 3632.34, - 1736.22 - ]], - "labels": [ - { - "label": "1", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T13:59:52.000000Z" - } - ] - }, - { - "shape": "single-pixel", - "coordinates": [[ - 2857.97, - 2182.47 - ]], - "labels": [ - { - "label": "2", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T13:59:53.000000Z" - } - ] - }, - { - "shape": "single-pixel", - "coordinates": [[ - 3645.47, - 2615.59 - ]], - "labels": [ - { - "label": "3", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T13:59:54.000000Z" - } - ] - }, - { - "shape": "rectangle", - "coordinates": [[ - 1251.9, - 1604.93, - 963.15, - 2031.5, - 1195.92, - 2189.07, - 1484.67, - 1762.5 - ]], - "labels": [ - { - "label": "4", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T13:59:59.000000Z" - } - ] - }, - { - "shape": "circle", - "coordinates": [[ - 2372.34, - 3160.28, - 220.11 - ]], - "labels": [ - { - "label": "1", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T14:00:02.000000Z" - } - ] - }, - { - "shape": "polyline", - "coordinates": [[ - 4268.91, - 3678.72, - 5207.34, - 3652.47, - 5443.59, - 2871.53 - ]], - "labels": [ - { - "label": "3", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T14:00:07.000000Z" - } - ] - }, - { - "shape": "polygon", - "coordinates": [[ - 4715.16, - 1552.47, - 5627.34, - 1703.41, - 5666.72, - 948.72, - 4918.59, - 699.34, - 4715.16, - 1552.47 - ]], - "labels": [ - { - "label": "4", - "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", - "created-at": "2023-11-22T14:00:14.000000Z" - } - ] - } - ] - } - ] + "SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG": { + "image-datetime": "2019-04-06 05:27:26.000000", + "image-uuid": "884dcc58-3ade-4d47-af11-2701356206c0", + "image-hash-sha256": "cc877a4f71cbc24ce0859fe9449a862d5b65dd1ca8e741015626e15d4b292da9", + "image-handle": "https://example.com/884dcc58-3ade-4d47-af11-2701356206c0", + "image-altitude": -4129.6, + "image-latitude": 11.8582192, + "image-longitude": -117.0214286, + "image-area-square-meter": 5.1, + "image-meters-above-ground": 2.1, + "image-camera-yaw-degrees": 21, + "image-annotations": [ + { + "shape": "single-pixel", + "coordinates": [[ + 3632.34, + 1736.22 + ]], + "labels": [ + { + "label": "1", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:52.000000Z" + } + ] + }, + { + "shape": "single-pixel", + "coordinates": [[ + 2857.97, + 2182.47 + ]], + "labels": [ + { + "label": "2", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:53.000000Z" + } + ] + }, + { + "shape": "single-pixel", + "coordinates": [[ + 3645.47, + 2615.59 + ]], + "labels": [ + { + "label": "3", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:54.000000Z" + } + ] + }, + { + "shape": "rectangle", + "coordinates": [[ + 1251.9, + 1604.93, + 963.15, + 2031.5, + 1195.92, + 2189.07, + 1484.67, + 1762.5 + ]], + "labels": [ + { + "label": "4", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T13:59:59.000000Z" + } + ] + }, + { + "shape": "circle", + "coordinates": [[ + 2372.34, + 3160.28, + 220.11 + ]], + "labels": [ + { + "label": "1", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:02.000000Z" + } + ] + }, + { + "shape": "polyline", + "coordinates": [[ + 4268.91, + 3678.72, + 5207.34, + 3652.47, + 5443.59, + 2871.53 + ]], + "labels": [ + { + "label": "3", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:07.000000Z" + } + ] + }, + { + "shape": "polygon", + "coordinates": [[ + 4715.16, + 1552.47, + 5627.34, + 1703.41, + 5666.72, + 948.72, + 4918.59, + 699.34, + 4715.16, + 1552.47 + ]], + "labels": [ + { + "label": "4", + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:00:14.000000Z" + } + ] + } + ] + } } } diff --git a/tests/files/video-example-1.json b/tests/files/video-example-1.json new file mode 100644 index 0000000..7855365 --- /dev/null +++ b/tests/files/video-example-1.json @@ -0,0 +1,1447 @@ +{ + "image-set-header": + { + "image-abstract": "Image Abstract", + "image-acquisition": "video", + "image-altitude-meters": -4094.5, + "image-annotation-creators": + [ + { + "id": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "name": "Timm Schoening" + } + ], + "image-annotation-labels": + [ + { + "id": "some-uuid", + "name": "Animal" + } + ], + "image-area-square-meter": 5, + "image-context": + { + "name": "Text Context", + "uri": "https://example.com" + }, + "image-coordinate-reference-system": "EPSG:4326", + "image-coordinate-uncertainty-meters": 4.74, + "image-copyright": "(c) GEOMAR Helmholtz Centre for Ocean Research Kiel. Contact: presse@geomar.de", + "image-datetime": "2019-04-06 04:29:27.000000", + "image-event": + { + "name": "SO268-1_21-1_OFOS", + "uri": "https://portal.geomar.de/metadata/event/show/1603320?leg=348623" + }, + "image-latitude": 11.8581802, + "image-license": + { + "name": "CC-BY", + "uri": "https://creativecommons.org/licenses/by/4.0/legalcode" + }, + "image-longitude": -117.0214864, + "image-meters-above-ground": 2, + "image-pi": + { + "name": "Timm Schoening", + "uri": "https://orcid.org/0000-0002-0035-3282" + }, + "image-platform": + { + "name": "SO_PFM-01_OFOS", + "uri": "https://dm.pages.geomar.de/equipment/equipment/SO/PFM/SO_PFM-1_OFOS_Isitec/" + }, + "image-project": + { + "name": "Test Project", + "uri": "https://doi.org/example" + }, + "image-sensor": + { + "name": "SO_CAM-1_Photo_OFOS", + "uri": "https://dm.pages.geomar.de/equipment/equipment/SO/CAM/SO_CAM-1_Photo_OFOS/" + }, + "image-set-data-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@data", + "image-set-handle": "https://hdl.handle.net/someHandle", + "image-set-ifdo-version": "v2.1.0", + "image-set-metadata-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@metadata", + "image-set-name": "SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS", + "image-set-uuid": "d7546c4b-307f-4d42-8554-33236c577450" + }, + "image-set-items": + { + "SO242_2_163-1_LowerHD.mp4": + [ + { + "image-altitude": -4129.6, + "image-annotations": + [ + { + "coordinates": + [ + [ + 681.7, + 521.44, + 99.17 + ], + [ + 681, + 521, + 99 + ], + [ + 684, + 516, + 97 + ], + [ + 681, + 511, + 97 + ], + [ + 684, + 506, + 97 + ], + [ + 686, + 501, + 95 + ], + [ + 690, + 496, + 97 + ], + [ + 692, + 491, + 99 + ], + [ + 694, + 485, + 97 + ], + [ + 697, + 480, + 99 + ], + [ + 698, + 474, + 99 + ], + [ + 699, + 469, + 99 + ], + [ + 697, + 464, + 97 + ], + [ + 693, + 459, + 99 + ], + [ + 688, + 455, + 99 + ], + [ + 683, + 452, + 97 + ], + [ + 677, + 450, + 97 + ], + [ + 672, + 447, + 97 + ], + [ + 666, + 444, + 99 + ], + [ + 660, + 440, + 95 + ], + [ + 655, + 436, + 97 + ], + [ + 650, + 431, + 99 + ], + [ + 646, + 427, + 97 + ], + [ + 642, + 423, + 97 + ], + [ + 638, + 419, + 97 + ], + [ + 634, + 415, + 97 + ], + [ + 631, + 410, + 97 + ], + [ + 629, + 405, + 97 + ], + [ + 634, + 403, + 97 + ], + [ + 639, + 405, + 97 + ], + [ + 643, + 410, + 95 + ], + [ + 645, + 415, + 97 + ], + [ + 649, + 420, + 97 + ], + [ + 653, + 425, + 97 + ], + [ + 656, + 431, + 97 + ], + [ + 657, + 436, + 97 + ], + [ + 657, + 442, + 95 + ], + [ + 656, + 447, + 95 + ], + [ + 653.5, + 452.5, + 93.5 + ], + [ + 649, + 456, + 95 + ], + [ + 644.5, + 458.5, + 91.5 + ], + [ + 639.5, + 459.5, + 93.5 + ], + [ + 634.5, + 455.5, + 89.5 + ], + [ + 630.5, + 451.5, + 89.5 + ], + [ + 628.5, + 446.5, + 89.5 + ], + [ + 628.5, + 440.5, + 89.5 + ], + [ + 629, + 435, + 88 + ], + [ + 631, + 430, + 88 + ], + [ + 633, + 425, + 86 + ], + [ + 636, + 420, + 86 + ], + [ + 639.5, + 415.5, + 84.5 + ], + [ + 641.5, + 410.5, + 84.5 + ], + [ + 644, + 406, + 86 + ], + [ + 638.5, + 404.5, + 89.5 + ], + [ + 632.5, + 405.5, + 91.5 + ], + [ + 626.5, + 405.5, + 91.5 + ], + [ + 621.5, + 406.5, + 93.5 + ], + [ + 615.5, + 406.5, + 93.5 + ], + [ + 610.5, + 409.5, + 93.5 + ], + [ + 607.5, + 414.5, + 93.5 + ], + [ + 603.5, + 418.5, + 91.5 + ], + [ + 601.5, + 423.5, + 91.5 + ], + [ + 600.5, + 428.5, + 93.5 + ], + [ + 599.5, + 433.5, + 93.5 + ], + [ + 598.5, + 438.5, + 93.5 + ], + [ + 598.5, + 444.5, + 93.5 + ], + [ + 593.5, + 443.5, + 93.5 + ], + [ + 589.5, + 439.5, + 93.5 + ], + [ + 585.5, + 435.5, + 93.5 + ], + [ + 580.5, + 431.5, + 93.5 + ], + [ + 577.5, + 425.5, + 93.5 + ], + [ + 573.5, + 420.5, + 93.5 + ], + [ + 571.5, + 415.5, + 93.5 + ], + [ + 569.5, + 410.5, + 93.5 + ], + [ + 568.5, + 405.5, + 93.5 + ], + [ + 569.5, + 400.5, + 93.5 + ], + [ + 574.5, + 397.5, + 91.5 + ], + [ + 576, + 403, + 95 + ], + [ + 577.5, + 408.5, + 93.5 + ], + [ + 575, + 413, + 95 + ], + [ + 571, + 418, + 95 + ], + [ + 567, + 422, + 95 + ], + [ + 561, + 424, + 95 + ], + [ + 555, + 424, + 95 + ], + [ + 549, + 424, + 95 + ], + [ + 543, + 424, + 97 + ], + [ + 539, + 420, + 97 + ], + [ + 538, + 415, + 97 + ], + [ + 540, + 410, + 97 + ], + [ + 538, + 405, + 99 + ], + [ + 533, + 404, + 97 + ], + [ + 528, + 401, + 97 + ], + [ + 525, + 396, + 97 + ], + [ + 521, + 390, + 97 + ], + [ + 518, + 385, + 97 + ], + [ + 515, + 380, + 97 + ], + [ + 516, + 375, + 97 + ], + [ + 511, + 374, + 97 + ], + [ + 506, + 375, + 97 + ], + [ + 501, + 376, + 97 + ], + [ + 495, + 375, + 97 + ], + [ + 489, + 375, + 95 + ], + [ + 483, + 375, + 97 + ], + [ + 478, + 376, + 97 + ], + [ + 473, + 377, + 95 + ], + [ + 478, + 375, + 95 + ], + [ + 481, + 370, + 95 + ], + [ + 479.5, + 364.5, + 93.5 + ], + [ + 475, + 361, + 95 + ], + [ + 472.5, + 355.5, + 93.5 + ], + [ + 468.5, + 350.5, + 93.5 + ], + [ + 466.5, + 345.5, + 93.5 + ], + [ + 461.5, + 342.5, + 93.5 + ], + [ + 456.5, + 340.5, + 93.5 + ], + [ + 451.5, + 339.5, + 93.5 + ], + [ + 446.5, + 340.5, + 93.5 + ], + [ + 441.5, + 339.5, + 93.5 + ], + [ + 436, + 340, + 95 + ], + [ + 430, + 341, + 95 + ], + [ + 425, + 345, + 95 + ], + [ + 420.5, + 349.5, + 93.5 + ], + [ + 416.5, + 354.5, + 93.5 + ], + [ + 413.5, + 360.5, + 91.5 + ], + [ + 413.5, + 367.5, + 91.5 + ], + [ + 413.5, + 373.5, + 91.5 + ], + [ + 414.5, + 378.5, + 89.5 + ], + [ + 417.5, + 384.5, + 89.5 + ], + [ + 421.5, + 390.5, + 89.5 + ], + [ + 421.5, + 396.5, + 89.5 + ], + [ + 416.5, + 400.5, + 89.5 + ], + [ + 411.5, + 402.5, + 89.5 + ], + [ + 409.5, + 396.5, + 89.5 + ], + [ + 411, + 390, + 88 + ], + [ + 412, + 384, + 88 + ], + [ + 411, + 378, + 88 + ], + [ + 412, + 369, + 86 + ], + [ + 410.5, + 361.5, + 84.5 + ], + [ + 411.5, + 354.5, + 84.5 + ], + [ + 409, + 346, + 86 + ], + [ + 408, + 340, + 86 + ], + [ + 408, + 328, + 86 + ], + [ + 406, + 320, + 86 + ], + [ + 404, + 314, + 86 + ], + [ + 404, + 306, + 86 + ], + [ + 402.5, + 296.5, + 84.5 + ], + [ + 401.5, + 288.5, + 84.5 + ], + [ + 399, + 279, + 83 + ], + [ + 399, + 271, + 83 + ], + [ + 398, + 261, + 83 + ], + [ + 395, + 251, + 83 + ], + [ + 393, + 244, + 83 + ], + [ + 391, + 233, + 83 + ], + [ + 388, + 224, + 83 + ], + [ + 385, + 214, + 83 + ], + [ + 382, + 206, + 83 + ], + [ + 379.5, + 197.5, + 84.5 + ], + [ + 375.5, + 188.5, + 84.5 + ], + [ + 372.5, + 181.5, + 84.5 + ], + [ + 370, + 174, + 86 + ], + [ + 366, + 166, + 86 + ], + [ + 362, + 158, + 86 + ], + [ + 358, + 149, + 86 + ], + [ + 355, + 141, + 86 + ], + [ + 351, + 133, + 86 + ], + [ + 347, + 125, + 86 + ], + [ + 343, + 118, + 86 + ], + [ + 339, + 110, + 86 + ], + [ + 335, + 104, + 86 + ], + [ + 330.5, + 96.5, + 84.5 + ], + [ + 327.5, + 91.5, + 84.5 + ], + [ + 323.5, + 86.5, + 84.5 + ], + [ + 318.5, + 82.5, + 84.5 + ], + [ + 315.5, + 77.5, + 84.5 + ], + [ + 311, + 74, + 86 + ], + [ + 306, + 69, + 86 + ], + [ + 299, + 64, + 86 + ], + [ + 294, + 61, + 86 + ], + [ + 286.5, + 57.5, + 84.5 + ], + [ + 279.5, + 54.5, + 84.5 + ], + [ + 270.5, + 51.5, + 84.5 + ], + [ + 263.5, + 50.5, + 84.5 + ], + [ + 254.5, + 50.5, + 84.5 + ], + [ + 246.5, + 51.5, + 84.5 + ], + [ + 239.5, + 54.5, + 84.5 + ], + [ + 229.5, + 55.5, + 84.5 + ], + [ + 221.5, + 59.5, + 84.5 + ], + [ + 217, + 62, + 86 + ], + [ + 208, + 67, + 86 + ], + [ + 201, + 74, + 88 + ], + [ + 196, + 79, + 88 + ], + [ + 188, + 89, + 88 + ], + [ + 185, + 94, + 88 + ], + [ + 180, + 99, + 88 + ], + [ + 176, + 105, + 88 + ], + [ + 170.5, + 111.5, + 89.5 + ], + [ + 166.5, + 116.5, + 89.5 + ], + [ + 161.5, + 123.5, + 89.5 + ], + [ + 156.5, + 129.5, + 89.5 + ], + [ + 153.5, + 136.5, + 89.5 + ], + [ + 148.5, + 143.5, + 89.5 + ], + [ + 143.5, + 150.5, + 89.5 + ], + [ + 138.5, + 157.5, + 89.5 + ], + [ + 133.5, + 163.5, + 89.5 + ], + [ + 130.5, + 171.5, + 89.5 + ], + [ + 123.5, + 178.5, + 89.5 + ], + [ + 118.5, + 187.5, + 89.5 + ], + [ + 114.5, + 193.5, + 89.5 + ], + [ + 110.5, + 202.5, + 91.5 + ], + [ + 104.5, + 210.5, + 91.5 + ], + [ + 100.5, + 219.5, + 91.5 + ], + [ + 95.5, + 228.5, + 91.5 + ], + [ + 89.5, + 237.5, + 89.5 + ], + [ + 85, + 246, + 88 + ], + [ + 80, + 255, + 86 + ], + [ + 74.5, + 264.5, + 84.5 + ], + [ + 67.5, + 273.5, + 84.5 + ], + [ + 61, + 284, + 83 + ], + [ + 56, + 294, + 83 + ], + [ + 48, + 304, + 83 + ], + [ + 40, + 314, + 83 + ], + [ + 28, + 324, + 83 + ], + [ + 16, + 329, + 83 + ] + ], + "frames": + [ + 0.402947, + 0.48, + 1, + 1.32, + 1.64, + 1.84, + 2.04, + 2.24, + 2.4, + 2.6, + 2.8, + 3.04, + 3.36, + 3.64, + 3.84, + 4.04, + 4.16, + 4.28, + 4.4, + 4.52, + 4.64, + 4.76, + 4.88, + 5, + 5.12, + 5.28, + 5.44, + 5.68, + 6.08, + 6.36, + 6.64, + 6.84, + 7.04, + 7.24, + 7.48, + 7.64, + 7.88, + 8.08, + 8.32, + 8.52, + 8.72, + 9, + 9.24, + 9.52, + 9.72, + 9.92, + 10.12, + 10.32, + 10.48, + 10.72, + 10.92, + 11.16, + 11.36, + 12.44, + 12.76, + 13.04, + 13.28, + 13.52, + 13.88, + 14.36, + 14.8, + 15.08, + 15.36, + 15.6, + 15.92, + 16.32, + 16.88, + 17.16, + 17.4, + 17.6, + 17.8, + 18, + 18.16, + 18.36, + 18.56, + 18.8, + 19.44, + 19.8, + 20.04, + 20.28, + 20.56, + 20.8, + 21, + 21.2, + 21.4, + 21.76, + 22.24, + 22.6, + 23.12, + 24.2, + 24.64, + 25, + 25.28, + 25.52, + 25.72, + 26.04, + 26.4, + 28.12, + 28.36, + 28.6, + 28.8, + 29.04, + 29.32, + 29.64, + 30.08, + 31.24, + 31.64, + 32.48, + 32.84, + 33.32, + 33.64, + 34.04, + 34.68, + 35.48, + 35.8, + 36, + 36.2, + 36.4, + 36.6, + 36.8, + 37, + 37.24, + 37.52, + 37.84, + 38.08, + 38.32, + 38.48, + 38.68, + 38.88, + 39, + 39.16, + 39.48, + 39.6, + 39.68, + 39.76, + 39.84, + 39.92, + 39.96, + 40.04, + 40.08, + 40.16, + 40.2, + 40.24, + 40.28, + 40.32, + 40.36, + 40.4, + 40.44, + 40.48, + 40.52, + 40.56, + 40.6, + 40.64, + 40.68, + 40.72, + 40.76, + 40.8, + 40.84, + 40.88, + 40.92, + 40.96, + 41, + 41.04, + 41.08, + 41.12, + 41.16, + 41.2, + 41.24, + 41.28, + 41.32, + 41.36, + 41.4, + 41.44, + 41.48, + 41.52, + 41.6, + 41.64, + 41.72, + 41.8, + 41.88, + 41.96, + 42.04, + 42.12, + 42.2, + 42.28, + 42.36, + 42.4, + 42.48, + 42.56, + 42.6, + 42.68, + 42.72, + 42.76, + 42.8, + 42.84, + 42.88, + 42.92, + 42.96, + 43, + 43.04, + 43.08, + 43.12, + 43.16, + 43.2, + 43.24, + 43.28, + 43.32, + 43.36, + 43.4, + 43.44, + 43.48, + 43.52, + 43.56, + 43.6, + 43.64, + 43.68, + 43.72, + 43.76, + 43.8, + 43.84, + 43.88, + 43.92 + ], + "labels": + [ + { + "annotator": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "created-at": "2023-11-22T14:02:18.000000Z", + "label": "some-uuid" + } + ], + "shape": "circle" + } + ], + "image-area-square-meter": 5.1, + "image-camera-yaw-degrees": 21, + "image-datetime": "2019-04-06 04:29:27.000000", + "image-latitude": 11.8582192, + "image-longitude": -117.0214286, + "image-meters-above-ground": 2.1 + }, + { + "image-altitude": -4129.6, + "image-area-square-meter": 5.1, + "image-camera-yaw-degrees": 21, + "image-datetime": "2019-04-06 04:30:27.000000", + "image-latitude": 11.8582192, + "image-longitude": -117.0214286, + "image-meters-above-ground": 2.1 + } + ] + } +} From 624951962dd397313d34bb65021822e85ca90940 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Tue, 3 Sep 2024 16:13:39 +0200 Subject: [PATCH 3/8] Update dependency version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 780453f..e816e13 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } }, "require": { - "biigle/ifdo":"^0.3 || ^1.0" + "biigle/ifdo":"^0.4 || ^1.0" }, "extra": { "laravel": { From e2a18ba4ef3659b64b9c6f6c52de6f3865d943c5 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Tue, 3 Sep 2024 16:17:53 +0200 Subject: [PATCH 4/8] Fix image annotation coordinate parsing --- src/Converter.php | 11 ++++- tests/IfdoParserTest.php | 50 +++++++++++++---------- tests/MetadataIfdoServiceProviderTest.php | 14 ++----- 3 files changed, 42 insertions(+), 33 deletions(-) diff --git a/src/Converter.php b/src/Converter.php index 4565205..e358f7c 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -91,7 +91,7 @@ private function ifdoToBiigleAnnotation($ifdoAnnotation) $class = $this->annotationClass(); $params = [ $this->ifdoShapeToBiigleShape($ifdoAnnotation['shape']), - $ifdoAnnotation['coordinates'], + $this->ifdoParseCoordinates($ifdoAnnotation['coordinates']), $this->ifdoLabelsToBiigleLabelAndUsers($ifdoAnnotation['labels']), ]; @@ -201,4 +201,13 @@ private function annotationClass() return 'Biigle\Services\MetadataParsing\VideoAnnotation'; } } + + private function ifdoParseCoordinates($coordinates) + { + if ($this->acquisitionFormat === 'photo') { + return $coordinates[0]; + } + + return $coordinates; + } } diff --git a/tests/IfdoParserTest.php b/tests/IfdoParserTest.php index b80df41..2e3b5b5 100644 --- a/tests/IfdoParserTest.php +++ b/tests/IfdoParserTest.php @@ -4,6 +4,7 @@ use Biigle\MediaType; use Biigle\Modules\MetadataIfdo\IfdoParser; use Symfony\Component\HttpFoundation\File\File; +use Biigle\Shape; use TestCase; class ImageIfdoParserTest extends TestCase @@ -13,26 +14,27 @@ public function testGetMetadata() $file = new File(__DIR__ . "/files/image-ifdo.json"); $parser = new IfdoParser($file); $data = $parser->getMetadata(); - $this->assertEquals(MediaType::imageId(), $data->type->id); + $this->assertSame(MediaType::imageId(), $data->type->id); $this->assertNull($data->name); $this->assertNull($data->url); $this->assertNull($data->handle); $this->assertCount(2, $data->getFiles()); $file = $data->getFiles()->last(); - $this->assertEquals('SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG', $file->name); - $this->assertEquals('2019-04-06 05:27:26.000000', $file->takenAt); - $this->assertEquals(-117.0214286, $file->lng); - $this->assertEquals(11.8582192, $file->lat); - $this->assertEquals(-4129.6, $file->gpsAltitude); - $this->assertEquals(2.1, $file->distanceToGround); - $this->assertEquals(5.1, $file->area); - $this->assertEquals(21, $file->yaw); + $this->assertSame('SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG', $file->name); + $this->assertSame('2019-04-06 05:27:26.000000', $file->takenAt); + $this->assertSame(-117.0214286, $file->lng); + $this->assertSame(11.8582192, $file->lat); + $this->assertSame(-4129.6, $file->gpsAltitude); + $this->assertSame(2.1, $file->distanceToGround); + $this->assertSame(5.1, $file->area); + $this->assertSame(21.0, $file->yaw); $this->assertCount(7, $file->getAnnotations()); $annotation = array_pop($file->annotations); - $this->assertEquals(3, $annotation->shape->id); - $this->assertEquals('Hans Wurst', $annotation->labels[0]->user->name); + $this->assertSame(Shape::polygonId(), $annotation->shape->id); + $this->assertSame('Hans Wurst', $annotation->labels[0]->user->name); + $this->assertSame(4715.16, $annotation->points[0]); } public function testGetVideoMetadata() @@ -40,21 +42,27 @@ public function testGetVideoMetadata() $file = new File(__DIR__ . "/files/video-example-1.json"); $parser = new IfdoParser($file); $data = $parser->getMetadata(); - $this->assertEquals(MediaType::videoId(), $data->type->id); + $this->assertSame(MediaType::videoId(), $data->type->id); $this->assertNull($data->name); $this->assertNull($data->url); $this->assertNull($data->handle); $this->assertCount(1, $data->getFiles()); $file = $data->getFiles()->last(); - $this->assertEquals('SO242_2_163-1_LowerHD.mp4', $file->name); - $this->assertEquals('2019-04-06 04:29:27.000000', $file->takenAt); - $this->assertEquals(-117.0214286, $file->lng); - $this->assertEquals(11.8582192, $file->lat); - $this->assertEquals(-4129.6, $file->gpsAltitude); - $this->assertEquals(2.1, $file->distanceToGround); - $this->assertEquals(5.1, $file->area); - $this->assertEquals(21, $file->yaw); + $this->assertSame('SO242_2_163-1_LowerHD.mp4', $file->name); + $this->assertSame('2019-04-06 04:29:27.000000', $file->takenAt); + $this->assertSame(-117.0214286, $file->lng); + $this->assertSame(11.8582192, $file->lat); + $this->assertSame(-4129.6, $file->gpsAltitude); + $this->assertSame(2.1, $file->distanceToGround); + $this->assertSame(5.1, $file->area); + $this->assertSame(21.0, $file->yaw); - // var_dump($file); + $this->assertCount(1, $file->getAnnotations()); + $annotation = array_pop($file->annotations); + + $this->assertSame(Shape::circleId(), $annotation->shape->id); + $this->assertSame('Timm Schoening', $annotation->labels[0]->user->name); + $this->assertSame(681.7, $annotation->points[0][0]); + $this->assertSame(0.402947, $annotation->frames[0]); } } diff --git a/tests/MetadataIfdoServiceProviderTest.php b/tests/MetadataIfdoServiceProviderTest.php index 6809b75..9c7c106 100644 --- a/tests/MetadataIfdoServiceProviderTest.php +++ b/tests/MetadataIfdoServiceProviderTest.php @@ -1,7 +1,7 @@ markTestIncomplete('implement metadata parser'); - - $file = new File(__DIR__ . "/files/image-ifdo.json"); - $parser = ParserFactory::getParserForFile($file, 'image'); - $this->assertInstanceOf(ImageIfdoParser::class, $parser); + $this->assertTrue(ParserFactory::has('image', IfdoParser::class)); } public function testGetVideoIfdo() { - $this->markTestIncomplete('implement metadata parser'); - - $file = new File(__DIR__ . "/files/video-ifdo.json"); - $parser = ParserFactory::getParserForFile($file, 'video'); - $this->assertInstanceOf(VideoIfdoParser::class, $parser); + $this->assertTrue(ParserFactory::has('video', IfdoParser::class)); } } From 5a985f295e6fbb6f18f3f2cfcd0848b5f421d6dd Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 4 Sep 2024 10:03:49 +0200 Subject: [PATCH 5/8] Fix test iFDO files --- tests/IfdoParserTest.php | 14 ++++++++++++++ tests/files/image-ifdo.json | 4 +--- tests/files/video-example-1.json | 10 ++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/tests/IfdoParserTest.php b/tests/IfdoParserTest.php index 2e3b5b5..37eb552 100644 --- a/tests/IfdoParserTest.php +++ b/tests/IfdoParserTest.php @@ -9,6 +9,20 @@ class ImageIfdoParserTest extends TestCase { + public function testRecognizesFileImage() + { + $file = new File(__DIR__ . "/files/image-ifdo.json"); + $parser = new IfdoParser($file); + $this->assertTrue($parser->recognizesFile()); + } + + public function testRecognizesFileVideo() + { + $file = new File(__DIR__ . "/files/video-example-1.json"); + $parser = new IfdoParser($file); + $this->assertTrue($parser->recognizesFile()); + } + public function testGetMetadata() { $file = new File(__DIR__ . "/files/image-ifdo.json"); diff --git a/tests/files/image-ifdo.json b/tests/files/image-ifdo.json index 6abc626..07ffe5b 100644 --- a/tests/files/image-ifdo.json +++ b/tests/files/image-ifdo.json @@ -23,15 +23,13 @@ "image-quality": "raw", "image-resolution": "mm", "image-scale-reference": "laser marker", - "image-set-data-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@data", - "image-set-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450", "image-set-metadata-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@metadata", "image-set-name": "SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS", "image-set-uuid": "d7546c4b-307f-4d42-8554-33236c577450", "image-spectral-resolution": "rgb", "image-copyright": "(c) GEOMAR Helmholtz Centre for Ocean Research Kiel. Contact: presse@geomar.de", "image-abstract": "Image Abstract", - "image-set-handle": "https://hdl.handle.net/someHandle", + "image-set-handle": "https://hdl.handle.net/20.500.12085/d7546c4b-307f-4d42-8554-33236c577450", "image-license": { "name": "CC-BY", "uri": "https://creativecommons.org/licenses/by/4.0/legalcode" diff --git a/tests/files/video-example-1.json b/tests/files/video-example-1.json index 7855365..e1e44ed 100644 --- a/tests/files/video-example-1.json +++ b/tests/files/video-example-1.json @@ -62,11 +62,17 @@ "uri": "https://dm.pages.geomar.de/equipment/equipment/SO/CAM/SO_CAM-1_Photo_OFOS/" }, "image-set-data-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@data", - "image-set-handle": "https://hdl.handle.net/someHandle", + "image-set-handle": "https://hdl.handle.net/20.500.12085/d7546c4b-307f-4d42-8554-33236c577450", "image-set-ifdo-version": "v2.1.0", "image-set-metadata-handle": "20.500.12085/d7546c4b-307f-4d42-8554-33236c577450@metadata", "image-set-name": "SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS", - "image-set-uuid": "d7546c4b-307f-4d42-8554-33236c577450" + "image-set-uuid": "d7546c4b-307f-4d42-8554-33236c577450", + "image-creators": [ + { + "name": "Timm Schoening", + "uri": "https://orcid.org/0000-0002-0035-3282" + } + ] }, "image-set-items": { From 7dc39385aaf16eb3b18c44a96dcef2ed41b89dae Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 4 Sep 2024 10:04:15 +0200 Subject: [PATCH 6/8] implement volume name and handle import --- src/Converter.php | 17 ++++++++++++++--- tests/IfdoParserTest.php | 8 ++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/Converter.php b/src/Converter.php index e358f7c..9fc38e2 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -22,10 +22,16 @@ public function __construct($acquisitionFormat) public function convert($ifdo) { - $this->annotators = $this->extractIfdoAnnotators($ifdo->getImageSetHeader()['image-annotation-creators']); - $this->labels = $this->extractIfdoLabels($ifdo->getImageSetHeader()['image-annotation-labels']); + $header = $ifdo->getImageSetHeader(); + $this->annotators = $this->extractIfdoAnnotators($header['image-annotation-creators']); + $this->labels = $this->extractIfdoLabels($header['image-annotation-labels']); + + $data = new VolumeMetadata( + type: $this->mediaType(), + name: $header['image-set-name'] ?? null, + handle: $this->parseHandle($header['image-set-handle'] ?? null), + ); - $data = new VolumeMetadata($this->mediaType()); foreach ($ifdo->getImageSetItems() as $name => $items) { if ( ! array_is_list($items)) @@ -210,4 +216,9 @@ private function ifdoParseCoordinates($coordinates) return $coordinates; } + + private function parseHandle(?string $handle): ?string + { + return substr(parse_url($handle ?? '', PHP_URL_PATH) ?? '', 1) ?: null; + } } diff --git a/tests/IfdoParserTest.php b/tests/IfdoParserTest.php index 37eb552..1299d8f 100644 --- a/tests/IfdoParserTest.php +++ b/tests/IfdoParserTest.php @@ -29,9 +29,9 @@ public function testGetMetadata() $parser = new IfdoParser($file); $data = $parser->getMetadata(); $this->assertSame(MediaType::imageId(), $data->type->id); - $this->assertNull($data->name); + $this->assertSame('SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS', $data->name); $this->assertNull($data->url); - $this->assertNull($data->handle); + $this->assertSame('20.500.12085/d7546c4b-307f-4d42-8554-33236c577450', $data->handle); $this->assertCount(2, $data->getFiles()); $file = $data->getFiles()->last(); $this->assertSame('SO268-2_100-1_OFOS_SO_CAM-1_20190406_052726.JPG', $file->name); @@ -57,9 +57,9 @@ public function testGetVideoMetadata() $parser = new IfdoParser($file); $data = $parser->getMetadata(); $this->assertSame(MediaType::videoId(), $data->type->id); - $this->assertNull($data->name); + $this->assertSame('SO268 SO268-2_100-1_OFOS SO_CAM-1_Photo_OFOS', $data->name); $this->assertNull($data->url); - $this->assertNull($data->handle); + $this->assertSame('20.500.12085/d7546c4b-307f-4d42-8554-33236c577450', $data->handle); $this->assertCount(1, $data->getFiles()); $file = $data->getFiles()->last(); $this->assertSame('SO242_2_163-1_LowerHD.mp4', $file->name); From e3996464661952cc576d484f027c09ca294731e7 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 4 Sep 2024 14:19:44 +0200 Subject: [PATCH 7/8] Implement support for BIIGLE-specific attributes --- src/Converter.php | 13 +++++++++++-- tests/IfdoParserTest.php | 10 +++++++++- tests/files/image-ifdo.json | 5 ++++- tests/files/video-example-1.json | 5 ++++- 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Converter.php b/src/Converter.php index 9fc38e2..478f78f 100644 --- a/src/Converter.php +++ b/src/Converter.php @@ -129,7 +129,12 @@ private function extractIfdoLabels($ifdoLabels) $labels = []; foreach ($ifdoLabels as $ifdoLabel) { - $labels[$ifdoLabel['id']] = new Label($ifdoLabel['id'], $ifdoLabel['name']); + $labels[$ifdoLabel['id']] = new Label( + $ifdoLabel['id'], + $ifdoLabel['name'], + $ifdoLabel['color'] ?? null, + $ifdoLabel['uuid'] ?? null, + ); } return $labels; } @@ -139,7 +144,11 @@ private function extractIfdoAnnotators($ifdoAnnotators) $annotators = []; foreach ($ifdoAnnotators as $ifdoAnnotator) { - $annotators[$ifdoAnnotator['id']] = new User($ifdoAnnotator['id'], $ifdoAnnotator['name']); + $annotators[$ifdoAnnotator['id']] = new User( + $ifdoAnnotator['id'], + $ifdoAnnotator['name'], + $ifdoAnnotator['uuid'] ?? null, + ); } return $annotators; } diff --git a/tests/IfdoParserTest.php b/tests/IfdoParserTest.php index 1299d8f..1cf6196 100644 --- a/tests/IfdoParserTest.php +++ b/tests/IfdoParserTest.php @@ -7,7 +7,7 @@ use Biigle\Shape; use TestCase; -class ImageIfdoParserTest extends TestCase +class IfdoParserTest extends TestCase { public function testRecognizesFileImage() { @@ -48,6 +48,10 @@ public function testGetMetadata() $this->assertSame(Shape::polygonId(), $annotation->shape->id); $this->assertSame('Hans Wurst', $annotation->labels[0]->user->name); + $this->assertSame('4b6f42ff-6198-4b52-aa1c-fde5aa50265b', $annotation->labels[0]->user->uuid); + $this->assertSame('Trash', $annotation->labels[0]->label->name); + $this->assertSame('8a45f7e9-86aa-4ca8-bd58-2b2178ec4163', $annotation->labels[0]->label->uuid); + $this->assertSame('ff5900', $annotation->labels[0]->label->color); $this->assertSame(4715.16, $annotation->points[0]); } @@ -76,6 +80,10 @@ public function testGetVideoMetadata() $this->assertSame(Shape::circleId(), $annotation->shape->id); $this->assertSame('Timm Schoening', $annotation->labels[0]->user->name); + $this->assertSame('4b6f42ff-6198-4b52-aa1c-fde5aa50265b', $annotation->labels[0]->user->uuid); + $this->assertSame('Animal', $annotation->labels[0]->label->name); + $this->assertSame('8a45f7e9-86aa-4ca8-bd58-2b2178ec4163', $annotation->labels[0]->label->uuid); + $this->assertSame('ff5900', $annotation->labels[0]->label->color); $this->assertSame(681.7, $annotation->points[0][0]); $this->assertSame(0.402947, $annotation->frames[0]); } diff --git a/tests/files/image-ifdo.json b/tests/files/image-ifdo.json index 07ffe5b..7a1feef 100644 --- a/tests/files/image-ifdo.json +++ b/tests/files/image-ifdo.json @@ -79,12 +79,15 @@ }, { "id": "4", - "name": "Trash" + "name": "Trash", + "uuid": "8a45f7e9-86aa-4ca8-bd58-2b2178ec4163", + "color": "ff5900" } ], "image-annotation-creators": [ { "id": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "uuid": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", "name": "Hans Wurst" } ] diff --git a/tests/files/video-example-1.json b/tests/files/video-example-1.json index e1e44ed..d15d943 100644 --- a/tests/files/video-example-1.json +++ b/tests/files/video-example-1.json @@ -8,6 +8,7 @@ [ { "id": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", + "uuid": "4b6f42ff-6198-4b52-aa1c-fde5aa50265b", "name": "Timm Schoening" } ], @@ -15,7 +16,9 @@ [ { "id": "some-uuid", - "name": "Animal" + "name": "Animal", + "uuid": "8a45f7e9-86aa-4ca8-bd58-2b2178ec4163", + "color": "ff5900" } ], "image-area-square-meter": 5, From d93cbe40ebf3e56cf940564cbfd07b3132b77dca Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 4 Sep 2024 15:24:28 +0200 Subject: [PATCH 8/8] Fix docker compose command in test action --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f38099..9351aae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,9 +57,9 @@ jobs: docker pull ghcr.io/biigle/worker:latest - name: Start test database - run: docker-compose up -d --no-build database_testing && sleep 5 + run: docker compose up -d --no-build database_testing && sleep 5 working-directory: ../core - name: Run tests - run: docker-compose run --rm -u 1001 worker php -d memory_limit=1G vendor/bin/phpunit --random-order --filter 'Biigle\\Tests\\Modules\\'${MODULE_NAME} + run: docker compose run --rm -u 1001 worker php -d memory_limit=1G vendor/bin/phpunit --random-order --filter 'Biigle\\Tests\\Modules\\'${MODULE_NAME} working-directory: ../core