diff --git a/bats_ai/core/views/recording.py b/bats_ai/core/views/recording.py index 71f559a..d0701d9 100644 --- a/bats_ai/core/views/recording.py +++ b/bats_ai/core/views/recording.py @@ -63,7 +63,7 @@ class RecordingUploadSchema(Schema): class RecordingAnnotationSchema(Schema): - # species: list[SpeciesSchema] | None + species: list[SpeciesSchema] | None comments: str | None = None model: str | None = None owner: str @@ -73,7 +73,7 @@ class RecordingAnnotationSchema(Schema): @classmethod def from_orm(cls, obj: RecordingAnnotation, **kwargs): return cls( - # species=[SpeciesSchema.from_orm(species) for species in obj.species.all()], + species=[SpeciesSchema.from_orm(species) for species in obj.species.all()], owner=obj.owner.username, confidence=obj.confidence, comments=obj.comments, @@ -301,6 +301,18 @@ def get_recording(request: HttpRequest, id: int): return {'error': 'Recording not found'} +@router.get('/{recording_id}/recording-annotations') +def get_recording_annotations(request: HttpRequest, recording_id: int): + fileAnnotations = RecordingAnnotation.objects.filter(recording=recording_id).order_by( + 'confidence' + ) + output = [ + RecordingAnnotationSchema.from_orm(fileAnnotation).dict() + for fileAnnotation in fileAnnotations + ] + return output + + @router.get('/{id}/spectrogram') def get_spectrogram(request: HttpRequest, id: int): try: diff --git a/bats_ai/core/views/recording_annotation.py b/bats_ai/core/views/recording_annotation.py index 3452e05..4f258c1 100644 --- a/bats_ai/core/views/recording_annotation.py +++ b/bats_ai/core/views/recording_annotation.py @@ -5,6 +5,7 @@ from ninja.errors import HttpError from bats_ai.core.models import Recording, RecordingAnnotation, Species +from bats_ai.core.views.recording import SpeciesSchema logger = logging.getLogger(__name__) @@ -13,17 +14,27 @@ # Schemas for serialization class RecordingAnnotationSchema(Schema): - id: int - recording: int + species: list[SpeciesSchema] | None + comments: str | None = None + model: str | None = None owner: str - species: list[int] - comments: str = None - model: str = None confidence: float + id: int | None = None + + @classmethod + def from_orm(cls, obj: RecordingAnnotation, **kwargs): + return cls( + species=[SpeciesSchema.from_orm(species) for species in obj.species.all()], + owner=obj.owner.username, + confidence=obj.confidence, + comments=obj.comments, + model=obj.model, + id=obj.pk, + ) class CreateRecordingAnnotationSchema(Schema): - recording: int + recordingId: int species: list[int] comments: str = None model: str = None @@ -37,7 +48,6 @@ class UpdateRecordingAnnotationSchema(Schema): confidence: float = None -# GET Endpoint @router.get('/{id}', response=RecordingAnnotationSchema) def get_recording_annotation(request: HttpRequest, id: int): try: @@ -47,24 +57,15 @@ def get_recording_annotation(request: HttpRequest, id: int): 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, - } + return RecordingAnnotationSchema.from_orm(annotation).dict() 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) + recording = Recording.objects.get(pk=data.recordingId) # Check permission if recording.owner != request.user and not recording.public: @@ -91,7 +92,6 @@ def create_recording_annotation(request: HttpRequest, data: CreateRecordingAnnot 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 diff --git a/client/src/api/api.ts b/client/src/api/api.ts index bb9f824..ff5b150 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -107,10 +107,30 @@ export interface UserInfo { email: string; id: number; } + +export interface FileAnnotation { + species: Species[]; + comments?: string; + model?: string; + owner: string; + confidence: number; + id: number; +} + +export interface UpdateFileAnnotation { + recordingId?: number; + species_list: number[] | null; + comments?: string; + model?: string; + confidence: number; + id?: number; +} + export interface Spectrogram { url: string; filename?: string; annotations?: SpectrogramAnnotation[]; + fileAnnotations: FileAnnotation[]; temporal?: SpectrogramTemporalAnnotation[]; spectroInfo?: SpectroInfo; compressed?: { @@ -310,6 +330,23 @@ async function getOtherUserAnnotations(recordingId: string) { async function getCellLocation(cellId: number, quadrant?: 'SW' | 'NE' | 'NW' | 'SE') { return axiosInstance.get(`/grts/${cellId}`, { params: { quadrant }}); } +async function getFileAnnotations(recordingId: number) { + return axiosInstance.get(`recording/${recordingId}/recording-annotations`); +} + + +async function putFileAnnotation(fileAnnotation: UpdateFileAnnotation) { + return axiosInstance.put<{message: string, id: number}>(`/recording-annotation`, { fileAnnotation }); +} + +async function patchFileAnnotation(fileAnnotationId: number, fileAnnotation: UpdateFileAnnotation) { + return axiosInstance.patch<{message: string, id: number}>(`/recording-annotation/${fileAnnotationId}`, { fileAnnotation }); +} + +async function deleteFileAnnotation(fileAnnotationId: number) { + return axiosInstance.delete<{message: string, id: number}>(`/recording-annotation/${fileAnnotationId}`); +} + interface CellIDReponse { grid_cell_id?: number; @@ -367,4 +404,8 @@ export { getCellLocation, getCellfromLocation, getGuanoMetadata, + getFileAnnotations, + putFileAnnotation, + patchFileAnnotation, + deleteFileAnnotation, }; \ No newline at end of file diff --git a/client/src/components/AnnotationList.vue b/client/src/components/AnnotationList.vue index cd4ac67..c1e648d 100644 --- a/client/src/components/AnnotationList.vue +++ b/client/src/components/AnnotationList.vue @@ -5,10 +5,12 @@ import useState from "../use/useState"; import { watch, ref } from "vue"; import AnnotationEditor from "./AnnotationEditor.vue"; import { Species, SpectrogramAnnotation, SpectrogramTemporalAnnotation } from "../api/api"; +import RecordingAnnotations from "./RecordingAnnotations.vue"; export default defineComponent({ name: "AnnotationList", components: { AnnotationEditor, + RecordingAnnotations, }, props: { spectroInfo: { @@ -55,7 +57,7 @@ export default defineComponent({ selectedType.value = event as 'sequence' | 'pulse'; selectedId.value = null; } else { - tab.value = 'recordings'; + tab.value = 'recording'; } }; @@ -220,14 +222,20 @@ export default defineComponent({ + + + diff --git a/client/src/components/RecordingAnnotation.vue b/client/src/components/RecordingAnnotation.vue deleted file mode 100644 index e69de29..0000000 diff --git a/client/src/components/RecordingAnnotationEditor.vue b/client/src/components/RecordingAnnotationEditor.vue new file mode 100644 index 0000000..9dfd27d --- /dev/null +++ b/client/src/components/RecordingAnnotationEditor.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/client/src/components/RecordingAnnotations.vue b/client/src/components/RecordingAnnotations.vue new file mode 100644 index 0000000..d229bce --- /dev/null +++ b/client/src/components/RecordingAnnotations.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/client/src/views/Spectrogram.vue b/client/src/views/Spectrogram.vue index b1fda64..cda5543 100644 --- a/client/src/views/Spectrogram.vue +++ b/client/src/views/Spectrogram.vue @@ -558,6 +558,8 @@ export default defineComponent({ :annotations="annotations" :temporal-annotations="temporalAnnotations" :selected-annotation="selectedAnnotation" + :species="speciesList" + :recording-id="id" @select="processSelection($event)" @update:annotation="getAnnotationsList()" @delete:annotation="