Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bats_ai/core/management/commands/generateNABat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@

from bats_ai.core.models import Species
from bats_ai.core.models.nabat import NABatRecording, NABatRecordingAnnotation
from bats_ai.core.utils.batbot_metadata import generate_spectrogram_assets
from bats_ai.utils.spectrogram_utils import (
generate_nabat_compressed_spectrogram,
generate_nabat_spectrogram,
generate_spectrogram_assets,
)

fake = Faker()
Expand Down
163 changes: 163 additions & 0 deletions bats_ai/core/migrations/0026_alter_annotations_end_time_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Generated by Django 4.2.23 on 2026-01-27 18:11

import django.contrib.postgres.fields
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
('core', '0025_configuration_mark_annotations_completed_enabled_and_more'),
]

operations = [
migrations.AlterField(
model_name='annotations',
name='end_time',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='annotations',
name='high_freq',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='annotations',
name='low_freq',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='annotations',
name='start_time',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='compressedspectrogram',
name='length',
field=models.FloatField(),
),
migrations.AlterField(
model_name='compressedspectrogram',
name='starts',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='compressedspectrogram',
name='stops',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='compressedspectrogram',
name='widths',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='nabatcompressedspectrogram',
name='length',
field=models.FloatField(),
),
migrations.AlterField(
model_name='nabatcompressedspectrogram',
name='starts',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='nabatcompressedspectrogram',
name='stops',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='nabatcompressedspectrogram',
name='widths',
field=django.contrib.postgres.fields.ArrayField(
base_field=django.contrib.postgres.fields.ArrayField(
base_field=models.FloatField(), size=None
),
size=None,
),
),
migrations.AlterField(
model_name='nabatspectrogram',
name='duration',
field=models.FloatField(),
),
migrations.AlterField(
model_name='nabatspectrogram',
name='frequency_max',
field=models.FloatField(),
),
migrations.AlterField(
model_name='nabatspectrogram',
name='frequency_min',
field=models.FloatField(),
),
migrations.AlterField(
model_name='nabatspectrogram',
name='height',
field=models.FloatField(),
),
migrations.AlterField(
model_name='nabatspectrogram',
name='width',
field=models.FloatField(),
),
migrations.AlterField(
model_name='sequenceannotations',
name='end_time',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='sequenceannotations',
name='start_time',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='spectrogram',
name='duration',
field=models.FloatField(),
),
migrations.AlterField(
model_name='spectrogram',
name='frequency_max',
field=models.FloatField(),
),
migrations.AlterField(
model_name='spectrogram',
name='frequency_min',
field=models.FloatField(),
),
migrations.AlterField(
model_name='spectrogram',
name='height',
field=models.FloatField(),
),
migrations.AlterField(
model_name='spectrogram',
name='width',
field=models.FloatField(),
),
]
8 changes: 4 additions & 4 deletions bats_ai/core/models/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
class Annotations(TimeStampedModel, models.Model):
recording = models.ForeignKey(Recording, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
start_time = models.IntegerField(blank=True, null=True)
end_time = models.IntegerField(blank=True, null=True)
low_freq = models.IntegerField(blank=True, null=True)
high_freq = models.IntegerField(blank=True, null=True)
start_time = models.FloatField(blank=True, null=True)
end_time = models.FloatField(blank=True, null=True)
low_freq = models.FloatField(blank=True, null=True)
high_freq = models.FloatField(blank=True, null=True)
type = models.TextField(blank=True, null=True)
species = models.ManyToManyField(Species)
comments = models.TextField(blank=True, null=True)
Expand Down
8 changes: 4 additions & 4 deletions bats_ai/core/models/compressed_spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
class CompressedSpectrogram(TimeStampedModel, models.Model):
recording = models.ForeignKey(Recording, on_delete=models.CASCADE)
spectrogram = models.ForeignKey(Spectrogram, on_delete=models.CASCADE)
length = models.IntegerField()
length = models.FloatField()
images = GenericRelation(SpectrogramImage)
starts = ArrayField(ArrayField(models.IntegerField()))
stops = ArrayField(ArrayField(models.IntegerField()))
widths = ArrayField(ArrayField(models.IntegerField()))
starts = ArrayField(ArrayField(models.FloatField()))
stops = ArrayField(ArrayField(models.FloatField()))
widths = ArrayField(ArrayField(models.FloatField()))
cache_invalidated = models.BooleanField(default=True)

@property
Expand Down
8 changes: 4 additions & 4 deletions bats_ai/core/models/nabat/nabat_compressed_spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class NABatCompressedSpectrogram(TimeStampedModel, models.Model):
nabat_recording = models.ForeignKey(NABatRecording, on_delete=models.CASCADE)
spectrogram = models.ForeignKey(NABatSpectrogram, on_delete=models.CASCADE)
images = GenericRelation(SpectrogramImage)
length = models.IntegerField()
starts = ArrayField(ArrayField(models.IntegerField()))
stops = ArrayField(ArrayField(models.IntegerField()))
widths = ArrayField(ArrayField(models.IntegerField()))
length = models.FloatField()
starts = ArrayField(ArrayField(models.FloatField()))
stops = ArrayField(ArrayField(models.FloatField()))
widths = ArrayField(ArrayField(models.FloatField()))
cache_invalidated = models.BooleanField(default=True)

@property
Expand Down
10 changes: 5 additions & 5 deletions bats_ai/core/models/nabat/nabat_spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
class NABatSpectrogram(TimeStampedModel, models.Model):
nabat_recording = models.ForeignKey(NABatRecording, on_delete=models.CASCADE)
images = GenericRelation(SpectrogramImage)
width = models.IntegerField() # pixels
height = models.IntegerField() # pixels
duration = models.IntegerField() # milliseconds
frequency_min = models.IntegerField() # hz
frequency_max = models.IntegerField() # hz
width = models.FloatField() # pixels
height = models.FloatField() # pixels
Comment on lines +21 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these two fields specifically, which I believe are just a pixel count, would it make sense to just keep them as integers?

duration = models.FloatField() # milliseconds
frequency_min = models.FloatField() # hz
frequency_max = models.FloatField() # hz

@property
def image_url_list(self):
Expand Down
4 changes: 2 additions & 2 deletions bats_ai/core/models/sequence_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
class SequenceAnnotations(models.Model):
recording = models.ForeignKey(Recording, on_delete=models.CASCADE)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
start_time = models.IntegerField(blank=True, null=True)
end_time = models.IntegerField(blank=True, null=True)
start_time = models.FloatField(blank=True, null=True)
end_time = models.FloatField(blank=True, null=True)
type = models.TextField(blank=True, null=True)
comments = models.TextField(blank=True, null=True)
species = models.ManyToManyField(Species)
10 changes: 5 additions & 5 deletions bats_ai/core/models/spectrogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
class Spectrogram(TimeStampedModel, models.Model):
recording = models.ForeignKey(Recording, on_delete=models.CASCADE)
images = GenericRelation(SpectrogramImage)
width = models.IntegerField() # pixels
height = models.IntegerField() # pixels
duration = models.IntegerField() # milliseconds
frequency_min = models.IntegerField() # hz
frequency_max = models.IntegerField() # hz
width = models.FloatField() # pixels
height = models.FloatField() # pixels
Comment on lines +15 to +16
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question here. Would it be more accurate to use integers for height/width?

duration = models.FloatField() # milliseconds
frequency_min = models.FloatField() # hz
frequency_max = models.FloatField() # hz

@property
def image_url_list(self):
Expand Down
5 changes: 3 additions & 2 deletions bats_ai/core/tasks/nabat/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

from bats_ai.core.models import Configuration, ProcessingTask, Species
from bats_ai.core.models.nabat import NABatRecording, NABatRecordingAnnotation
from bats_ai.core.utils.batbot_metadata import generate_spectrogram_assets
from bats_ai.utils.spectrogram_utils import (
generate_nabat_compressed_spectrogram,
generate_nabat_spectrogram,
generate_spectrogram_assets,
predict_from_compressed,
)

Expand Down Expand Up @@ -60,7 +60,8 @@ def generate_spectrograms(

try:
config = Configuration.objects.first()
if config and config.run_inference_on_upload:
# TODO: Disabled until prediction is in batbot
if config and config.run_inference_on_upload and False:
Comment on lines +63 to +64
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an issue in batbot or BatAI tracking this? It would be nice to have a link to an issue in either of the GH repos here while we wait for this feature.

It might also be nice to add some sort of banner in the UI somewhere that tells users that prediction is disabled for now.

self.update_state(
state='Progress',
meta={'description': 'Running Prediction on Spectrogram'},
Expand Down
17 changes: 14 additions & 3 deletions bats_ai/core/tasks/tasks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
import shutil
import tempfile

from django.contrib.contenttypes.models import ContentType
Expand All @@ -15,7 +16,8 @@
Spectrogram,
SpectrogramImage,
)
from bats_ai.utils.spectrogram_utils import generate_spectrogram_assets, predict_from_compressed
from bats_ai.core.utils.batbot_metadata import generate_spectrogram_assets
from bats_ai.utils.spectrogram_utils import predict_from_compressed

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('NABatDataRetrieval')
Expand All @@ -26,7 +28,15 @@ def recording_compute_spectrogram(recording_id: int):
recording = Recording.objects.get(pk=recording_id)

with tempfile.TemporaryDirectory() as tmpdir:
results = generate_spectrogram_assets(recording.audio_file, tmpdir)
# Copy the audio file from FileField to a temporary file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed because of how batbot handles spectrogram generation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah it no longer is using a fileHandle or something like that it. It needs a real file.

audio_filename = os.path.basename(recording.audio_file.name)
temp_audio_path = os.path.join(tmpdir, audio_filename)

with recording.audio_file.open('rb') as source_file:
with open(temp_audio_path, 'wb') as dest_file:
shutil.copyfileobj(source_file, dest_file)

results = generate_spectrogram_assets(temp_audio_path, output_folder=tmpdir)
# Create or get Spectrogram
spectrogram, _ = Spectrogram.objects.get_or_create(
recording=recording,
Expand Down Expand Up @@ -79,7 +89,8 @@ def recording_compute_spectrogram(recording_id: int):
)

config = Configuration.objects.first()
if config and config.run_inference_on_upload:
# TODO: Disabled until prediction is in batbot
if config and config.run_inference_on_upload and False:
Comment on lines +92 to +93
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same idea here about a linked issue/visibility for users on the front-end web side.

predict_results = predict_from_compressed(compressed_obj)
label = predict_results['label']
score = predict_results['score']
Expand Down
Loading