Skip to content

Commit

Permalink
Merge pull request #194 from biigle/flat-label-list-tab
Browse files Browse the repository at this point in the history
Add flat label list tab
  • Loading branch information
mzur authored Jan 30, 2025
2 parents fc7ad73 + 84f2fe7 commit 2bd4b87
Show file tree
Hide file tree
Showing 19 changed files with 1,078 additions and 10 deletions.
61 changes: 61 additions & 0 deletions src/Http/Controllers/Api/Projects/ProjectAnnotationLabels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Biigle\Modules\Largo\Http\Controllers\Api\Projects;

use Biigle\Label;
use Biigle\Project;
use Illuminate\Support\Facades\DB;
use Biigle\Http\Controllers\Api\Controller;

class ProjectAnnotationLabels extends Controller
{
/**
* Get all image labels and annotation count for a given project
*
* @api {get} projects/:pid/label-count Get annotation labels with a annotation count
* @apiGroup Projects
* @apiName test
* @apiParam {Number} id The Project ID
* @apiPermission projectMember
* @apiDescription Returns a collection of annotation labels and their counts in the project
*
* @apiSuccessExample {json} Success response:
* [{"id":1,
* "name":"a",
* "color":"f2617c",
* "label_tree_id":1,
* "count":10}]
*
* @param int $id Project ID
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getProjectAnnotationLabelCounts($id)
{
$project = Project::findOrFail($id);
$this->authorize('access', $project);

$imageLabelQuery = Label::query()
->join('image_annotation_labels', 'labels.id', '=', 'image_annotation_labels.label_id')
->join('image_annotations', 'image_annotation_labels.annotation_id', '=', 'image_annotations.id')
->join('images', 'image_annotations.image_id', '=', 'images.id')
->join('project_volume', 'images.volume_id', '=', 'project_volume.volume_id')
->where('project_volume.project_id', '=', $id)
->select('labels.*');

$videoLabelQuery = Label::query()
->join('video_annotation_labels', 'labels.id', '=', 'video_annotation_labels.label_id')
->join('video_annotations', 'video_annotation_labels.annotation_id', '=', 'video_annotations.id')
->join('videos', 'video_annotations.video_id', '=', 'videos.id')
->join('project_volume', 'videos.volume_id', '=', 'project_volume.volume_id')
->where('project_volume.project_id', '=', $id)
->select('labels.*');

$union = $videoLabelQuery->unionAll($imageLabelQuery);

return DB::query()->fromSub($union, 'labels')
->selectRaw('labels.id, labels.name, labels.color, labels.label_tree_id, count(labels.id) as count')
->groupBy(['labels.id', 'labels.name', 'labels.color', 'labels.label_tree_id'])
->orderBy('labels.name')
->get();
}
}
78 changes: 78 additions & 0 deletions src/Http/Controllers/Api/Volumes/VolumeAnnotationLabels.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Biigle\Modules\Largo\Http\Controllers\Api\Volumes;

use Biigle\Volume;
use Biigle\ImageAnnotation;
use Biigle\VideoAnnotation;
use Illuminate\Http\Request;
use Biigle\Http\Controllers\Api\Controller;

class VolumeAnnotationLabels extends Controller
{
/**
* Get all annotation labels and annotation count for a given volume
*
* @api {get} volumes/:vid/label-count Get annotation labels with a annotation count
* @apiGroup Volumes
* @apiName test
* @apiParam {Number} id The Volume ID
* @apiPermission projectMember
* @apiDescription Returns a collection of annotation labels and their counts in the volume
*
* @apiSuccessExample {json} Success response:
* [{"id":1,
* "name":"a",
* "color":"f2617c",
* "label_tree_id":1,
* "count":10}]
*
* @param int $id Volume ID
* @return \Illuminate\Database\Eloquent\Collection
*/
public function getVolumeAnnotationLabels(Request $request, $id)
{
$volume = Volume::findOrFail($id);
$this->authorize('access', $volume);
$isImageVolume = $volume->isImageVolume();
$session = $volume->getActiveAnnotationSession($request->user());

if ($session) {
$query = $isImageVolume ? ImageAnnotation::allowedBySession($session, $request->user()) :
VideoAnnotation::allowedBySession($session, $request->user());
} else {
$query = $isImageVolume ? ImageAnnotation::query() : VideoAnnotation::query();
}

if ($isImageVolume) {
$labelQuery = $query
->join('image_annotation_labels', 'image_annotations.id', '=', 'image_annotation_labels.annotation_id')
->join('labels', 'image_annotation_labels.label_id', '=', 'labels.id')
->join('images', 'image_annotations.image_id', '=', 'images.id')
->where('images.volume_id', '=', $id);
} else {
$labelQuery = $query
->join('video_annotation_labels', 'video_annotations.id', '=', 'video_annotation_labels.annotation_id')
->join('labels', 'video_annotation_labels.label_id', '=', 'labels.id')
->join('videos', 'video_annotations.video_id', '=', 'videos.id')
->where('videos.volume_id', '=', $id);
}


return $labelQuery
->when($session, function ($query) use ($session, $request, $isImageVolume) {
if ($session->hide_other_users_annotations) {
if ($isImageVolume) {
$query->where('image_annotation_labels.user_id', $request->user()->id);
} else {
$query->where('video_annotation_labels.user_id', $request->user()->id);
}
}
})
->selectRaw('labels.id, labels.name, labels.color, labels.label_tree_id, count(labels.id) as count')
->groupBy(['labels.id', 'labels.name', 'labels.color', 'labels.label_tree_id'])
->orderBy('labels.name')
->get();

}
}
8 changes: 8 additions & 0 deletions src/Http/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
'uses' => 'Projects\FilterVideoAnnotationsByLabelController@index',
]);

