Skip to content

Commit

Permalink
basics of rendering temporal annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
BryonLewis committed Feb 7, 2024
1 parent db68837 commit e5e4a63
Show file tree
Hide file tree
Showing 19 changed files with 547 additions and 33 deletions.
2 changes: 2 additions & 0 deletions bats_ai/core/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from .image import ImageAdmin
from .recording import RecordingAdmin
from .spectrogram import SpectrogramAdmin
from .temporal_annotations import TemporalAnnotationsAdmin

__all__ = [
'AnnotationsAdmin',
'ImageAdmin',
'RecordingAdmin',
'SpectrogramAdmin',
'TemporalAnnotationsAdmin',
]
18 changes: 18 additions & 0 deletions bats_ai/core/admin/temporal_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib import admin

from bats_ai.core.models import TemporalAnnotations


@admin.register(TemporalAnnotations)
class TemporalAnnotationsAdmin(admin.ModelAdmin):
list_display = [
'pk',
'recording',
'owner',
'start_time',
'end_time',
'type',
'comments',
]
list_select_related = True
autocomplete_fields = ['owner']
22 changes: 18 additions & 4 deletions bats_ai/core/migrations/0007_temporalannotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('core', '0006_alter_recording_recording_location'),
Expand All @@ -16,13 +15,28 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='TemporalAnnotations',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
(
'id',
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name='ID'
),
),
('start_time', models.IntegerField(blank=True, null=True)),
('end_time', models.IntegerField(blank=True, null=True)),
('type', models.TextField(blank=True, null=True)),
('comments', models.TextField(blank=True, null=True)),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
('recording', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.recording')),
(
'owner',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
(
'recording',
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to='core.recording'
),
),
],
),
]
9 changes: 9 additions & 0 deletions bats_ai/core/views/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,22 @@ def get_spectrogram(request: HttpRequest, id: int):
spectro_data['currentUser'] = request.user.email

annotations_qs = Annotations.objects.filter(recording=recording, owner=request.user)
temporal_annotations_qs = TemporalAnnotations.objects.filter(
recording=recording, owner=request.user
)

# Serialize the annotations using AnnotationSchema
annotations_data = [
AnnotationSchema.from_orm(annotation, owner_email=request.user.email).dict()
for annotation in annotations_qs
]
temporal_annotations_data = [
TemporalAnnotationSchema.from_orm(annotation, owner_email=request.user.email).dict()
for annotation in temporal_annotations_qs
]

spectro_data['annotations'] = annotations_data
spectro_data['temporal'] = temporal_annotations_data
return spectro_data


Expand Down
14 changes: 12 additions & 2 deletions bats_ai/core/views/temporal_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@


class TemporalAnnotationSchema(Schema):
recording: int # Foreign Key to index
owner_username: str
start_time: int
end_time: int
type: str
comments: str
owner_email: str = None

@classmethod
def from_orm(cls, obj, owner_email=None, **kwargs):
return cls(
start_time=obj.start_time,
end_time=obj.end_time,
type=obj.type,
comments=obj.comments,
id=obj.id,
owner_email=owner_email, # Include owner_email in the schema
)


