Skip to content

Commit

Permalink
Merge pull request #3 from biigle/implement-metadata-parser
Browse files Browse the repository at this point in the history
Implement metadata parser
  • Loading branch information
mzur authored Sep 4, 2024
2 parents ce8011f + d93cbe4 commit da0ab36
Show file tree
Hide file tree
Showing 11 changed files with 2,090 additions and 110 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}
},
"require": {
"biigle/ifdo":"^0.3 || ^1.0"
"biigle/ifdo":"^0.4 || ^1.0"
},
"extra": {
"laravel": {
Expand Down
233 changes: 233 additions & 0 deletions src/Converter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
<?php
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)
{
$this->acquisitionFormat = $acquisitionFormat;
}

public function convert($ifdo)
{
$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),
);

foreach ($ifdo->getImageSetItems() as $name => $items)
{
if ( ! array_is_list($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(...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);
}

}
}

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 addAnnotations($fileData, $ifdoAnnotations)
{
foreach ($ifdoAnnotations as $ifdoAnnotation)
{
$fileData->addAnnotation($this->ifdoToBiigleAnnotation($ifdoAnnotation));
}
}

private function ifdoToBiigleAnnotation($ifdoAnnotation)
{
$class = $this->annotationClass();
$params = [
$this->ifdoShapeToBiigleShape($ifdoAnnotation['shape']),
$this->ifdoParseCoordinates($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'],
$ifdoLabel['color'] ?? null,
$ifdoLabel['uuid'] ?? null,
);
}
return $labels;
}

private function extractIfdoAnnotators($ifdoAnnotators)
{
$annotators = [];
foreach ($ifdoAnnotators as $ifdoAnnotator)
{
$annotators[$ifdoAnnotator['id']] = new User(
$ifdoAnnotator['id'],
$ifdoAnnotator['name'],
$ifdoAnnotator['uuid'] ?? null,
);
}
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)
{
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';
}
}

private function annotationClass()
{
switch ($this->acquisitionFormat)
{
case 'photo':
return 'Biigle\Services\MetadataParsing\ImageAnnotation';
case 'video':
return 'Biigle\Services\MetadataParsing\VideoAnnotation';
}
}

private function ifdoParseCoordinates($coordinates)
{
if ($this->acquisitionFormat === 'photo') {
return $coordinates[0];
}

return $coordinates;
}

private function parseHandle(?string $handle): ?string
{
return substr(parse_url($handle ?? '', PHP_URL_PATH) ?? '', 1) ?: null;
}
}
56 changes: 56 additions & 0 deletions src/IfdoParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
namespace Biigle\Modules\MetadataIfdo;

use Biigle\Ifdo\Ifdo;
use Biigle\Services\MetadataParsing\MetadataParser;
use Biigle\Services\MetadataParsing\VolumeMetadata;

class IfdoParser extends MetadataParser
{
private $ifdo = null;

/**
* {@inheritdoc}
*/
public static function getKnownMimeTypes(): array {
return [
'application/json',
];
}

/**
* {@inheritdoc}
*/
public static function getName(): string
{
return "iFDO";
}

public function getIfdo(): Ifdo
{
if ( ! $this->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());
}
}
41 changes: 0 additions & 41 deletions src/ImageIfdoParser.php

This file was deleted.

Loading

0 comments on commit da0ab36

Please sign in to comment.