From 9d9d8f2e56b753c9932cb049d651e32371120798 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 10 Sep 2025 15:11:23 +0200 Subject: [PATCH 1/3] Optimize metadata parsing --- src/Annotation.php | 23 +++++++++++++---------- src/Coco.php | 4 +++- src/CocoParser.php | 31 ++++++++++++++++--------------- 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/Annotation.php b/src/Annotation.php index 05fb1a1..750a2a3 100644 --- a/src/Annotation.php +++ b/src/Annotation.php @@ -68,8 +68,8 @@ public static function validate(array $data): void public function getLabel(array $categories): Label { - $categoryIndex = array_search($this->category_id, array_column($categories, 'id')); - $category = $categories[$categoryIndex]; + $category = $categories[$this->category_id]; + return new Label(id: $category->id, name: $category->name); } @@ -77,6 +77,7 @@ public function getLabelAndUsers(array $categories): array { $cocoUser = Coco::getCocoUser(); $label = $this->getLabel($categories); + return [new LabelAndUser(label: $label, user: $cocoUser)]; } @@ -85,6 +86,11 @@ public function getPoints(): array if ($this->getShape()->id === Shape::circleId()) { return $this->getCirclePoints(); } + + if ($this->getShape()->id === Shape::rectangleId()) { + return array_slice($this->segmentation, 0, 8); + } + return $this->segmentation; } @@ -204,7 +210,10 @@ function euclidean_distance($x1, $y1, $x2, $y2) public function isRectangleShape(): bool { - if (count($this->segmentation) !== 8) { + // Some rectangles have an additional last point equalling the first point, + // similar to polygons. + $segmentationCount = count($this->segmentation); + if ($segmentationCount !== 8 && $segmentationCount !== 10) { return false; } @@ -214,17 +223,11 @@ public function isRectangleShape(): bool // Punkte (x1, y1), (x2, y2), (x3, y3), (x4, y4) list($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4) = $this->segmentation; - // Berechne die Seitenlängen - $d1 = $this->euclidean_distance($x1, $y1, $x2, $y2); // Distanz zwischen P1 und P2 - $d2 = $this->euclidean_distance($x2, $y2, $x3, $y3); // Distanz zwischen P2 und P3 - $d3 = $this->euclidean_distance($x3, $y3, $x4, $y4); // Distanz zwischen P3 und P4 - $d4 = $this->euclidean_distance($x4, $y4, $x1, $y1); // Distanz zwischen P4 und P1 - // Berechne die Diagonalen $diag1 = $this->euclidean_distance($x1, $y1, $x3, $y3); // Diagonale P1 -> P3 $diag2 = $this->euclidean_distance($x2, $y2, $x4, $y4); // Diagonale P2 -> P4 // Prüfen, ob gegenüberliegende Seiten gleich lang sind und Diagonalen gleich lang sind - return abs($d1 - $d3) < $tolerance && abs($d2 - $d4) < $tolerance && abs($diag1 - $diag2) < $tolerance; + return abs($diag1 - $diag2) < $tolerance; } } diff --git a/src/Coco.php b/src/Coco.php index 30d508d..5e76777 100644 --- a/src/Coco.php +++ b/src/Coco.php @@ -51,10 +51,12 @@ public static function create(array $data): self }, $data['annotations']); // Create the Category objects - $instance->categories = array_map(function ($categoryData) { + $categories = array_map(function ($categoryData) { return Category::create($categoryData); }, $data['categories']); + $instance->categories = array_combine(array_map(fn ($c) => $c->id, $categories), $categories); + // validate the data consistency $instance->validateCategoriesInData(); $instance->validateImagesInData(); diff --git a/src/CocoParser.php b/src/CocoParser.php index d4f512c..cdbde74 100644 --- a/src/CocoParser.php +++ b/src/CocoParser.php @@ -3,12 +3,13 @@ namespace Biigle\Modules\MetadataCoco; use Biigle\MediaType; -use Biigle\Services\MetadataParsing\MetadataParser; -use Biigle\Services\MetadataParsing\VolumeMetadata; -use Biigle\Services\MetadataParsing\ImageAnnotation; -use Biigle\Services\MetadataParsing\ImageMetadata; use Biigle\Modules\MetadataCoco\Coco; use Biigle\Modules\MetadataCoco\Image; +use Biigle\Services\MetadataParsing\ImageAnnotation; +use Biigle\Services\MetadataParsing\ImageMetadata; +use Biigle\Services\MetadataParsing\MetadataParser; +use Biigle\Services\MetadataParsing\VolumeMetadata; +use Illuminate\Support\Collection; class CocoParser extends MetadataParser { @@ -74,12 +75,17 @@ public function getMetadata(): VolumeMetadata handle: null, ); + $annotations = collect($coco->annotations)->groupBy('image_id'); + $categories = $coco->categories; + foreach ($coco->images as $image) { $imageMetaData = new ImageMetadata( name: $image->file_name ); - $this->processImageAnnotations($image, $imageMetaData); + if ($annotations->has($image->id)) { + $imageMetaData->annotations = $this->processImageAnnotations($annotations->get($image->id), $categories); + } $metadata->addFile($imageMetaData); } @@ -87,19 +93,14 @@ public function getMetadata(): VolumeMetadata return $metadata; } - private function processImageAnnotations(Image $image, ImageMetadata $imageMetaData) + private function processImageAnnotations(Collection $annotations, array $categories) { - $annotations = array_filter($this->getCoco()->annotations, function ($annotation) use ($image) { - return $annotation->image_id === $image->id; - }); - - foreach ($annotations as $annotation) { - $metaDataAnnotation = new ImageAnnotation( + return $annotations->map(function ($annotation) use ($categories) { + return new ImageAnnotation( shape: $annotation->getShape(), points: $annotation->getPoints(), - labels: $annotation->getLabelAndUsers($this->getCoco()->categories), + labels: $annotation->getLabelAndUsers($categories), ); - $imageMetaData->addAnnotation($metaDataAnnotation); - } + })->toArray(); } } From b06b66ee4d393cf2b0c7daaed668c4d147de3f55 Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 10 Sep 2025 15:13:07 +0200 Subject: [PATCH 2/3] Update comments --- src/Annotation.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Annotation.php b/src/Annotation.php index 750a2a3..2afef3a 100644 --- a/src/Annotation.php +++ b/src/Annotation.php @@ -217,17 +217,15 @@ public function isRectangleShape(): bool return false; } - // Toleranz für Gleitkomma-Vergleiche + // Tolerance for float comparison. $tolerance = 0.01; - // Punkte (x1, y1), (x2, y2), (x3, y3), (x4, y4) list($x1, $y1, $x2, $y2, $x3, $y3, $x4, $y4) = $this->segmentation; - // Berechne die Diagonalen - $diag1 = $this->euclidean_distance($x1, $y1, $x3, $y3); // Diagonale P1 -> P3 - $diag2 = $this->euclidean_distance($x2, $y2, $x4, $y4); // Diagonale P2 -> P4 + $diag1 = $this->euclidean_distance($x1, $y1, $x3, $y3); + $diag2 = $this->euclidean_distance($x2, $y2, $x4, $y4); - // Prüfen, ob gegenüberliegende Seiten gleich lang sind und Diagonalen gleich lang sind + // If diagonals are equal we have a rectangle. return abs($diag1 - $diag2) < $tolerance; } } From c8a06d4e35832959f98b6a117d3d350104c25b9d Mon Sep 17 00:00:00 2001 From: Martin Zurowietz Date: Wed, 10 Sep 2025 15:18:06 +0200 Subject: [PATCH 3/3] Fix and update tests --- tests/CocoParserTest.php | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/CocoParserTest.php b/tests/CocoParserTest.php index a35bb96..929f8c2 100644 --- a/tests/CocoParserTest.php +++ b/tests/CocoParserTest.php @@ -163,6 +163,37 @@ public function testIsRectangleShape() $this->assertSame($rectangleAnnotation->getPoints(), $rectangleAnnotation->segmentation); } + public function testIsRectangleShapeWithMorePoints() + { + $rectangleAnnotation = Annotation::create([ + 'id' => 1, + 'image_id' => 1, + 'category_id' => 1, + 'bbox' => null, + 'segmentation' => [ + [ + 1853.22, + 596.22, + + 1776.16, + 799.04, + + 1597.21, + 731.04, + + 1674.27, + 528.23, + + 1853.22, + 596.22, + ] + ], + ]); + $this->assertTrue($rectangleAnnotation->isRectangleShape()); + $this->assertSame($rectangleAnnotation->getShape(), Shape::rectangle()); + $this->assertSame($rectangleAnnotation->getPoints(), array_slice($rectangleAnnotation->segmentation, 0, 8)); + } + public function testIsCircleShape() { $circleAnnotation = Annotation::create([ @@ -328,7 +359,7 @@ public function testIsPolygonShape() 'image_id' => 1, 'category_id' => 1, 'bbox' => null, - 'segmentation' => [[1, 1, 2, 2, 3, 3, 4, 4, 1, 1]] + 'segmentation' => [[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 1, 1]] ]); $this->assertSame($polygonAnnotation->getShape(), Shape::polygon()); }