Skip to content

Commit ede5bd8

Browse files
committed
Merge branch 'main' into batbot-origsr
2 parents 210ef70 + 5275a71 commit ede5bd8

13 files changed

Lines changed: 788 additions & 431 deletions

File tree

bats_ai/core/fixtures/species.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,5 +1623,18 @@
16231623
},
16241624
"model": "core.Species",
16251625
"pk": "133"
1626+
},
1627+
{
1628+
"fields": {
1629+
"category": "multiple",
1630+
"common_name": "Florida bonneted bat, Brazilian free-tailed bat",
1631+
"family": "",
1632+
"genus": "",
1633+
"species": "Eumops floridanus, Tadarida brasiliensis",
1634+
"species_code": "EUFLTABR",
1635+
"species_code_6": ""
1636+
},
1637+
"model": "core.Species",
1638+
"pk": 134
16261639
}
16271640
]

bats_ai/core/management/commands/loadGRTS.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
14,
3838
"CONUS",
3939
# Backup URL
40-
"https://data.kitware.com/api/v1/item/697cc601e7dea9be44ec5aee/download",
40+
"https://data.kitware.com/api/v1/item/6a05c0aef88b2b3dd9854751/download",
4141
),
4242
# Optional regions (disabled by default). Include via --locations.
4343
"AKCAN": (
@@ -64,6 +64,12 @@
6464
"Puerto Rico",
6565
None,
6666
),
67+
"CONUS_OFFSHORE": (
68+
"https://data.kitware.com/api/v1/item/6a05c0dbf88b2b3dd9854754/download",
69+
24,
70+
"Offshore CONUS",
71+
None,
72+
),
6773
}
6874

6975
LOCATION_ID_TO_KEY = {str(config[1]): key for key, config in LOCATION_SOURCES.items()}
@@ -122,7 +128,11 @@ def handle(self, *args, **options): # noqa: C901, PLR0912, PLR0915
122128
shapefiles = [LOCATION_SOURCES[location_key] for location_key in selected_keys]
123129

124130
for url, sample_frame_id, name, backup_url in shapefiles:
125-
logger.info("Downloading shapefile for Location %s...", name)
131+
logger.info(
132+
"Downloading shapefile for Location %s with sample frame id %s",
133+
name,
134+
sample_frame_id,
135+
)
126136
with tempfile.TemporaryDirectory() as tmpdir:
127137
tmpdir = Path(tmpdir)
128138

@@ -167,9 +177,20 @@ def handle(self, *args, **options): # noqa: C901, PLR0912, PLR0915
167177
else:
168178
logger.warning("Shapefile CRS unknown; assuming EPSG:4326")
169179

180+
if "GRTS_ID" in gdf.columns:
181+
grts_id_col = "GRTS_ID"
182+
elif "grts_id" in gdf.columns:
183+
grts_id_col = "grts_id"
184+
logger.info("GRTS_ID column not found; using grts_id for cell ids.")
185+
else:
186+
raise CommandError(
187+
"Shapefile must contain a GRTS_ID or grts_id column; "
188+
f"columns present: {', '.join(map(str, gdf.columns))}"
189+
)
190+
170191
# Replace-on-reimport behavior: delete existing rows for any
171192
# (grts_cell_id, sample_frame_id) present in this import batch.
172-
incoming_grts_ids = set(gdf["GRTS_ID"].dropna().astype(int).tolist())
193+
incoming_grts_ids = set(gdf[grts_id_col].dropna().astype(int).tolist())
173194
if incoming_grts_ids:
174195
deleted_count, _ = GRTSCells.objects.filter(
175196
sample_frame_id=sample_frame_id,
@@ -191,11 +212,11 @@ def handle(self, *args, **options): # noqa: C901, PLR0912, PLR0915
191212
total=len(gdf),
192213
desc=f"Importing {sample_frame_id}",
193214
):
194-
# Hard fail if GRTS_ID is missing
195-
if "GRTS_ID" not in row or row["GRTS_ID"] is None:
196-
raise CommandError(f"Row {idx} missing required GRTS_ID field!")
215+
# Hard fail if GRTS id is missing
216+
if grts_id_col not in row or row[grts_id_col] is None:
217+
raise CommandError(f"Row {idx} missing required {grts_id_col} field!")
197218

198-
grts_id = int(row["GRTS_ID"])
219+
grts_id = int(row[grts_id_col])
199220
cell_key = (grts_id, sample_frame_id)
200221
if cell_key in seen_in_file:
201222
continue
@@ -220,13 +241,13 @@ def handle(self, *args, **options): # noqa: C901, PLR0912, PLR0915
220241

221242
if len(records_to_create) >= batch_size:
222243
with transaction.atomic():
223-
GRTSCells.objects.bulk_create(records_to_create, ignore_conflicts=True)
244+
GRTSCells.objects.bulk_create(records_to_create)
224245
records_to_create.clear()
225246