@router.get('/{id}')
Expand Down
8 changes: 6 additions & 2 deletions client/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
<script lang="ts">
import { defineComponent, inject, computed, ref } from 'vue';
import OAuthClient from '@girder/oauth-client';
import { useRouter } from 'vue-router';
export default defineComponent({
setup() {
const oauthClient = inject<OAuthClient>('oauthClient');
const router = useRouter();
if (oauthClient === undefined) {
throw new Error('Must provide "oauthClient" into component.');
}
const loginText = computed(() => (oauthClient.isLoggedIn ? 'Logout' : 'Login'));
const logInOrOut = () => {
const logInOrOut = async () => {
if (oauthClient.isLoggedIn) {
oauthClient.logout();
await oauthClient.logout();
router.push('Login');
} else {
oauthClient.redirectToLogin();
}
Expand All @@ -27,6 +30,7 @@ export default defineComponent({
<v-app id="app">
<v-app-bar app>
<v-tabs
v-if="oauthClient.isLoggedIn"
fixed-tabs
:model-value="activeTab"
>
Expand Down
12 changes: 12 additions & 0 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ export interface SpectrogramAnnotation {
owner_email?: string;
}

export interface SpectrogramTemporalAnnotation {
start_time: number;
end_time: number;
id: number;
editing?: boolean;
type?: string;
comments?: string;
owner_email?: string;
}


export interface UpdateSpectrogramAnnotation {
start_time?: number;
end_time?: number;
Expand All @@ -81,6 +92,7 @@ export interface Spectrogram {
url?: string;
filename?: string;
annotations?: SpectrogramAnnotation[];
temporal?: SpectrogramTemporalAnnotation[];
spectroInfo?: SpectroInfo;
currentUser?: string;
otherUsers?: UserInfo[];
Expand Down
6 changes: 6 additions & 0 deletions client/src/components/SpectrogramViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
patchAnnotation,
putAnnotation,
SpectrogramAnnotation,
SpectrogramTemporalAnnotation,
} from "../api/api";
import LayerManager from "./geoJS/LayerManager.vue";
import { GeoEvent } from "geojs";
Expand All @@ -29,6 +30,10 @@ export default defineComponent({
type: Array as PropType<SpectrogramAnnotation[]>,
default: () => [],
},
temporalAnnotations: {
type: Array as PropType<SpectrogramTemporalAnnotation[]>,
default: () => [],
},
otherUserAnnotations: {
type: Object as PropType<OtherUserAnnotations>,
default: () => ({}),
Expand Down Expand Up @@ -214,6 +219,7 @@ export default defineComponent({
:geo-viewer-ref="geoViewerRef"
:spectro-info="spectroInfo"
:annotations="annotations"
:temporal-annotations="temporalAnnotations"
:other-user-annotations="otherUserAnnotations"
:selected-id="selectedId"
@selected="clickSelected($event)"
Expand Down
114 changes: 114 additions & 0 deletions client/src/components/TemporalList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { SpectroInfo } from './geoJS/geoJSUtils';
import { SpectrogramTemporalAnnotation } from "../api/api";
import useState from "../use/useState";
import { watch } from "vue";
export default defineComponent({
name: "TemporalList",
components: {
},
props: {
spectroInfo: {
type: Object as PropType<SpectroInfo | undefined>,
default: () => undefined,
},
annotations: {
type: Array as PropType<SpectrogramTemporalAnnotation[]>,
default: () => [],
},
selectedId: {
type: Number as PropType<number | null>,
default: null,
},
},
emits: ['select'],
setup(props) {
const { annotationState, setAnnotationState } = useState();
const scrollToId = (id: number) => {
const el = document.getElementById(`annotation-${id}`);
if (el) {
el.scrollIntoView({block: 'end', behavior: 'smooth'});
}
};
watch(() => props.selectedId, () => {
if (props.selectedId !== null) {
scrollToId(props.selectedId);
}
});
return {
annotationState,
setAnnotationState,
};
},
});
</script>

<template>
<v-card class="pa-0 ma-0">
<v-card-title>
<v-row class="pa-2">
Temporal Annotations
<v-spacer />
<v-btn
:disabled="annotationState === 'creating'"
@click="annotationState = 'creating'"
>
Add<v-icon>mdi-plus</v-icon>
</v-btn>
</v-row>
</v-card-title>
<v-list>
<v-list-item>
<v-row dense>
<v-col><b>Time</b></v-col>
</v-row>
</v-list-item>
<v-list-item
v-for="annotation in annotations"
:id="`annotation-${annotation.id}`"
:key="annotation.id"
:class="{selected: annotation.id===selectedId}"
class="annotation-item"
@click="$emit('select', annotation.id)"
>
<v-row>
<v-col class="annotation-time">
<span>{{ annotation.start_time }}-{{ annotation.end_time }}ms</span>
</v-col>
</v-row>
<v-row
class="ma-0 pa-0"
>
<v-col class="ma-0 pa-0">
<div class="type-name">
{{ annotation.type }}
</div>
</v-col>
</v-row>
</v-list-item>
</v-list>
</v-card>
</template>

<style lang="scss" scoped>
.annotation-id {
cursor:pointer;
text-decoration: underline;
}
.annotation-time {
font-size: 1em;
}
.annotation-item {
border: 1px solid gray;
}
.type-name {
font-weight: bold;
font-size: 1em;
}.selected {
background-color: cyan;
}
</style>
2 changes: 1 addition & 1 deletion client/src/components/UploadRecording.vue
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export default defineComponent({
<v-spacer />
<map-location
:size="{width: 600, height: 400}"
:location="{ x: latitude, y: longitude}"
:location="{ x: longitude, y: latitude}"
:update-map="updateMap"
@location="setLocation($event)"
/>
Expand Down
Loading

0 comments on commit e5e4a63

Please sign in to comment.