diff --git a/.gitignore b/.gitignore index 4d6d643b..841a6296 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ temp*.png staticfiles/ .env dev/.env.prod.docker-compose +**/*.egg-info/** diff --git a/bats_ai/core/migrations/0014_configuration_run_inference_on_upload_and_more.py b/bats_ai/core/migrations/0014_configuration_run_inference_on_upload_and_more.py new file mode 100644 index 00000000..ab83b9a7 --- /dev/null +++ b/bats_ai/core/migrations/0014_configuration_run_inference_on_upload_and_more.py @@ -0,0 +1,31 @@ +# Generated by Django 4.1.13 on 2025-03-26 21:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ('core', '0013_configuration'), + ] + + operations = [ + migrations.AddField( + model_name='configuration', + name='run_inference_on_upload', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='configuration', + name='spectrogram_view', + field=models.CharField( + choices=[('compressed', 'Compressed'), ('uncompressed', 'Uncompressed')], + default='compressed', + max_length=12, + ), + ), + migrations.AddField( + model_name='configuration', + name='spectrogram_x_stretch', + field=models.DecimalField(decimal_places=2, default=2.5, max_digits=3), + ), + ] diff --git a/bats_ai/core/models/configuration.py b/bats_ai/core/models/configuration.py index fad37fab..4b565188 100644 --- a/bats_ai/core/models/configuration.py +++ b/bats_ai/core/models/configuration.py @@ -5,8 +5,17 @@ # Define the Configuration model class Configuration(models.Model): + class SpectrogramViewMode(models.TextChoices): + COMPRESSED = 'compressed' + UNCOMPRESSED = 'uncompressed' + display_pulse_annotations = models.BooleanField(default=True) display_sequence_annotations = models.BooleanField(default=True) + run_inference_on_upload = models.BooleanField(default=True) + spectrogram_x_stretch = models.DecimalField(default=2.5, max_digits=3, decimal_places=2) + spectrogram_view = models.CharField( + max_length=12, choices=SpectrogramViewMode.choices, default=SpectrogramViewMode.COMPRESSED + ) def save(self, *args, **kwargs): # Ensure only one instance of Configuration exists diff --git a/bats_ai/core/tasks.py b/bats_ai/core/tasks.py index 65fd580a..03216ddc 100644 --- a/bats_ai/core/tasks.py +++ b/bats_ai/core/tasks.py @@ -11,6 +11,7 @@ from bats_ai.core.models import ( Annotations, CompressedSpectrogram, + Configuration, Recording, RecordingAnnotation, Species, @@ -187,7 +188,9 @@ def recording_compute_spectrogram(recording_id: int): spectrogram_id = spectrogram_id_temp if spectrogram_id is not None: compressed_spectro = generate_compress_spectrogram(recording_id, spectrogram_id) - predict(compressed_spectro.pk) + config = Configuration.objects.first() + if not config or not config.run_inference_on_upload: + predict(compressed_spectro.pk) @shared_task diff --git a/bats_ai/core/views/configuration.py b/bats_ai/core/views/configuration.py index dd85597c..9ae2121b 100644 --- a/bats_ai/core/views/configuration.py +++ b/bats_ai/core/views/configuration.py @@ -17,6 +17,9 @@ class ConfigurationSchema(Schema): display_pulse_annotations: bool display_sequence_annotations: bool is_admin: bool | None = None + run_inference_on_upload: bool + spectrogram_x_stretch: float + spectrogram_view: Configuration.SpectrogramViewMode # Endpoint to retrieve the configuration status @@ -28,6 +31,9 @@ def get_configuration(request): return ConfigurationSchema( display_pulse_annotations=config.display_pulse_annotations, display_sequence_annotations=config.display_sequence_annotations, + run_inference_on_upload=config.run_inference_on_upload, + spectrogram_x_stretch=config.spectrogram_x_stretch, + spectrogram_view=config.spectrogram_view, is_admin=request.user.is_authenticated and request.user.is_superuser, ) diff --git a/client/src/api/api.ts b/client/src/api/api.ts index 74988af5..0539b1b0 100644 --- a/client/src/api/api.ts +++ b/client/src/api/api.ts @@ -2,111 +2,111 @@ import axios from 'axios'; import { SpectroInfo } from '../components/geoJS/geoJSUtils'; export interface PaginatedResponse { - count: number, - next: string, - previous:string, - results: E[]; + count: number, + next: string, + previous: string, + results: E[]; } export interface Recording { - id: number, - created: string, - modified: string, - name: string, - audio_file: string, - audio_file_presigned_url: string, - owner_id: number; - owner_username: string; - recorded_date: string; - recorded_time: string; - equipment?: string, - comments?: string; - recording_location?: null | GeoJSON.Point, - grts_cell_id?: null | number; - grts_cell?: null | number; - public: boolean; - userMadeAnnotations: boolean; - userAnnotations: number; - hasSpectrogram: boolean; - fileAnnotations: FileAnnotation[]; - site_name?: string; - software?: string; - detector?: string; - species_list?: string; - unusual_occurrences?: string; + id: number, + created: string, + modified: string, + name: string, + audio_file: string, + audio_file_presigned_url: string, + owner_id: number; + owner_username: string; + recorded_date: string; + recorded_time: string; + equipment?: string, + comments?: string; + recording_location?: null | GeoJSON.Point, + grts_cell_id?: null | number; + grts_cell?: null | number; + public: boolean; + userMadeAnnotations: boolean; + userAnnotations: number; + hasSpectrogram: boolean; + fileAnnotations: FileAnnotation[]; + site_name?: string; + software?: string; + detector?: string; + species_list?: string; + unusual_occurrences?: string; } export interface AcousticFiles { - id: number, - recording_time: string; - recording_location: string | null; - file_name: string | null; - s3_verified: boolean | null; - length_ms: number | null; - size_bytes: number | null; - survey_event: null; + id: number, + recording_time: string; + recording_location: string | null; + file_name: string | null; + s3_verified: boolean | null; + length_ms: number | null; + size_bytes: number | null; + survey_event: null; } export interface Species { - species_code: string; - family: string; - genus: string; - common_name: string; - species_code_6?: string; - id: number; + species_code: string; + family: string; + genus: string; + common_name: string; + species_code_6?: string; + id: number; } export interface SpectrogramAnnotation { - start_time: number; - end_time: number; - low_freq: number; - high_freq: number; - id: number; - editing?: boolean; - species?: Species[]; - comments?: string; - type?: string; - owner_email?: string; + start_time: number; + end_time: number; + low_freq: number; + high_freq: number; + id: number; + editing?: boolean; + species?: Species[]; + comments?: string; + type?: string; + owner_email?: string; } export interface SpectrogramTemporalAnnotation { - start_time: number; - end_time: number; - id: number; - editing?: boolean; - type?: string; - comments?: string; - species?: Species[]; - owner_email?: string; + start_time: number; + end_time: number; + id: number; + editing?: boolean; + type?: string; + comments?: string; + species?: Species[]; + owner_email?: string; } export interface UpdateSpectrogramAnnotation { - start_time?: number; - end_time?: number; - low_freq?: number; - high_freq?: number; - id?: number; - editing?: boolean; - type?: string; - species?: Species[]; - comments?: string; + start_time?: number; + end_time?: number; + low_freq?: number; + high_freq?: number; + id?: number; + editing?: boolean; + type?: string; + species?: Species[]; + comments?: string; } export interface UpdateSpectrogramTemporalAnnotation { - start_time?: number; - end_time?: number; - id?: number; - editing?: boolean; - species?: Species[]; - comments?: string; - type?: string; + start_time?: number; + end_time?: number; + id?: number; + editing?: boolean; + species?: Species[]; + comments?: string; + type?: string; } export interface UserInfo { - username: string; - email: string; - id: number; + username: string; + email: string; + id: number; } export interface FileAnnotation { @@ -125,282 +125,284 @@ export interface FileAnnotationDetails { confidences: { label: string, value: string}[]; } export interface UpdateFileAnnotation { - recordingId?: number; - species: number[] | null; - comments?: string; - model?: string; - confidence: number; - id?: number; + recordingId?: number; + species: 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?: { - start_times: number[]; - end_times: number[]; - } - currentUser?: string; - otherUsers?: UserInfo[]; + url: string; + filename?: string; + annotations?: SpectrogramAnnotation[]; + fileAnnotations: FileAnnotation[]; + temporal?: SpectrogramTemporalAnnotation[]; + spectroInfo?: SpectroInfo; + compressed?: { + start_times: number[]; + end_times: number[]; + } + currentUser?: string; + otherUsers?: UserInfo[]; } -export type OtherUserAnnotations = Record; +export type OtherUserAnnotations = Record; -export type UploadLocation = null | { latitude?: number, longitude?: number, gridCellId?: number}; +export type UploadLocation = null | { latitude?: number, longitude?: number, gridCellId?: number }; export const axiosInstance = axios.create({ baseURL: import.meta.env.VUE_APP_API_ROOT as string, }); export interface RecordingFileParameters { - name: string; - recorded_date: string; - recorded_time: string; - equipment: string; - comments: string; - location?: UploadLocation; - publicVal: boolean; - site_name?: string; - software?: string; - detector?: string; - species_list?: string; - unusual_occurrences?: string; - -} - -async function uploadRecordingFile(file: File, params: RecordingFileParameters ) { - const formData = new FormData(); - formData.append('audio_file', file); - formData.append('name', params.name); - formData.append('recorded_date', params.recorded_date); - formData.append('recorded_time', params.recorded_time); - formData.append('equipment', params.equipment); - formData.append('comments', params.comments); - if (params.location) { - if (params.location.latitude && params.location.longitude) { - formData.append('latitude', params.location.latitude.toString()); - formData.append('longitude', params.location.longitude.toString()); - } - if (params.location.gridCellId) { - formData.append('gridCellId', params.location.gridCellId.toString()); - } - } - - if (params.software) { - formData.append('software', params.software); + name: string; + recorded_date: string; + recorded_time: string; + equipment: string; + comments: string; + location?: UploadLocation; + publicVal: boolean; + site_name?: string; + software?: string; + detector?: string; + species_list?: string; + unusual_occurrences?: string; + +} + +async function uploadRecordingFile(file: File, params: RecordingFileParameters) { + const formData = new FormData(); + formData.append('audio_file', file); + formData.append('name', params.name); + formData.append('recorded_date', params.recorded_date); + formData.append('recorded_time', params.recorded_time); + formData.append('equipment', params.equipment); + formData.append('comments', params.comments); + if (params.location) { + if (params.location.latitude && params.location.longitude) { + formData.append('latitude', params.location.latitude.toString()); + formData.append('longitude', params.location.longitude.toString()); } - if (params.detector) { - formData.append('detector', params.detector); + if (params.location.gridCellId) { + formData.append('gridCellId', params.location.gridCellId.toString()); } - if (params.site_name) { - formData.append('site_name', params.site_name); - } - if (params.species_list) { - formData.append('species_list', params.species_list); - } - if (params.unusual_occurrences) { - formData.append('unusual_occurrences', params.unusual_occurrences); - } - const recordingParams = { + } + + if (params.software) { + formData.append('software', params.software); + } + if (params.detector) { + formData.append('detector', params.detector); + } + if (params.site_name) { + formData.append('site_name', params.site_name); + } + if (params.species_list) { + formData.append('species_list', params.species_list); + } + if (params.unusual_occurrences) { + formData.append('unusual_occurrences', params.unusual_occurrences); + } + const recordingParams = { + name: params.name, + equipment: params.equipment, + comments: params.comments, + site_name: params.site_name, + software: params.software, + detector: params.detector, + species_list: params.species_list, + unusual_occurrences: params.unusual_occurrences + }; + const payloadBlob = new Blob([JSON.stringify(recordingParams)], { type: 'application/json' }); + formData.append('payload', payloadBlob); + await axiosInstance.post('/recording/', + formData, + { + params: { publicVal: !!params.publicVal }, + headers: { + 'Content-Type': 'multipart/form-data', + } + }); +} + +async function patchRecording(recordingId: number, params: RecordingFileParameters) { + const latitude = params.location ? params.location.latitude : undefined; + const longitude = params.location ? params.location.longitude : undefined; + const gridCellId = params.location ? params.location.gridCellId : undefined; + + await axiosInstance.patch(`/recording/${recordingId}`, + { name: params.name, + recorded_date: params.recorded_date, + recorded_time: params.recorded_time, equipment: params.equipment, comments: params.comments, + publicVal: !!params.publicVal, + latitude, + longitude, + gridCellId, site_name: params.site_name, software: params.software, detector: params.detector, species_list: params.species_list, - unusual_occurrences: params.unusual_occurrences - }; - const payloadBlob = new Blob([JSON.stringify(recordingParams)], { type: 'application/json' }); - formData.append('payload', payloadBlob); - await axiosInstance.post('/recording/', - formData, - { - params: { publicVal: !!params.publicVal }, - headers: { - 'Content-Type': 'multipart/form-data', - } - }); - } - - async function patchRecording(recordingId: number, params: RecordingFileParameters) { - const latitude = params.location ? params.location.latitude : undefined; - const longitude = params.location ? params.location.longitude : undefined; - const gridCellId = params.location ? params.location.gridCellId : undefined; - - await axiosInstance.patch(`/recording/${recordingId}`, - { - name: params.name, - recorded_date: params.recorded_date, - recorded_time: params.recorded_time, - equipment: params.equipment, - comments: params.comments, - publicVal: !!params.publicVal, - latitude, - longitude, - gridCellId, - site_name: params.site_name, - software: params.software, - detector: params.detector, - species_list: params.species_list, - unusual_occurrences: params.unusual_occurrences, - }, - { - headers: { - 'Content-Type': 'application/json', - } - } - ); + unusual_occurrences: params.unusual_occurrences, + }, + { + headers: { + 'Content-Type': 'application/json', + } + } + ); } interface DeletionResponse { - message?: string; - error?: string; + message?: string; + error?: string; } interface GRTSCellCenter { - latitude?: number; - longitude?: number; - error?: string; + latitude?: number; + longitude?: number; + error?: string; } - -async function getRecordings(getPublic=false) { - return axiosInstance.get(`/recording/?public=${getPublic}`); + +async function getRecordings(getPublic = false) { + return axiosInstance.get(`/recording/?public=${getPublic}`); } async function getRecording(id: string) { - return axiosInstance.get(`/recording/${id}/`); + return axiosInstance.get(`/recording/${id}/`); } async function deleteRecording(id: number) { - return axiosInstance.delete(`/recording/${id}`); + return axiosInstance.delete(`/recording/${id}`); } async function getSpectrogram(id: string) { - return axiosInstance.get(`/recording/${id}/spectrogram`); + return axiosInstance.get(`/recording/${id}/spectrogram`); } async function getSpectrogramCompressed(id: string) { - return axiosInstance.get(`/recording/${id}/spectrogram/compressed`); + return axiosInstance.get(`/recording/${id}/spectrogram/compressed`); } async function getAnnotations(recordingId: string) { - return axiosInstance.get(`/recording/${recordingId}/annotations`); + return axiosInstance.get(`/recording/${recordingId}/annotations`); } async function getTemporalAnnotations(recordingId: string) { - return axiosInstance.get(`/recording/${recordingId}/temporal-annotations`); + return axiosInstance.get(`/recording/${recordingId}/temporal-annotations`); } async function getSpecies() { - return axiosInstance.get('/species/'); + return axiosInstance.get('/species/'); } async function patchAnnotation(recordingId: string, annotationId: number, annotation: UpdateSpectrogramAnnotation, speciesList: number[] | null = null) { - return axiosInstance.patch(`/recording/${recordingId}/annotations/${annotationId}`, { annotation, species_ids: speciesList}); + 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}); + 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}); + 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}); + 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}`); + return axiosInstance.delete(`/recording/${recordingId}/annotations/${annotationId}`); } async function deleteTemporalAnnotation(recordingId: string, annotationId: number) { - return axiosInstance.delete(`/recording/${recordingId}/temporal-annotations/${annotationId}`); + return axiosInstance.delete(`/recording/${recordingId}/temporal-annotations/${annotationId}`); } async function getOtherUserAnnotations(recordingId: string) { - return axiosInstance.get(`/recording/${recordingId}/annotations/other_users`); + return axiosInstance.get(`/recording/${recordingId}/annotations/other_users`); } async function getCellLocation(cellId: number, quadrant?: 'SW' | 'NE' | 'NW' | 'SE') { - return axiosInstance.get(`/grts/${cellId}`, { params: { quadrant }}); + return axiosInstance.get(`/grts/${cellId}`, { params: { quadrant } }); } async function getFileAnnotations(recordingId: number) { - return axiosInstance.get(`recording/${recordingId}/recording-annotations`); + return axiosInstance.get(`recording/${recordingId}/recording-annotations`); } async function getFileAnnotationDetails(recordingId: number) { return axiosInstance.get<(FileAnnotation & {details: FileAnnotationDetails })>(`recording-annotation/${recordingId}/details`); - } async function putFileAnnotation(fileAnnotation: UpdateFileAnnotation) { - return axiosInstance.put<{message: string, id: number}>(`/recording-annotation/`, { ...fileAnnotation }); + 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 }); + 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}`); + return axiosInstance.delete<{ message: string, id: number }>(`/recording-annotation/${fileAnnotationId}`); } interface CellIDReponse { - grid_cell_id?: number; - error?: string, + grid_cell_id?: number; + error?: string, } async function getCellfromLocation(latitude: number, longitude: number) { - return axiosInstance.get(`/grts/grid_cell_id`, {params: {latitude, longitude}}); + return axiosInstance.get(`/grts/grid_cell_id`, { params: { latitude, longitude } }); } export interface ConfigurationSettings { -display_pulse_annotations: boolean; -display_sequence_annotations: boolean; -is_admin: boolean; + display_pulse_annotations: boolean; + display_sequence_annotations: boolean; + run_inference_on_upload: boolean; + spectrogram_x_stretch: number; + spectrogram_view: 'compressed' | 'uncompressed'; + is_admin: boolean; } -export type Configuration = ConfigurationSettings & { is_admin : boolean }; +export type Configuration = ConfigurationSettings & { is_admin: boolean }; async function getConfiguration() { - return axiosInstance.get('/configuration/'); + return axiosInstance.get('/configuration/'); } async function patchConfiguration(config: ConfigurationSettings) { - return axiosInstance.patch('/configuration/', {...config }); + return axiosInstance.patch('/configuration/', { ...config }); } interface GuanoMetadata { - nabat_grid_cell_grts_id?: string - nabat_latitude?: number - nabat_longitude?: number - nabat_site_name?: string - nabat_activation_start_time?: string - nabat_activation_end_time?: string - nabat_software_type?: string - nabat_species_list?: string[] - nabat_comments?: string - nabat_detector_type?: string - nabat_unusual_occurrences?: string + nabat_grid_cell_grts_id?: string + nabat_latitude?: number + nabat_longitude?: number + nabat_site_name?: string + nabat_activation_start_time?: string + nabat_activation_end_time?: string + nabat_software_type?: string + nabat_species_list?: string[] + nabat_comments?: string + nabat_detector_type?: string + nabat_unusual_occurrences?: string } async function getGuanoMetadata(file: File): Promise { - const formData = new FormData(); - formData.append('audio_file', file); - const results = await axiosInstance.post('/guano/',formData, { - headers: { - 'Content-Type': 'multipart/form-data', - } - }); - return results.data; + const formData = new FormData(); + formData.append('audio_file', file); + const results = await axiosInstance.post('/guano/', formData, { + headers: { + 'Content-Type': 'multipart/form-data', + } + }); + return results.data; } @@ -432,4 +434,4 @@ export { getConfiguration, patchConfiguration, getFileAnnotationDetails, -}; \ No newline at end of file +}; diff --git a/client/src/components/SpectrogramViewer.vue b/client/src/components/SpectrogramViewer.vue index 44b412de..c40bd2c7 100644 --- a/client/src/components/SpectrogramViewer.vue +++ b/client/src/components/SpectrogramViewer.vue @@ -47,6 +47,7 @@ export default defineComponent({ creationType, blackBackground, scaledVals, + configuration, } = useState(); const containerRef: Ref = ref(); const geoJS = useGeoJS(); @@ -99,9 +100,9 @@ export default defineComponent({ const freq = adjustedHeight - y >= 0 ? ((adjustedHeight - y) * (props.spectroInfo.high_freq - props.spectroInfo.low_freq)) / - adjustedHeight / - 1000 + - props.spectroInfo.low_freq / 1000 + adjustedHeight / + 1000 + + props.spectroInfo.low_freq / 1000 : -1; if (!props.compressed) { @@ -181,9 +182,9 @@ export default defineComponent({ initialized.value = true; emit("geoViewerRef", geoJS.getGeoViewer()); if (props.compressed) { - scaledVals.value = { x: 2.5, y: 1 }; + scaledVals.value = { x: configuration.value.spectrogram_x_stretch, y: 1 }; let baseWidth = 0; - let baseHeight = 0; + let baseHeight = 0; if (props.image) { const { naturalWidth, naturalHeight } = props.image; baseWidth = naturalWidth; diff --git a/client/src/use/useState.ts b/client/src/use/useState.ts index 0b48909a..0af22a85 100644 --- a/client/src/use/useState.ts +++ b/client/src/use/useState.ts @@ -12,17 +12,24 @@ const selectedUsers: Ref = ref([]); const currentUser: Ref = ref(''); const selectedId: Ref = ref(null); const selectedType: Ref<'pulse' | 'sequence'> = ref('pulse'); -const annotations : Ref = ref([]); +const annotations: Ref = ref([]); const temporalAnnotations: Ref = ref([]); const otherUserAnnotations: Ref = ref({}); const sharedList: Ref = ref([]); const recordingList: Ref = ref([]); const nextShared: Ref = ref(false); const blackBackground = ref(true); -const scaledVals: Ref<{x: number, y: number}> = ref({x: 1, y: 1}); +const scaledVals: Ref<{ x: number, y: number }> = ref({ x: 1, y: 1 }); const viewCompressedOverlay = ref(false); const sideTab: Ref<'annotations' | 'recordings'> = ref('annotations'); -const configuration: Ref = ref({display_pulse_annotations: true, display_sequence_annotations: true, is_admin: false}); +const configuration: Ref = ref({ + display_pulse_annotations: true, + display_sequence_annotations: true, + spectrogram_view: 'compressed', + spectrogram_x_stretch: 2.5, + run_inference_on_upload: true, + is_admin: false, +}); type AnnotationState = "" | "editing" | "creating" | "disabled"; export default function useState() { @@ -55,12 +62,12 @@ export default function useState() { function createColorScale(userEmails: string[]) { colorScale.value = d3.scaleOrdinal() - .domain(userEmails) - .range(d3.schemeCategory10.filter(color => color !== 'red' && color !== 'cyan' && color !== 'yellow')); + .domain(userEmails) + .range(d3.schemeCategory10.filter(color => color !== 'red' && color !== 'cyan' && color !== 'yellow')); } - function setSelectedId(id: number | null, annotationType?: 'pulse' | 'sequence' ) { + function setSelectedId(id: number | null, annotationType?: 'pulse' | 'sequence') { selectedId.value = id; if (annotationType) { selectedType.value = annotationType; @@ -69,7 +76,7 @@ export default function useState() { watch(sharedList, () => { const filtered = sharedList.value.filter((item) => !item.userMadeAnnotations); if (filtered.length > 0) { - nextShared.value = filtered[0]; + nextShared.value = filtered[0]; } else { nextShared.value = false; } @@ -78,8 +85,8 @@ export default function useState() { async function loadConfiguration() { configuration.value = (await getConfiguration()).data; } - - return { + + return { annotationState, creationType, setAnnotationState, diff --git a/client/src/views/Admin.vue b/client/src/views/Admin.vue index 6eda7fc4..13ffc948 100644 --- a/client/src/views/Admin.vue +++ b/client/src/views/Admin.vue @@ -1,40 +1,53 @@ - - - + loadConfiguration(); + }; + + return { + settings, + saveSettings, + spectrogramViewOptions, + }; + }, +}); + + - - \ No newline at end of file + diff --git a/client/src/views/Spectrogram.vue b/client/src/views/Spectrogram.vue index f4012e1c..47aa6f3e 100644 --- a/client/src/views/Spectrogram.vue +++ b/client/src/views/Spectrogram.vue @@ -56,15 +56,16 @@ export default defineComponent({ scaledVals, viewCompressedOverlay, sideTab, + configuration, } = useState(); const image: Ref = ref(new Image()); const spectroInfo: Ref = ref(); const selectedUsers: Ref = ref([]); const speciesList: Ref = ref([]); const loadedImage = ref(false); - const compressed = ref(false); const gridEnabled = ref(false); const recordingInfo = ref(false); + const compressed = computed(() => configuration.value.spectrogram_view === 'compressed'); const getAnnotationsList = async (annotationId?: number) => { const response = await getAnnotations(props.id); annotations.value = response.data.sort( @@ -106,12 +107,12 @@ export default defineComponent({ : await getSpectrogram(props.id); if (response.data["url"]) { if (import.meta.env.PROD) { - const updateHost = `${window.location.protocol}//${window.location.hostname}/`; - const updatedURL = response.data["url"].replace( - "http://127.0.0.1:9000/", - updateHost - ); - image.value.src = updatedURL.split("?")[0]; + const updateHost = `${window.location.protocol}//${window.location.hostname}/`; + const updatedURL = response.data["url"].replace( + "http://127.0.0.1:9000/", + updateHost + ); + image.value.src = updatedURL.split("?")[0]; } else { image.value.src = response.data['url']; } @@ -241,7 +242,7 @@ export default defineComponent({ }; const toggleCompressedOverlay = () => { - viewCompressedOverlay.value = ! viewCompressedOverlay.value; + viewCompressedOverlay.value = !viewCompressedOverlay.value; }; return { @@ -340,9 +341,7 @@ export default defineComponent({ class="px-0" style="font-size: 20px" > -
+
Mode: {{ annotationState }}
@@ -512,9 +511,7 @@ export default defineComponent({ - +