$router->get('projects/{id}/label-count', [
'uses' => 'Projects\ProjectAnnotationLabels@getProjectAnnotationLabelCounts',
]);

$router->get('volumes/{id}/annotations/sort/outliers/{id2}', [
'uses' => 'Volumes\SortAnnotationsByOutliersController@index',
]);
Expand All @@ -76,4 +80,8 @@
$router->get('volumes/{id}/video-annotations/filter/label/{id2}', [
'uses' => 'Volumes\FilterVideoAnnotationsByLabelController@index',
]);

$router->get('volume/{id}/label-count', [
'uses' => 'Volumes\VolumeAnnotationLabels@getVolumeAnnotationLabels'
]);
});
2 changes: 1 addition & 1 deletion src/public/assets/scripts/main.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/public/assets/styles/main.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/public/mix-manifest.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"/assets/scripts/main.js": "/assets/scripts/main.js?id=e905f62357d0ef81ac20e94395510a2a",
"/assets/styles/main.css": "/assets/styles/main.css?id=68a88f330c08af97df1d47c207ea8ccb"
"/assets/scripts/main.js": "/assets/scripts/main.js?id=e1053b7af4d2947420a8f46c4fa630c8",
"/assets/styles/main.css": "/assets/styles/main.css?id=0ef4b3ef391cbeaff5022f2ba61a6b05"
}
4 changes: 4 additions & 0 deletions src/resources/assets/js/api/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ export default Vue.resource('api/v1/projects{/id}/largo', {}, {
method: 'GET',
url: 'api/v1/projects{/id}/annotations/sort/similarity',
},
fetchProjectAnnotationLabelCount: {
method: 'GET',
url: 'api/v1/projects{/id}/label-count'
},
});
4 changes: 4 additions & 0 deletions src/resources/assets/js/api/volumes.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,8 @@ export default Vue.resource('api/v1/volumes{/id}/largo', {}, {
method: 'GET',
url: 'api/v1/volumes{/id}/annotations/sort/similarity',
},
fetchVolumeAnnotationLabelCount: {
method: 'GET',
url: 'api/v1/volume{/id}/label-count',
},
});
33 changes: 33 additions & 0 deletions src/resources/assets/js/components/labelList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<script>
import LabelItem from './labelListLabelItem';
export default {
components: {
labelItem: LabelItem,
},
props: {
labels: {
type: Array,
default() {
return [];
},
},
},
computed: {
annotationBadgeCount() {
return this.labels.reduce((acc, l) => {
return acc + l.count;
}, 0);
},
},
methods: {
handleSelectedLabel(label) {
this.$emit('select', label);
},
handleDeselectedLabel() {
this.$emit('deselect');
},
},
};
</script>
55 changes: 55 additions & 0 deletions src/resources/assets/js/components/labelListLabelItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<li class="annotations-tab-item--largo" :class="classObject" :title="title">
<div class="annotations-tab-item__title--largo" @click="emitSelectLabel">
<span class="pull-right badge" v-text="count" :title="countTitle"></span>
<span class="annotations-tab-item__color--largo" :style="colorStyle"></span>
<span v-text="label.name"></span>
</div>
</li>
</template>

<script>
export default {
props: {
label: {
type: Object,
default() {
return {};
},
},
},
computed: {
title() {
return `Annotations with label ${this.label.name}`;
},
classObject() {
return {
selected: this.label.selected,
};
},
countTitle() {
return `There are ${this.count} annotations with label ${this.label.name}`;
},
colorStyle() {
return 'background-color: #' + this.label.color;
},
count() {
return this.label.count;
},
},
methods: {
emitSelectLabel() {
this.labelItem.selected = !this.labelItem.selected
if (this.labelItem.selected) {
this.$emit('select', this.labelItem)
} else {
this.$emit('deselect');
}
}
},
created() {
this.labelItem = this.label;
}
};
</script>
3 changes: 3 additions & 0 deletions src/resources/assets/js/largoContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ export default {
}
return response;
},
fetchLabelCount() {
return VolumesApi.fetchVolumeAnnotationLabelCount({ id: this.volumeId });
}
},
created() {
Expand Down
Loading

0 comments on commit 2bd4b87

Please sign in to comment.