Skip to content

Commit

Permalink
temporal creation/editing/deletion
Browse files Browse the repository at this point in the history
  • Loading branch information
BryonLewis committed Feb 16, 2024
1 parent 3a81aea commit e077453
Show file tree
Hide file tree
Showing 17 changed files with 739 additions and 244 deletions.
2 changes: 2 additions & 0 deletions bats_ai/core/admin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from .annotations import AnnotationsAdmin
from .image import ImageAdmin
from .recording import RecordingAdmin
from .species import SpeciesAdmin
from .spectrogram import SpectrogramAdmin
from .temporal_annotations import TemporalAnnotationsAdmin

Expand All @@ -10,4 +11,5 @@
'RecordingAdmin',
'SpectrogramAdmin',
'TemporalAnnotationsAdmin',
'SpeciesAdmin',
]
17 changes: 17 additions & 0 deletions bats_ai/core/admin/species.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.contrib import admin

from bats_ai.core.models import Species


@admin.register(Species)
class SpeciesAdmin(admin.ModelAdmin):
list_display = [
'pk',
'species_code',
'family',
'genus',
'species',
'common_name',
'species_code_6',
]
list_select_related = True
3 changes: 2 additions & 1 deletion bats_ai/core/migrations/0007_temporalannotations.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 4.1.13 on 2024-02-07 13:23
# Generated by Django 4.1.13 on 2024-02-15 18:08

from django.conf import settings
from django.db import migrations, models
Expand Down Expand Up @@ -37,6 +37,7 @@ class Migration(migrations.Migration):
on_delete=django.db.models.deletion.CASCADE, to='core.recording'
),
),
('species', models.ManyToManyField(to='core.species')),
],
),
]
2 changes: 2 additions & 0 deletions bats_ai/core/models/temporal_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db import models

from .recording import Recording
from .species import Species


class TemporalAnnotations(models.Model):
Expand All @@ -11,3 +12,4 @@ class TemporalAnnotations(models.Model):
end_time = models.IntegerField(blank=True, null=True)
type = models.TextField(blank=True, null=True)
comments = models.TextField(blank=True, null=True)
species = models.ManyToManyField(Species)
82 changes: 69 additions & 13 deletions bats_ai/core/views/recording.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@

from bats_ai.core.models import Annotations, Recording, Species, TemporalAnnotations
from bats_ai.core.views.species import SpeciesSchema
from bats_ai.core.views.temporal_annotations import TemporalAnnotationSchema
from bats_ai.core.views.temporal_annotations import (
TemporalAnnotationSchema,
UpdateTemporalAnnotationSchema,
)

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -413,7 +416,7 @@ def patch_annotation(
recording_id: int,
id: int,
annotation: UpdateAnnotationsSchema,
species_ids: list[int],
species_ids: list[int] | None,
):
try:
recording = Recording.objects.get(pk=recording_id)
Expand All @@ -438,16 +441,68 @@ def patch_annotation(
annotation_instance.save()

# Clear existing species associations
annotation_instance.species.clear()
if species_ids:
annotation_instance.species.clear()
# Add species to the annotation based on the provided species_ids
for species_id in species_ids:
try:
species_obj = Species.objects.get(pk=species_id)
annotation_instance.species.add(species_obj)
except Species.DoesNotExist:
# Handle the case where the species with the given ID doesn't exist
return {'error': f'Species with ID {species_id} not found'}

# Add species to the annotation based on the provided species_ids
for species_id in species_ids:
try:
species_obj = Species.objects.get(pk=species_id)
annotation_instance.species.add(species_obj)
except Species.DoesNotExist:
# Handle the case where the species with the given ID doesn't exist
return {'error': f'Species with ID {species_id} not found'}
return {'message': 'Annotation updated successfully', 'id': annotation_instance.pk}
else:
return {
'error': 'Permission denied. You do not own this recording, and it is not public.'
}

except Recording.DoesNotExist:
return {'error': 'Recording not found'}
except Annotations.DoesNotExist:
return {'error': 'Annotation not found'}


@router.patch('/{recording_id}/temporal-annotations/{id}')
def patch_temporal_annotation(
request,
recording_id: int,
id: int,
annotation: UpdateTemporalAnnotationSchema,
species_ids: list[int] | None,
):
try:
recording = Recording.objects.get(pk=recording_id)

# Check if the user owns the recording or if the recording is public
if recording.owner == request.user or recording.public:
annotation_instance = TemporalAnnotations.objects.get(
pk=id, recording=recording, owner=request.user
)

# Update annotation details
if annotation.start_time:
annotation_instance.start_time = annotation.start_time
if annotation.end_time:
annotation_instance.end_time = annotation.end_time
if annotation.comments:
annotation_instance.comments = annotation.comments
if annotation.type:
annotation_instance.type = annotation.type
annotation_instance.save()

# Clear existing species associations
if species_ids:
annotation_instance.species.clear()
# Add species to the annotation based on the provided species_ids
for species_id in species_ids:
try:
species_obj = Species.objects.get(pk=species_id)
annotation_instance.species.add(species_obj)
except Species.DoesNotExist:
# Handle the case where the species with the given ID doesn't exist
return {'error': f'Species with ID {species_id} not found'}

return {'message': 'Annotation updated successfully', 'id': annotation_instance.pk}
else:
Expand Down Expand Up @@ -490,7 +545,7 @@ def delete_annotation(request, recording_id: int, id: int):
# TEMPORAL ANNOTATIONS


@router.get('recording/{id}/temporal-annotations')
@router.get('/{id}/temporal-annotations')
def get_temporal_annotations(request: HttpRequest, id: int):
try:
recording = Recording.objects.get(pk=id)
Expand Down Expand Up @@ -518,11 +573,12 @@ def get_temporal_annotations(request: HttpRequest, id: int):
return {'error': 'Recording not found'}


@router.put('recording/{id}/temporal-annotations')
@router.put('/{id}/temporal-annotations')
def put_temporal_annotation(
request,
id: int,
annotation: TemporalAnnotationSchema,
species_ids: list[int] | None,
):
try:
recording = Recording.objects.get(pk=id)
Expand Down
11 changes: 11 additions & 0 deletions bats_ai/core/views/temporal_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
from ninja.pagination import RouterPaginated

from bats_ai.core.models import Annotations, Recording, TemporalAnnotations
from bats_ai.core.views.species import SpeciesSchema

router = RouterPaginated()


class TemporalAnnotationSchema(Schema):
id: int
start_time: int
end_time: int
type: str
comments: str
species: list[SpeciesSchema] | None
owner_email: str = None

@classmethod
Expand All @@ -20,12 +23,20 @@ def from_orm(cls, obj, owner_email=None, **kwargs):
start_time=obj.start_time,
end_time=obj.end_time,
type=obj.type,
species=[SpeciesSchema.from_orm(species) for species in obj.species.all()],
comments=obj.comments,
id=obj.id,
owner_email=owner_email, # Include owner_email in the schema
)


