-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add recording annnotation and migrations
- Loading branch information
1 parent
63c0dcd
commit c145a9c
Showing
8 changed files
with
281 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
bats_ai/core/migrations/0011_alter_annotations_options_annotations_confidence_and_more.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
# Generated by Django 4.1.13 on 2024-12-02 15:22 | ||
|
||
from django.conf import settings | ||
import django.core.validators | ||
from django.db import migrations, models | ||
import django.db.models.deletion | ||
import django.utils.timezone | ||
import django_extensions.db.fields | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
migrations.swappable_dependency(settings.AUTH_USER_MODEL), | ||
('core', '0010_compressedspectrogram'), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterModelOptions( | ||
name='annotations', | ||
options={'get_latest_by': 'modified'}, | ||
), | ||
migrations.AddField( | ||
model_name='annotations', | ||
name='confidence', | ||
field=models.FloatField(blank=True, null=True), | ||
), | ||
migrations.AddField( | ||
model_name='annotations', | ||
name='created', | ||
field=django_extensions.db.fields.CreationDateTimeField( | ||
auto_now_add=True, default=django.utils.timezone.now, verbose_name='created' | ||
), | ||
preserve_default=False, | ||
), | ||
migrations.AddField( | ||
model_name='annotations', | ||
name='model', | ||
field=models.TextField(blank=True, null=True), | ||
), | ||
migrations.AddField( | ||
model_name='annotations', | ||
name='modified', | ||
field=django_extensions.db.fields.ModificationDateTimeField( | ||
auto_now=True, verbose_name='modified' | ||
), | ||
), | ||
migrations.CreateModel( | ||
name='RecordingAnnotation', | ||
fields=[ | ||
( | ||
'id', | ||
models.BigAutoField( | ||
auto_created=True, primary_key=True, serialize=False, verbose_name='ID' | ||
), | ||
), | ||
( | ||
'created', | ||
django_extensions.db.fields.CreationDateTimeField( | ||
auto_now_add=True, verbose_name='created' | ||
), | ||
), | ||
( | ||
'modified', | ||
django_extensions.db.fields.ModificationDateTimeField( | ||
auto_now=True, verbose_name='modified' | ||
), | ||
), | ||
('comments', models.TextField(blank=True, null=True)), | ||
('model', models.TextField(blank=True, null=True)), | ||
( | ||
'confidence', | ||
models.FloatField( | ||
default=1.0, | ||
help_text='A confidence value between 0 and 1.0, default is 1.0.', | ||
validators=[ | ||
django.core.validators.MinValueValidator(0.0), | ||
django.core.validators.MaxValueValidator(1.0), | ||
], | ||
), | ||
), | ||
( | ||
'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' | ||
), | ||
), | ||
('species', models.ManyToManyField(to='core.species')), | ||
], | ||
options={ | ||
'get_latest_by': 'modified', | ||
'abstract': False, | ||
}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,23 @@ | ||
from django.contrib.auth.models import User | ||
from django.core.validators import MaxValueValidator, MinValueValidator | ||
from django.db import models | ||
from django_extensions.db.models import TimeStampedModel | ||
|
||
from .recording import Recording | ||
from .species import Species | ||
|
||
|
||
class RecordingAnnotation(models.Model): | ||
class RecordingAnnotation(TimeStampedModel, models.Model): | ||
recording = models.ForeignKey(Recording, on_delete=models.CASCADE) | ||
owner = models.ForeignKey(User, on_delete=models.CASCADE) | ||
species = models.ManyToManyField(Species) | ||
comments = models.TextField(blank=True, null=True) | ||
model = models.TextField(blank=True, null=True) # AI Model information if inference used | ||
confidence = models.FloatField( | ||
default=1.0, | ||
validators=[ | ||
MinValueValidator(0.0), | ||
MaxValueValidator(1.0), | ||
], | ||
help_text='A confidence value between 0 and 1.0, default is 1.0.', | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import logging | ||
|
||
from django.http import HttpRequest | ||
from ninja import Router, Schema | ||
from ninja.errors import HttpError | ||
|
||
from bats_ai.core.models import Recording, RecordingAnnotation, Species | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
router = Router() | ||
|
||
|
||
# Schemas for serialization | ||
class RecordingAnnotationSchema(Schema): | ||
id: int | ||
recording: int | ||
owner: str | ||
species: list[int] | ||
comments: str = None | ||
model: str = None | ||
confidence: float | ||
|
||
|
||
class CreateRecordingAnnotationSchema(Schema): | ||
recording: int | ||
species: list[int] | ||
comments: str = None | ||
model: str = None | ||
confidence: float | ||
|
||
|
||
class UpdateRecordingAnnotationSchema(Schema): | ||
species: list[int] = None | ||
comments: str = None | ||
model: str = None | ||
confidence: float = None | ||
|
||
|
||
# GET Endpoint | ||
@router.get('/{id}', response=RecordingAnnotationSchema) | ||
def get_recording_annotation(request: HttpRequest, id: int): | ||
try: | ||
annotation = RecordingAnnotation.objects.get(pk=id) | ||
|
||
# Check permission | ||
if annotation.recording.owner != request.user and not annotation.recording.public: | ||
raise HttpError(403, 'Permission denied.') | ||
|
||
return { | ||
'id': annotation.id, | ||
'recording': annotation.recording.id, | ||
'owner': annotation.owner.username, | ||
'species': [s.id for s in annotation.species.all()], | ||
'comments': annotation.comments, | ||
'model': annotation.model, | ||
'confidence': annotation.confidence, | ||
} | ||
except RecordingAnnotation.DoesNotExist: | ||
raise HttpError(404, 'Recording annotation not found.') | ||
|
||
|
||
# PUT Endpoint | ||
@router.put('/', response={200: str}) | ||
def create_recording_annotation(request: HttpRequest, data: CreateRecordingAnnotationSchema): | ||
try: | ||
recording = Recording.objects.get(pk=data.recording) | ||
|
||
# Check permission | ||
if recording.owner != request.user and not recording.public: | ||
raise HttpError(403, 'Permission denied.') | ||
|
||
# Create the recording annotation | ||
annotation = RecordingAnnotation.objects.create( | ||
recording=recording, | ||
owner=request.user, | ||
comments=data.comments, | ||
model=data.model, | ||
confidence=data.confidence, | ||
) | ||
|
||
# Add species | ||
for species_id in data.species: | ||
species = Species.objects.get(pk=species_id) | ||
annotation.species.add(species) | ||
|
||
return 'Recording annotation created successfully.' | ||
except Recording.DoesNotExist: | ||
raise HttpError(404, 'Recording not found.') | ||
except Species.DoesNotExist: | ||
raise HttpError(404, 'One or more species IDs not found.') | ||
|
||
|
||
# PATCH Endpoint | ||
@router.patch('/{id}', response={200: str}) | ||
def update_recording_annotation( | ||
request: HttpRequest, id: int, data: UpdateRecordingAnnotationSchema | ||
): | ||
try: | ||
annotation = RecordingAnnotation.objects.get(pk=id) | ||
|
||
# Check permission | ||
if annotation.recording.owner != request.user: | ||
raise HttpError(403, 'Permission denied.') | ||
|
||
# Update fields if provided | ||
if data.comments is not None: | ||
annotation.comments = data.comments | ||
if data.model is not None: | ||
annotation.model = data.model | ||
if data.confidence is not None: | ||
annotation.confidence = data.confidence | ||
if data.species is not None: | ||
annotation.species.clear() # Clear existing species | ||
for species_id in data.species: | ||
species = Species.objects.get(pk=species_id) | ||
annotation.species.add(species) | ||
|
||
annotation.save() | ||
return 'Recording annotation updated successfully.' | ||
except RecordingAnnotation.DoesNotExist: | ||
raise HttpError(404, 'Recording annotation not found.') | ||
except Species.DoesNotExist: | ||
raise HttpError(404, 'One or more species IDs not found.') | ||
|
||
|
||
# DELETE Endpoint | ||
@router.delete('/{id}', response={200: str}) | ||
def delete_recording_annotation(request: HttpRequest, id: int): | ||
try: | ||
annotation = RecordingAnnotation.objects.get(pk=id) | ||
|
||
# Check permission | ||
if annotation.recording.owner != request.user: | ||
raise HttpError(403, 'Permission denied.') | ||
|
||
annotation.delete() | ||
return 'Recording annotation deleted successfully.' | ||
except RecordingAnnotation.DoesNotExist: | ||
raise HttpError(404, 'Recording annotation not found.') |