From e5e4a63f0269f3d8dc1e03f5de340c57012488f6 Mon Sep 17 00:00:00 2001 From: Bryon Lewis Date: Wed, 7 Feb 2024 11:30:35 -0500 Subject: [PATCH] basics of rendering temporal annotations --- bats_ai/core/admin/__init__.py | 2 + bats_ai/core/admin/temporal_annotations.py | 18 ++ .../migrations/0007_temporalannotations.py | 22 +- bats_ai/core/views/recording.py | 9 + bats_ai/core/views/temporal_annotations.py | 14 +- client/src/App.vue | 8 +- client/src/api/api.ts | 12 ++ client/src/components/SpectrogramViewer.vue | 6 + client/src/components/TemporalList.vue | 114 +++++++++++ client/src/components/UploadRecording.vue | 2 +- client/src/components/geoJS/LayerManager.vue | 35 +++- client/src/components/geoJS/geoJSUtils.ts | 93 ++++++++- .../components/geoJS/layers/rectangleLayer.ts | 11 - .../components/geoJS/layers/temporalLayer.ts | 193 ++++++++++++++++++ client/src/router/index.ts | 14 +- client/src/use/useState.ts | 2 +- client/src/views/Login.vue | 17 ++ client/src/views/Recordings.vue | 2 +- client/src/views/Spectrogram.vue | 6 + 19 files changed, 547 insertions(+), 33 deletions(-) create mode 100644 bats_ai/core/admin/temporal_annotations.py create mode 100644 client/src/components/TemporalList.vue create mode 100644 client/src/components/geoJS/layers/temporalLayer.ts create mode 100644 client/src/views/Login.vue diff --git a/bats_ai/core/admin/__init__.py b/bats_ai/core/admin/__init__.py index 68eb9b8..c405338 100644 --- a/bats_ai/core/admin/__init__.py +++ b/bats_ai/core/admin/__init__.py @@ -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', ] diff --git a/bats_ai/core/admin/temporal_annotations.py b/bats_ai/core/admin/temporal_annotations.py new file mode 100644 index 0000000..9415855 --- /dev/null +++ b/bats_ai/core/admin/temporal_annotations.py @@ -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'] diff --git a/bats_ai/core/migrations/0007_temporalannotations.py b/bats_ai/core/migrations/0007_temporalannotations.py index 0351928..5fbf65f 100644 --- a/bats_ai/core/migrations/0007_temporalannotations.py +++ b/bats_ai/core/migrations/0007_temporalannotations.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('core', '0006_alter_recording_recording_location'), @@ -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' + ), + ), ], ), ] diff --git a/bats_ai/core/views/recording.py b/bats_ai/core/views/recording.py index 1aa48ba..84c653d 100644 --- a/bats_ai/core/views/recording.py +++ b/bats_ai/core/views/recording.py @@ -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 diff --git a/bats_ai/core/views/temporal_annotations.py b/bats_ai/core/views/temporal_annotations.py index e961363..10785b8 100644 --- a/bats_ai/core/views/temporal_annotations.py +++ b/bats_ai/core/views/temporal_annotations.py @@ -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}') diff --git a/client/src/App.vue b/client/src/App.vue index c31c389..43b0aa9 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,18 +1,21 @@ + + + + diff --git a/client/src/components/UploadRecording.vue b/client/src/components/UploadRecording.vue index 8f71304..75bf020 100644 --- a/client/src/components/UploadRecording.vue +++ b/client/src/components/UploadRecording.vue @@ -291,7 +291,7 @@ export default defineComponent({ diff --git a/client/src/components/geoJS/LayerManager.vue b/client/src/components/geoJS/LayerManager.vue index 8a1ad46..1c78bf5 100644 --- a/client/src/components/geoJS/LayerManager.vue +++ b/client/src/components/geoJS/LayerManager.vue @@ -1,9 +1,10 @@ + + diff --git a/client/src/views/Recordings.vue b/client/src/views/Recordings.vue index 37bc374..944d9cb 100644 --- a/client/src/views/Recordings.vue +++ b/client/src/views/Recordings.vue @@ -119,7 +119,7 @@ export default defineComponent({ id: item.id, }; if (item.recording_location) { - const [ lat, lon ] = item.recording_location.coordinates; + const [ lon, lat ] = item.recording_location.coordinates; editingRecording.value['location'] = {lat, lon}; } uploadDialog.value = true; diff --git a/client/src/views/Spectrogram.vue b/client/src/views/Spectrogram.vue index 3a7b421..f6635b7 100644 --- a/client/src/views/Spectrogram.vue +++ b/client/src/views/Spectrogram.vue @@ -9,6 +9,7 @@ import { getSpectrogramCompressed, OtherUserAnnotations, getOtherUserAnnotations, + SpectrogramTemporalAnnotation, } from "../api/api"; import SpectrogramViewer from "../components/SpectrogramViewer.vue"; import { SpectroInfo } from "../components/geoJS/geoJSUtils"; @@ -43,6 +44,7 @@ export default defineComponent({ const image: Ref = ref(new Image()); const spectroInfo: Ref = ref(); const annotations: Ref = ref([]); + const temporalAnnotations: Ref = ref([]); const otherUserAnnotations: Ref = ref({}); const selectedId: Ref = ref(null); const selectedUsers: Ref = ref([]); @@ -84,6 +86,7 @@ export default defineComponent({ image.value.src = `data:image/png;base64,${response.data["base64_spectrogram"]}`; spectroInfo.value = response.data["spectroInfo"]; annotations.value = response.data["annotations"]?.sort((a, b) => a.start_time - b.start_time); + temporalAnnotations.value = response.data["temporal"]?.sort((a, b) => a.start_time - b.start_time); if (response.data.currentUser) { currentUser.value = response.data.currentUser; } @@ -175,6 +178,7 @@ export default defineComponent({ freqRef, // Other user selection otherUserAnnotations, + temporalAnnotations, otherUsers, selectedUsers, deleteChip, @@ -319,6 +323,7 @@ export default defineComponent({ :spectro-info="spectroInfo" :recording-id="id" :annotations="annotations" + :temporal-annotations="temporalAnnotations" :other-user-annotations="otherUserAnnotations" :selected-id="selectedId" :grid="gridEnabled" @@ -334,6 +339,7 @@ export default defineComponent({ :spectro-info="spectroInfo" :recording-id="id" :annotations="annotations" + :temporal-annotations="temporalAnnotations" :other-user-annotations="otherUserAnnotations" :selected-id="selectedId" :parent-geo-viewer-ref="parentGeoViewerRef"