Skip to content

Commit 5f85f46

Browse files
authored
UI-Improvements/Fixes (#137)
* non-compressed X-axis highlight, zooming legend fix, 2.5x default compressed * cursor time compressed fix, axis length fix, edit column reorder in Recordings * recording annotation details * removing some commented out emits * remove footer from details for inference annotations * reference the outputs.tags instead of outputs.results
1 parent 59d789b commit 5f85f46

13 files changed

Lines changed: 400 additions & 162 deletions

File tree

.github/workflows/cd.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,4 @@ jobs:
5050
context: .
5151
platforms: linux/amd64
5252
push: true
53-
tags: ${{ steps.image-tags.outputs.result }}
53+
tags: ${{ steps.image-tags.outputs.tags }}

bats_ai/core/views/recording.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ class RecordingAnnotationSchema(Schema):
6969
owner: str
7070
confidence: float
7171
id: int | None = None
72+
hasDetails: bool
7273

7374
@classmethod
7475
def from_orm(cls, obj: RecordingAnnotation, **kwargs):
@@ -79,6 +80,7 @@ def from_orm(cls, obj: RecordingAnnotation, **kwargs):
7980
comments=obj.comments,
8081
model=obj.model,
8182
id=obj.pk,
83+
hasDetails=obj.additional_data is not None,
8284
)
8385

8486

bats_ai/core/views/recording_annotation.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class RecordingAnnotationSchema(Schema):
2020
owner: str
2121
confidence: float
2222
id: int | None = None
23+
hasDetails: bool
2324

2425
@classmethod
2526
def from_orm(cls, obj: RecordingAnnotation, **kwargs):
@@ -30,6 +31,31 @@ def from_orm(cls, obj: RecordingAnnotation, **kwargs):
3031
comments=obj.comments,
3132
model=obj.model,
3233
id=obj.pk,
34+
hasDetails=obj.additional_data is not None,
35+
)
36+
37+
38+
class RecordingAnnotationDetailsSchema(Schema):
39+
species: list[SpeciesSchema] | None
40+
comments: str | None = None
41+
model: str | None = None
42+
owner: str
43+
confidence: float
44+
id: int | None = None
45+
details: dict
46+
hasDetails: bool
47+
48+
@classmethod
49+
def from_orm(cls, obj: RecordingAnnotation, **kwargs):
50+
return cls(
51+
species=[SpeciesSchema.from_orm(species) for species in obj.species.all()],
52+
owner=obj.owner.username,
53+
confidence=obj.confidence,
54+
comments=obj.comments,
55+
model=obj.model,
56+
hasDetails=obj.additional_data is not None,
57+
details=obj.additional_data,
58+
id=obj.pk,
3359
)
3460

3561

@@ -62,6 +88,20 @@ def get_recording_annotation(request: HttpRequest, id: int):
6288
raise HttpError(404, 'Recording annotation not found.')
6389

6490

91+
@router.get('/{id}/details', response=RecordingAnnotationDetailsSchema)
92+
def get_recording_annotation_details(request: HttpRequest, id: int):
93+
try:
94+
annotation = RecordingAnnotation.objects.get(pk=id)
95+
96+
# Check permission
97+
if annotation.recording.owner != request.user and not annotation.recording.public:
98+
raise HttpError(403, 'Permission denied.')
99+
100+
return RecordingAnnotationDetailsSchema.from_orm(annotation).dict()
101+
except RecordingAnnotation.DoesNotExist:
102+
raise HttpError(404, 'Recording annotation not found.')
103+
104+
65105
@router.put('/', response={200: str})
66106
def create_recording_annotation(request: HttpRequest, data: CreateRecordingAnnotationSchema):
67107
try:

client/src/api/api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,15 @@ export interface FileAnnotation {
115115
model?: string;
116116
owner: string;
117117
confidence: number;
118+
hasDetails: boolean;
118119
id: number;
119120
}
120121

