diff --git a/src/Http/Controllers/Api/Projects/SortAnnotationsByOutliersController.php b/src/Http/Controllers/Api/Projects/SortAnnotationsByOutliersController.php new file mode 100644 index 00000000..52bdaafa --- /dev/null +++ b/src/Http/Controllers/Api/Projects/SortAnnotationsByOutliersController.php @@ -0,0 +1,74 @@ +authorize('access', $project); + + // This was too complicated with the query builder. Since there is no rist of SQL + // injection here, we just use raw SQL. + $sql = << ( + SELECT AVG("temp2"."vector") FROM ( + SELECT "vector" FROM "image_annotation_label_feature_vectors" + WHERE "label_id" = :lid AND "volume_id" IN ( + SELECT "volume_id" FROM "project_volume" WHERE "project_id" = :pid + ) + UNION ALL + SELECT "vector" FROM "video_annotation_label_feature_vectors" + WHERE "label_id" = :lid AND "volume_id" IN ( + SELECT "volume_id" FROM "project_volume" WHERE "project_id" = :pid + ) + ) AS temp2 + ) DESC, id DESC + SQL; + + $ids = DB::select($sql, ['pid' => $pid, 'lid' => $lid]); + + // Filtering unique IDs is not required here because the UNIQUE in the query + // takes care of that. + return array_map(fn ($v) => $v->id, $ids); + } +} diff --git a/src/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersController.php b/src/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersController.php index ebeb74b0..84abf7a9 100644 --- a/src/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersController.php +++ b/src/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersController.php @@ -32,11 +32,11 @@ public function index($vid, $lid) if ($volume->isVideoVolume()) { $query = VideoAnnotationLabelFeatureVector::where('volume_id', $vid) ->where('label_id', $lid) - ->orderByRaw('vector <=> (SELECT avg(vector) FROM video_annotation_label_feature_vectors WHERE volume_id = ? AND label_id = ?) DESC, annotation_id DESC', [$vid, $lid]); + ->orderByRaw('vector <=> (SELECT AVG(vector) FROM video_annotation_label_feature_vectors WHERE volume_id = ? AND label_id = ?) DESC, annotation_id DESC', [$vid, $lid]); } else { $query = ImageAnnotationLabelFeatureVector::where('volume_id', $vid) ->where('label_id', $lid) - ->orderByRaw('vector <=> (SELECT avg(vector) FROM image_annotation_label_feature_vectors WHERE volume_id = ? AND label_id = ?) DESC, annotation_id DESC', [$vid, $lid]); + ->orderByRaw('vector <=> (SELECT AVG(vector) FROM image_annotation_label_feature_vectors WHERE volume_id = ? AND label_id = ?) DESC, annotation_id DESC', [$vid, $lid]); } return $query->pluck('annotation_id') diff --git a/src/Http/routes.php b/src/Http/routes.php index 7003af75..ad39aa3d 100644 --- a/src/Http/routes.php +++ b/src/Http/routes.php @@ -33,6 +33,10 @@ 'uses' => 'Labels\VideoAnnotationsController@index', ]); + $router->get('projects/{id}/annotations/sort/outliers/{id2}', [ + 'uses' => 'Projects\SortAnnotationsByOutliersController@index', + ]); + $router->get('projects/{id}/image-annotations/filter/label/{id2}', [ 'uses' => 'Projects\FilterImageAnnotationsByLabelController@index', ]); diff --git a/tests/Http/Controllers/Api/Projects/SortAnnotationsByOutliersControllerTest.php b/tests/Http/Controllers/Api/Projects/SortAnnotationsByOutliersControllerTest.php new file mode 100644 index 00000000..536bc410 --- /dev/null +++ b/tests/Http/Controllers/Api/Projects/SortAnnotationsByOutliersControllerTest.php @@ -0,0 +1,95 @@ +project()->id; + + $v1 = Volume::factory()->create(); + $this->project()->addVolumeId($v1->id); + + $l1 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v1->id, + ]); + $l2 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v1->id, + 'label_id' => $l1->label_id, + ]); + $l3 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v1->id, + 'label_id' => $l1->label_id, + // The other feature vectors are identical so this one will be an outlier. + 'vector' => range(1, 384), + ]); + $l4 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v1->id, + ]); + + $v2 = Volume::factory()->create(['media_type_id' => MediaType::videoId()]); + $this->project()->addVolumeId($v2->id); + + $l5 = VideoAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v2->id, + 'label_id' => $l1->label_id, + ]); + $l6 = VideoAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $v2->id, + 'label_id' => $l1->label_id, + 'vector' => range(1, 384), + ]); + + // Annotations from other volume should not appear. + ImageAnnotationLabelFeatureVector::factory()->create(); + VideoAnnotationLabelFeatureVector::factory()->create(); + + $this->doTestApiRoute('GET', "/api/v1/projects/{$id}/annotations/sort/outliers/{$l1->label_id}"); + + $this->beUser(); + $this->get("/api/v1/projects/{$id}/annotations/sort/outliers/{$l1->label_id}") + ->assertStatus(403); + + $this->beGuest(); + + $this->get("/api/v1/projects/{$id}/annotations/sort/outliers/{$l1->label_id}") + ->assertStatus(200) + ->assertExactJson([ + 'v'.$l6->annotation_id, + 'i'.$l3->annotation_id, + 'v'.$l5->annotation_id, + 'i'.$l2->annotation_id, + 'i'.$l1->annotation_id, + ]); + + $this->get("/api/v1/projects/{$id}/annotations/sort/outliers/{$l4->label_id}") + ->assertStatus(200) + ->assertExactJson(['i'.$l4->annotation_id]); + } + + public function testIndexDuplicate() + { + $id = $this->project()->id; + $vid = $this->volume()->id; + $l1 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $vid, + ]); + $l2 = ImageAnnotationLabelFeatureVector::factory()->create([ + 'volume_id' => $vid, + 'label_id' => $l1->label_id, + 'annotation_id' => $l1->annotation_id, + ]); + + $this->beEditor(); + $this->get("/api/v1/projects/{$id}/annotations/sort/outliers/{$l1->label_id}") + ->assertStatus(200) + ->assertExactJson(['i'.$l1->annotation_id]); + } +} diff --git a/tests/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersControllerTest.php b/tests/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersControllerTest.php index 1fb1f2e5..81200f62 100644 --- a/tests/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersControllerTest.php +++ b/tests/Http/Controllers/Api/Volumes/SortAnnotationsByOutliersControllerTest.php @@ -44,8 +44,8 @@ public function testIndexImage() ->assertStatus(200) ->assertExactJson([ $l3->annotation_id, - $l1->annotation_id, $l2->annotation_id, + $l1->annotation_id, ]); $this->get("/api/v1/volumes/{$id}/annotations/sort/outliers/{$l4->label_id}") @@ -106,8 +106,8 @@ public function testIndexVideo() ->assertStatus(200) ->assertExactJson([ $l3->annotation_id, - $l1->annotation_id, $l2->annotation_id, + $l1->annotation_id, ]); $this->get("/api/v1/volumes/{$id}/annotations/sort/outliers/{$l4->label_id}")