-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #173 from biigle/issue-125
Sort by similarity
- Loading branch information
Showing
21 changed files
with
833 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
src/Http/Controllers/Api/Projects/SortAnnotationsBySimilarityController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
namespace Biigle\Modules\Largo\Http\Controllers\Api\Projects; | ||
|
||
use Biigle\Http\Controllers\Api\Controller; | ||
use Biigle\Modules\Largo\Http\Requests\IndexProjectAnnotationsSimilarity; | ||
use DB; | ||
|
||
class SortAnnotationsBySimilarityController extends Controller | ||
{ | ||
/** | ||
* Sort annotations with specific label by similarity. | ||
* | ||
* @api {get} projects/:id/annotations/sort/similarity Sort annotations with the same label by similarity | ||
* @apiGroup Projects | ||
* @apiName ShowProjectsAnnotationsSortSimilarity | ||
* @apiParam {Number} id The project ID | ||
* @apiParam (Required arguments) {Number} label_id The Label ID | ||
* @apiParam (Required arguments) {Number} image_annotation_id The reference image annotation to sort by similarity. This is not required if `video_annotation_id` is provided. | ||
* @apiParam (Required arguments) {Number} video_annotation_id The reference video annotation to sort by similarity. This is not required if `image_annotation_id` is provided. | ||
* @apiPermission projectMember | ||
* @apiDescription Returns a list of image/video annotation IDs with the most similar first (without the reference annotation ID). Image annotation IDs are prefixed with `i` (e.g. `i123`) and video annotation IDs are prefixed with `v` (e.g. `v456`). | ||
* | ||
* @param IndexProjectAnnotationsSimilarity $request | ||
*/ | ||
public function index(IndexProjectAnnotationsSimilarity $request) | ||
{ | ||
$r = $request->reference; | ||
|
||
// This was too complicated with the query builder. Since there is no risk of SQL | ||
// injection here, we just use raw SQL. | ||
$sql = <<<SQL | ||
SELECT "id" FROM ( | ||
( | ||
SELECT CONCAT('i', "annotation_id") AS id, "vector" | ||
FROM "image_annotation_label_feature_vectors" | ||
WHERE "label_id" = :lid AND "annotation_id" != :iid AND "volume_id" IN ( | ||
SELECT "volume_id" FROM "project_volume" WHERE "project_id" = :pid | ||
) | ||
) | ||
UNION | ||
( | ||
SELECT CONCAT('v', "annotation_id") AS id, "vector" | ||
FROM "video_annotation_label_feature_vectors" | ||
WHERE "label_id" = :lid AND "annotation_id" != :vid AND "volume_id" IN ( | ||
SELECT "volume_id" FROM "project_volume" WHERE "project_id" = :pid | ||
) | ||
) | ||
) AS "temp" | ||
ORDER BY "temp"."vector" <=> :vector, id DESC | ||
SQL; | ||
|
||
$ids = DB::select($sql, [ | ||
'pid' => $request->project->id, | ||
'lid' => $r->label_id, | ||
'vector' => $r->vector, | ||
// We need only one at a time but since the ID 0 never exists, we just take | ||
// it as the other ID. | ||
'iid' => $request->input('image_annotation_id', 0), | ||
'vid' => $request->input('video_annotation_id', 0), | ||
]); | ||
|
||
// Filtering unique IDs is not required here because the UNION in the query | ||
// takes care of that. | ||
return array_map(fn ($v) => $v->id, $ids); | ||
} | ||
} |
49 changes: 49 additions & 0 deletions
49
src/Http/Controllers/Api/Volumes/SortAnnotationsBySimilarityController.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
namespace Biigle\Modules\Largo\Http\Controllers\Api\Volumes; | ||
|
||
use Biigle\Http\Controllers\Api\Controller; | ||
use Biigle\Modules\Largo\ImageAnnotationLabelFeatureVector; | ||
use Biigle\Modules\Largo\VideoAnnotationLabelFeatureVector; | ||
use Biigle\Modules\Largo\Http\Requests\IndexVolumeAnnotationsSimilarity; | ||
|
||
class SortAnnotationsBySimilarityController extends Controller | ||
{ | ||
/** | ||
* Sort annotations with specific label by similarity. | ||
* | ||
* @api {get} volumes/:id/annotations/sort/similarity Sort annotations with the same label by similarity | ||
* @apiGroup Volumes | ||
* @apiName ShowVolumesAnnotationsSortSimilarity | ||
* @apiParam {Number} id The volume ID | ||
* @apiParam (Required arguments) {Number} label_id The Label ID | ||
* @apiParam (Required arguments) {Number} annotation_id The reference annotation to sort by similarity | ||
* @apiPermission projectMember | ||
* @apiDescription Returns a list of image/video annotation IDs with the most similar first (without the reference annotation ID). | ||
* | ||
* @param IndexVolumeAnnotationsSimilarity $request | ||
*/ | ||
public function index(IndexVolumeAnnotationsSimilarity $request) | ||
{ | ||
$r = $request->reference; | ||
|
||
if ($request->volume->isVideoVolume()) { | ||
$query = VideoAnnotationLabelFeatureVector::where('volume_id', $r->volume_id) | ||
->where('label_id', $r->label_id) | ||
->where('id', '!=', $r->id) | ||
->orderByRaw('vector <=> ?, annotation_id DESC', [$r->vector]); | ||
} else { | ||
$query = ImageAnnotationLabelFeatureVector::where('volume_id', $r->volume_id) | ||
->where('label_id', $r->label_id) | ||
->where('id', '!=', $r->id) | ||
->orderByRaw('vector <=> ?, annotation_id DESC', [$r->vector]); | ||
} | ||
|
||
return $query->pluck('annotation_id') | ||
// Use distinct/unique *after* fetchig from the DB because otherwise the | ||
// vector would need to be selected, too (order by expressions must appear in | ||
// the select list). But we don't want to get the huge vectors. | ||
->unique() | ||
->values(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<?php | ||
|
||
namespace Biigle\Modules\Largo\Http\Requests; | ||
|
||
use Biigle\Annotation; | ||
use Biigle\ImageAnnotation; | ||
use Biigle\Modules\Largo\ImageAnnotationLabelFeatureVector; | ||
use Biigle\Modules\Largo\VideoAnnotationLabelFeatureVector; | ||
use Biigle\VideoAnnotation; | ||
use Biigle\Project; | ||
use Illuminate\Foundation\Http\FormRequest; | ||
|
||
class IndexProjectAnnotationsSimilarity extends FormRequest | ||
{ | ||
/** | ||
* The project of which to index the annotations. | ||
*/ | ||
public Project $project; | ||
|
||
/** | ||
* The reference annotation for sorting. | ||
*/ | ||
public $reference; | ||
|
||
/** | ||
* Determine if the user is authorized to make this request. | ||
* | ||
* @return bool | ||
*/ | ||
public function authorize() | ||
{ | ||
$this->project = Project::findOrFail($this->route('id')); | ||
|
||
return $this->user()->can('access', $this->project); | ||
} | ||
|
||
/** | ||
* Get the validation rules that apply to the request. | ||
* | ||
* @return array | ||
*/ | ||
public function rules() | ||
{ | ||
return [ | ||
'label_id' => 'required|bail|integer', | ||
'image_annotation_id' => 'required_without:video_annotation_id|bail|integer', | ||
'video_annotation_id' => 'required_without:image_annotation_id|bail|integer', | ||
]; | ||
} | ||
|
||
/** | ||
* Configure the validator instance. | ||
* | ||
* @param \Illuminate\Validation\Validator $validator | ||
* @return void | ||
*/ | ||
public function withValidator($validator) | ||
{ | ||
$validator->after(function ($validator) { | ||
if ($validator->errors()->isNotEmpty()) { | ||
return; | ||
} | ||
|
||
$ids = $this->project->volumes()->pluck('id'); | ||
if ($this->input('image_annotation_id')) { | ||
$this->reference = ImageAnnotationLabelFeatureVector::whereIn('volume_id', $ids) | ||
->where('label_id', $this->input('label_id')) | ||
->where('annotation_id', $this->input('image_annotation_id')) | ||
->first(); | ||
|
||
if (is_null($this->reference)) { | ||
$validator->errors()->add('image_annotation_id', 'The annotation does not exist in the project.'); | ||
} | ||
} else { | ||
$this->reference = VideoAnnotationLabelFeatureVector::whereIn('volume_id', $ids) | ||
->where('label_id', $this->input('label_id')) | ||
->where('annotation_id', $this->input('video_annotation_id')) | ||
->first(); | ||
|
||
if (is_null($this->reference)) { | ||
$validator->errors()->add('video_annotation_id', 'The annotation does not exist in the project.'); | ||
} | ||
} | ||
|
||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?php | ||
|
||
namespace Biigle\Modules\Largo\Http\Requests; | ||
|
||
use Biigle\Annotation; | ||
use Biigle\ImageAnnotation; | ||
use Biigle\Modules\Largo\ImageAnnotationLabelFeatureVector; | ||
use Biigle\Modules\Largo\VideoAnnotationLabelFeatureVector; | ||
use Biigle\VideoAnnotation; | ||
use Biigle\Volume; | ||
use Illuminate\Foundation\Http\FormRequest; | ||
|
||
class IndexVolumeAnnotationsSimilarity extends FormRequest | ||
{ | ||
/** | ||
* The volume of which to index the annotations. | ||
*/ | ||
public Volume $volume; | ||
|
||
/** | ||
* The reference annotation for sorting. | ||
*/ | ||
public $reference; | ||
|
||
/** | ||
* Determine if the user is authorized to make this request. | ||
* | ||
* @return bool | ||
*/ | ||
public function authorize() | ||
{ | ||
$this->volume = Volume::findOrFail($this->route('id')); | ||
|
||
return $this->user()->can('access', $this->volume); | ||
} | ||
|
||
/** | ||
* Get the validation rules that apply to the request. | ||
* | ||
* @return array | ||
*/ | ||
public function rules() | ||
{ | ||
return [ | ||
'label_id' => 'required|bail|integer', | ||
'annotation_id' => 'required|bail|integer', | ||
]; | ||
} | ||
|
||
/** | ||
* Configure the validator instance. | ||
* | ||
* @param \Illuminate\Validation\Validator $validator | ||
* @return void | ||
*/ | ||
public function withValidator($validator) | ||
{ | ||
$validator->after(function ($validator) { | ||
if ($this->volume->isImageVolume()) { | ||
$this->reference = ImageAnnotationLabelFeatureVector::where('volume_id', $this->volume->id) | ||
->where('label_id', $this->input('label_id')) | ||
->where('annotation_id', $this->input('annotation_id')) | ||
->first(); | ||
} else { | ||
$this->reference = VideoAnnotationLabelFeatureVector::where('volume_id', $this->volume->id) | ||
->where('label_id', $this->input('label_id')) | ||
->where('annotation_id', $this->input('annotation_id')) | ||
->first(); | ||
} | ||
|
||
if (is_null($this->reference)) { | ||
$validator->errors()->add('annotation_id', 'The annotation does not exist in the volume.'); | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
{ | ||
"/assets/scripts/main.js": "/assets/scripts/main.js?id=9d428530385ad8ea4cbd82e4600d8cd0", | ||
"/assets/styles/main.css": "/assets/styles/main.css?id=f5673570cda3cacb644da42a2840c218" | ||
"/assets/scripts/main.js": "/assets/scripts/main.js?id=608af5558117398ca4c3d3598a905abb", | ||
"/assets/styles/main.css": "/assets/styles/main.css?id=5295955359904ac00dbeabaf613c5792" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.