122+
export interface FileAnnotationDetails {
123+
label: string;
124+
score: number;
125+
confidences: { label: string, value: string}[];
126+
}
121127
export interface UpdateFileAnnotation {
122128
recordingId?: number;
123129
species: number[] | null;
@@ -330,6 +336,10 @@ async function getFileAnnotations(recordingId: number) {
330336
return axiosInstance.get<FileAnnotation[]>(`recording/${recordingId}/recording-annotations`);
331337
}
332338

339+
async function getFileAnnotationDetails(recordingId: number) {
340+
return axiosInstance.get<(FileAnnotation & {details: FileAnnotationDetails })>(`recording-annotation/${recordingId}/details`);
341+
342+
}
333343

334344
async function putFileAnnotation(fileAnnotation: UpdateFileAnnotation) {
335345
return axiosInstance.put<{message: string, id: number}>(`/recording-annotation/`, { ...fileAnnotation });
@@ -421,4 +431,5 @@ export {
421431
deleteFileAnnotation,
422432
getConfiguration,
423433
patchConfiguration,
434+
getFileAnnotationDetails,
424435
};
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<script lang="ts">
2+
import { defineComponent, ref, onMounted, PropType } from 'vue';
3+
import { FileAnnotationDetails, getFileAnnotationDetails } from '../api/api';
4+
5+
export default defineComponent({
6+
props: {
7+
recordingId: {
8+
type: Number as PropType<number>,
9+
required: true,
10+
},
11+
},
12+
emits: ['close'],
13+
setup(props, { emit }) {
14+
const annotationData = ref<FileAnnotationDetails | null>(null);
15+
const loading = ref(true);
16+
17+
onMounted(async () => {
18+
try {
19+
const response = await getFileAnnotationDetails(props.recordingId);
20+
annotationData.value = response.data.details;
21+
} catch (error) {
22+
console.error('Error fetching annotation details:', error);
23+
} finally {
24+
loading.value = false;
25+
}
26+
});
27+
28+
return { annotationData, loading, emit };
29+
},
30+
});
31+
</script>
32+
33+
<template>
34+
<v-card v-if="annotationData">
35+
<v-card-title>
36+
<v-row dense>
37+
Annotation Details
38+
<v-spacer />
39+
<v-icon @click="$emit('close')">
40+
mdi-close
41+
</v-icon>
42+
</v-row>
43+
</v-card-title>
44+
<v-card-text>
45+
<div>
46+
<h3>{{ annotationData.label }} (Score: {{ annotationData.score.toFixed(2) }})</h3>
47+
<v-data-table
48+
:headers="[
49+
{ title: 'Label', value: 'label', sortable: true },
50+
{ title: 'Value', value: 'value', sortable: true }
51+
]"
52+
:items="annotationData.confidences"
53+
:items-per-page="-1"
54+
density="compact"
55+
>
56+
<template #bottom />
57+
</v-data-table>
58+
</div>
59+
</v-card-text>
60+
</v-card>
61+
</template>

client/src/components/RecordingAnnotationEditor.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,14 @@ export default defineComponent({
3434
3535
const speciesEdit: Ref<string[]> = ref( props.annotation?.species?.map((item) => item.species_code || item.common_name) || []);
3636
const comments: Ref<string> = ref(props.annotation?.comments || '');
37-
const confidence: Ref<number> = ref(1.0);
37+
const confidence: Ref<number> = ref(props.annotation?.confidence || 1.0);
3838
3939
watch(() => props.annotation, () => {
4040
if (props.annotation?.species) {
4141
speciesEdit.value = props.annotation.species.map((item) => item.species_code || item.common_name);
4242
}
43-
if (props.annotation?.comments) {
44-
comments.value = props.annotation.comments;
43+
if (props.annotation) {
44+
comments.value = props.annotation.comments || '';
4545
}
4646
if (props.annotation) {
4747
confidence.value = props.annotation.confidence;

client/src/components/RecordingAnnotations.vue

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { defineComponent, onMounted, PropType, Ref } from "vue";
33
import { ref } from "vue";
44
import { FileAnnotation, getFileAnnotations, putFileAnnotation, Species, UpdateFileAnnotation } from "../api/api";
55
import RecordingAnnotationEditor from "./RecordingAnnotationEditor.vue";
6+
import RecordingAnnotationDetails from "./RecordingAnnotationDetails.vue";
67
export default defineComponent({
78
name: "AnnotationList",
89
components: {
910
RecordingAnnotationEditor,
11+
RecordingAnnotationDetails,
1012
},
1113
props: {
1214
species: {
@@ -23,6 +25,8 @@ export default defineComponent({
2325
const selectedAnnotation: Ref<null | FileAnnotation> = ref(null);
2426
const annotationState: Ref<'creating' | 'editing' | null> = ref(null);
2527
const annotations: Ref<FileAnnotation[]> = ref([]);
28+
const detailsDialog = ref(false);
29+
const detailRecordingId = ref(-1);
2630
2731
const setSelectedId = (annotation: FileAnnotation) => {
2832
selectedAnnotation.value = annotation;
@@ -63,13 +67,21 @@ export default defineComponent({
6367
}
6468
};
6569
70+
const loadDetails = async (id: number) => {
71+
detailRecordingId.value = id;
72+
detailsDialog.value = true;
73+
};
74+
6675
return {
6776
selectedAnnotation,
6877
annotationState,
6978
annotations,
7079
setSelectedId,
7180
addAnnotation,
7281
updatedAnnotation,
82+
loadDetails,
83+
detailsDialog,
84+
detailRecordingId,
7385
};
7486
},
7587
});
@@ -103,7 +115,14 @@ export default defineComponent({
103115
>
104116
<v-row>
105117
<v-col class="annotation-owner">
106-
<span>{{ annotation.owner }}</span>
118+
<div>{{ annotation.owner }}</div>
119+
<v-btn
120+
v-if="annotation.hasDetails"
121+
size="small"
122+
@click.stop.prevent="loadDetails(annotation.id)"
123+
>
124+
Details
125+
</v-btn>
107126
</v-col>
108127
<v-col class="annotation-confidence">
109128
<span>{{ annotation.confidence }} </span>
@@ -142,6 +161,15 @@ export default defineComponent({
142161
@update:annotation="updatedAnnotation()"
143162
@delete:annotation="updatedAnnotation(true)"
144163
/>
164+
<v-dialog
165+
v-model="detailsDialog"
166+
width="600"
167+
>
168+
<RecordingAnnotationDetails
169+
:recording-id="detailRecordingId"
170+
@close="detailsDialog = false"
171+
/>
172+
</v-dialog>
145173
</div>
146174
</template>
147175

0 commit comments

Comments
 (0)