class UpdateTemporalAnnotationSchema(Schema):
start_time: int = None
end_time: int = None
type: str | None = None
comments: str | None = None


@router.get('/{id}')
def get_temporal_annotation(request: HttpRequest, id: int):
try:
Expand Down
34 changes: 32 additions & 2 deletions client/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface SpectrogramTemporalAnnotation {
editing?: boolean;
type?: string;
comments?: string;
species?: Species[];
owner_email?: string;
}

Expand All @@ -82,6 +83,16 @@ export interface UpdateSpectrogramAnnotation {
comments?: string;
}

export interface UpdateSpectrogramTemporalAnnotation {
start_time?: number;
end_time?: number;
id?: number;
editing?: boolean;
species?: Species[];
comments?: string;
type?: string;
}

export interface UserInfo {
username: string;
email: string;
Expand Down Expand Up @@ -186,25 +197,40 @@ async function getSpectrogramCompressed(id: string) {

async function getAnnotations(recordingId: string) {
return axiosInstance.get<SpectrogramAnnotation[]>(`/recording/${recordingId}/annotations`);
}

async function getTemporalAnnotations(recordingId: string) {
return axiosInstance.get<SpectrogramTemporalAnnotation[]>(`/recording/${recordingId}/temporal-annotations`);
}

async function getSpecies() {
return axiosInstance.get<Species[]>('/species/');
}

async function patchAnnotation(recordingId: string, annotationId: number, annotation: UpdateSpectrogramAnnotation, speciesList: number[] = []) {
async function patchAnnotation(recordingId: string, annotationId: number, annotation: UpdateSpectrogramAnnotation, speciesList: number[] | null = null) {
return axiosInstance.patch(`/recording/${recordingId}/annotations/${annotationId}`, { annotation, species_ids: speciesList});
}

async function patchTemporalAnnotation(recordingId: string, annotationId: number, annotation: UpdateSpectrogramTemporalAnnotation, speciesList: number[] | null = null) {
return axiosInstance.patch(`/recording/${recordingId}/temporal-annotations/${annotationId}`, { annotation, species_ids: speciesList});
}

async function putAnnotation(recordingId: string, annotation: UpdateSpectrogramAnnotation, speciesList: number[] = []) {
return axiosInstance.put<{message: string, id: number}>(`/recording/${recordingId}/annotations`, { annotation, species_ids: speciesList});
}

async function putTemporalAnnotation(recordingId: string, annotation: UpdateSpectrogramTemporalAnnotation, speciesList: number[] | null = null) {
return axiosInstance.put<{message: string, id: number}>(`/recording/${recordingId}/temporal-annotations`, { annotation, species_ids: speciesList});
}

async function deleteAnnotation(recordingId: string, annotationId: number) {
return axiosInstance.delete(`/recording/${recordingId}/annotations/${annotationId}`);
}

async function deleteTemporalAnnotation(recordingId: string, annotationId: number) {
return axiosInstance.delete(`/recording/${recordingId}/temporal-annotations/${annotationId}`);
}

async function getOtherUserAnnotations(recordingId: string) {
return axiosInstance.get<OtherUserAnnotations>(`/recording/${recordingId}/annotations/other_users`);
}
Expand All @@ -215,10 +241,14 @@ export {
patchRecording,
getSpectrogram,
getSpectrogramCompressed,
getTemporalAnnotations,
getOtherUserAnnotations,
getSpecies,
getAnnotations,
patchAnnotation,
patchTemporalAnnotation,
putAnnotation,
deleteAnnotation
putTemporalAnnotation,
deleteAnnotation,
deleteTemporalAnnotation,
};
Loading

0 comments on commit e077453

Please sign in to comment.