226247
# Insert remaining records
227248
if records_to_create:
228249
with transaction.atomic():
229-
GRTSCells.objects.bulk_create(records_to_create, ignore_conflicts=True)
250+
GRTSCells.objects.bulk_create(records_to_create)
230251

231252
logger.info(
232253
"Finished importing shapefile for sample frame %s: %s new records",

bats_ai/core/utils/batbot_metadata.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -534,9 +534,7 @@ def generate_spectrogram_assets(
534534
)
535535
mask_paths, mask_origsr_paths = _split_origsr_paths(metadata.spectrogram.mask_path)
536536
masked_paths, masked_origsr_paths = _split_origsr_paths(metadata.spectrogram.masked_path)
537-
waveplot_paths, waveplot_origsr_paths = _split_origsr_paths(
538-
metadata.spectrogram.waveplot_path
539-
)
537+
waveplot_paths, waveplot_origsr_paths = _split_origsr_paths(metadata.spectrogram.waveplot_path)
540538
compressed_waveplot_paths, compressed_waveplot_origsr_paths = _split_origsr_paths(
541539
metadata.spectrogram.waveplot_compressed_path
542540
)

bats_ai/core/utils/grts_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
from __future__ import annotations
22

3+
from django.db.models import Case, F, IntegerField, Value, When
4+
5+
6+
def recording_effective_sample_frame_id_case() -> Case:
7+
"""Return a ``Case`` matching ``normalize_sample_frame_id`` for ``Recording`` rows."""
8+
return Case(
9+
When(sample_frame_id__isnull=True, then=Value(14)),
10+
When(sample_frame_id=19, then=Value(20)),
11+
default=F("sample_frame_id"),
12+
output_field=IntegerField(),
13+
)
14+
315

416
def normalize_sample_frame_id(sample_frame_id: int | None) -> int | None:
517
"""Normalize sample frame IDs for AKCAN compatibility.
@@ -12,4 +24,6 @@ def normalize_sample_frame_id(sample_frame_id: int | None) -> int | None:
1224
"""
1325
if sample_frame_id == 19:
1426
return 20
27+
if sample_frame_id is None:
28+
return 14
1529
return sample_frame_id

bats_ai/core/views/recording.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
from bats_ai.core.models import (
2020
Annotations,
2121
CompressedSpectrogram,
22-
GRTSCells,
2322
PulseMetadata,
2423
Recording,
2524
RecordingAnnotation,
@@ -29,7 +28,7 @@
2928
Spectrogram,
3029
)
3130
from bats_ai.core.tasks.tasks import recording_compute_spectrogram
32-
from bats_ai.core.views.recording_location import _parse_bbox
31+
from bats_ai.core.views.recording_location import _parse_bbox, filter_recordings_by_map_bbox
3332
from bats_ai.core.views.species import SpeciesSchema
3433

3534
if TYPE_CHECKING:
@@ -525,13 +524,7 @@ def get_recordings( # noqa: C901
525524
if q.bbox and q.bbox.strip():
526525
min_lon, min_lat, max_lon, max_lat = _parse_bbox(q.bbox)
527526
bbox_poly = Polygon.from_bbox((min_lon, min_lat, max_lon, max_lat))
528-
# Need to check the GRTSCells centroids as well as the recording_location
529-
grts_cell_ids = GRTSCells.objects.filter(centroid_4326__intersects=bbox_poly).values_list(
530-
"grts_cell_id", flat=True
531-
)
532-
queryset = queryset.filter(
533-
Q(recording_location__intersects=bbox_poly) | Q(grts_cell_id__in=grts_cell_ids)
534-
)
527+
queryset = filter_recordings_by_map_bbox(queryset, bbox_poly)
535528

536529
sort_field = q.sort_by or "created"
537530
order_prefix = "" if q.sort_direction == "asc" else "-"
@@ -610,13 +603,7 @@ def apply_filters_and_sort(qs: QuerySet[Recording]) -> QuerySet[Recording]:
610603
if bbox and bbox.strip():
611604
min_lon, min_lat, max_lon, max_lat = _parse_bbox(bbox)
612605
bbox_poly = Polygon.from_bbox((min_lon, min_lat, max_lon, max_lat))
613-
# Need to check the GRTSCells centroids as well as the recording_location
614-
grts_cell_ids = GRTSCells.objects.filter(
615-
centroid_4326__intersects=bbox_poly
616-
).values_list("grts_cell_id", flat=True)
617-
qs = qs.filter(
618-
Q(recording_location__intersects=bbox_poly) | Q(grts_cell_id__in=grts_cell_ids)
619-
)
606+
qs = filter_recordings_by_map_bbox(qs, bbox_poly)
620607
order_prefix = "" if sort_direction == "asc" else "-"
621608
if sort_by == "owner_username":
622609
qs = qs.order_by(f"{order_prefix}owner__username")

0 commit comments

Comments
 (0)