diff --git a/README.md b/README.md index 7e72311..9b5871c 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ maintenance. To non-destructively update your development stack at any time: 2. Run `docker compose build --pull --no-cache` 3. Run `docker compose run --rm django ./manage.py migrate` 3. Run `docker compose run --rm django ./manage.py createsuperuser` -4. Run `docker compose run --rm django ./manage.py makeclient --username your.super.user@email.address --uri http://localhost:3000/` +4. Run `docker compose run --rm django ./manage.py loaddata species` to load species data into the database +5. Run `docker compose run --rm django ./manage.py makeclient --username your.super.user@email.address --uri http://localhost:3000/` ## Develop Natively (advanced) This configuration still uses Docker to run attached services in the background, but allows developers to run Python code on their native system. diff --git a/bats_ai/api.py b/bats_ai/api.py index c45e7c6..9d77141 100644 --- a/bats_ai/api.py +++ b/bats_ai/api.py @@ -4,7 +4,7 @@ from ninja.security import HttpBearer from oauth2_provider.models import AccessToken -from bats_ai.core.views import RecordingRouter +from bats_ai.core.views import RecordingRouter, SpeciesRouter logger = logging.getLogger(__name__) @@ -22,3 +22,4 @@ def authenticate(self, request, token): api = NinjaAPI() api.add_router('/recording/', RecordingRouter) +api.add_router('/species/', SpeciesRouter) diff --git a/bats_ai/core/admin/__init__.py b/bats_ai/core/admin/__init__.py index e471619..d949e53 100644 --- a/bats_ai/core/admin/__init__.py +++ b/bats_ai/core/admin/__init__.py @@ -1,7 +1,9 @@ +from .annotations import AnnotationsAdmin from .image import ImageAdmin from .recording import RecordingAdmin __all__ = [ 'ImageAdmin', 'RecordingAdmin', + 'AnnotationsAdmin', ] diff --git a/bats_ai/core/admin/annotations.py b/bats_ai/core/admin/annotations.py new file mode 100644 index 0000000..34f1bfd --- /dev/null +++ b/bats_ai/core/admin/annotations.py @@ -0,0 +1,21 @@ +from django.contrib import admin + +from bats_ai.core.models import Annotations + + +@admin.register(Annotations) +class AnnotationsAdmin(admin.ModelAdmin): + list_display = [ + 'pk', + 'recording', + 'owner', + 'start_time', + 'end_time', + 'low_freq', + 'high_freq', + 'comments', + ] + list_select_related = True + # list_select_related = ['owner'] + filter_horizontal = ('species',) # or filter_vertical + autocomplete_fields = ['owner'] diff --git a/bats_ai/core/fixtures/species.json b/bats_ai/core/fixtures/species.json new file mode 100644 index 0000000..53f0208 --- /dev/null +++ b/bats_ai/core/fixtures/species.json @@ -0,0 +1,1010 @@ +[ + { + "model": "core.Species", + "pk": "67", + "fields": { + "species_code": "25k", + "family": "", + "genus": "", + "species": "", + "common_name": "Various species with pulses that have a minimum frequency of approximately 25 kHz.", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "68", + "fields": { + "species_code": "40k", + "family": "", + "genus": "", + "species": "", + "common_name": "Various species with pulses that have a minimum frequency in the range of 35-45 kHz.", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "65", + "fields": { + "species_code": "NOISE", + "family": "", + "genus": "", + "species": "Not a bat", + "common_name": "Not a bat/Noise", + "species_code_6": "NOTBAT" + } + }, + { + "model": "core.Species", + "pk": "1", + "fields": { + "species_code": "ANPA", + "family": "Vespertilionidae", + "genus": "Antrozous", + "species": "Antrozous pallidus", + "common_name": "Pallid bat", + "species_code_6": "ANTPAL" + } + }, + { + "model": "core.Species", + "pk": "55", + "fields": { + "species_code": "ANPAEPFU", + "family": "", + "genus": "", + "species": "Antrozous pallidus/Eptesicus fuscus", + "common_name": "Pallid bat/Big brown bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "45", + "fields": { + "species_code": "ARJA", + "family": "Phyllostomidae", + "genus": "Artibeus", + "species": "Artibeus jamaicensis", + "common_name": "Jamaican fruit-eating bat", + "species_code_6": "ARTJAM" + } + }, + { + "model": "core.Species", + "pk": "46", + "fields": { + "species_code": "BRCA", + "family": "Phyllostomidae", + "genus": "Brachyphylla", + "species": "Brachyphylla cavernarum", + "common_name": "Antillean fruit-eating bat", + "species_code_6": "BRACAV" + } + }, + { + "model": "core.Species", + "pk": "2", + "fields": { + "species_code": "CHME", + "family": "Phyllostomidae", + "genus": "Choeronycteris", + "species": "Choeronycteris mexicana", + "common_name": "Mexican long-tongued bat", + "species_code_6": "CHOMEX" + } + }, + { + "model": "core.Species", + "pk": "3", + "fields": { + "species_code": "CORA", + "family": "Vespertilionidae", + "genus": "Corynorhinus", + "species": "Corynorhinus rafinesquii", + "common_name": "Rafinesque's big-eared bat", + "species_code_6": "CORRAF" + } + }, + { + "model": "core.Species", + "pk": "47", + "fields": { + "species_code": "DIEC", + "family": "Phyllostomidae", + "genus": "Diphylla", + "species": "Diphylla ecaudata", + "common_name": "Hairy-legged vampire bat", + "species_code_6": "DIPECA" + } + }, + { + "model": "core.Species", + "pk": "5", + "fields": { + "species_code": "EPFU", + "family": "Vespertilionidae", + "genus": "Eptesicus", + "species": "Eptesicus fuscus", + "common_name": "Big brown bat", + "species_code_6": "EPTFUS" + } + }, + { + "model": "core.Species", + "pk": "82", + "fields": { + "species_code": "EPFULABO", + "family": "", + "genus": "", + "species": "Eptesicus fuscus/Lasiurus borealis", + "common_name": "Big brown bat/Eastern red bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "56", + "fields": { + "species_code": "EPFULANO", + "family": "", + "genus": "", + "species": "Eptesicus fuscus/Lasionycteris noctivagans", + "common_name": "Big brown bat/Silver-haired bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "85", + "fields": { + "species_code": "EPFUMYLU", + "family": "", + "genus": "Eptesicus/Myotis", + "species": "Eptesicus fuscus/Myotis lucifugus", + "common_name": "Big brown bat/Little brown bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "7", + "fields": { + "species_code": "EUFL", + "family": "Molossidae", + "genus": "Eumops", + "species": "Eumops floridanus", + "common_name": "Florida bonneted bat", + "species_code_6": "EUMFLO" + } + }, + { + "model": "core.Species", + "pk": "6", + "fields": { + "species_code": "EUMA", + "family": "Vespertilionidae", + "genus": "Euderma", + "species": "Euderma maculatum", + "common_name": "Spotted bat", + "species_code_6": "EUDMAC" + } + }, + { + "model": "core.Species", + "pk": "8", + "fields": { + "species_code": "EUPE", + "family": "Molossidae", + "genus": "Eumops", + "species": "Eumops perotis", + "common_name": "Greater mastiff bat", + "species_code_6": "EUMPER" + } + }, + { + "model": "core.Species", + "pk": "9", + "fields": { + "species_code": "EUUN", + "family": "Molossidae", + "genus": "Eumops", + "species": "Eumops underwoodi", + "common_name": "Underwood's mastiff bat", + "species_code_6": "EUMUND" + } + }, + { + "model": "core.Species", + "pk": "71", + "fields": { + "species_code": "HighF", + "family": "", + "genus": "", + "species": "", + "common_name": "Various species with pulses having a minimum frequency higher than 30 kHz.", + "species_code_6": "HiF" + } + }, + { + "model": "core.Species", + "pk": "10", + "fields": { + "species_code": "IDPH", + "family": "Vespertilionidae", + "genus": "Idionycteris", + "species": "Idionycteris phyllotis", + "common_name": "Allen's big-eared bat", + "species_code_6": "IDIPHY" + } + }, + { + "model": "core.Species", + "pk": "12", + "fields": { + "species_code": "LABL", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus blossevillii", + "common_name": "Western red bat", + "species_code_6": "LASBLO" + } + }, + { + "model": "core.Species", + "pk": "57", + "fields": { + "species_code": "LABLPAHE", + "family": "", + "genus": "", + "species": "Lasiurus blossevillii/Parastrellus hesperus", + "common_name": "Western red bat/Canyon bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "13", + "fields": { + "species_code": "LABO", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus borealis", + "common_name": "Eastern red bat", + "species_code_6": "LASBOR" + } + }, + { + "model": "core.Species", + "pk": "60", + "fields": { + "species_code": "LABOLASE", + "family": "", + "genus": "", + "species": "Lasiurus borealis/Lasiurus seminolus", + "common_name": "Eastern red bat/Seminole bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "59", + "fields": { + "species_code": "LABOMYLU", + "family": "", + "genus": "", + "species": "Lasiurus borealis/Myotis lucifugus", + "common_name": "Eastern red bat/Little brown bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "83", + "fields": { + "species_code": "LABONYHU", + "family": "", + "genus": "", + "species": "Lasiurus borealis/Nycticeius humeralis", + "common_name": "Eastern red bat/Evening bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "58", + "fields": { + "species_code": "LABOPESU", + "family": "", + "genus": "", + "species": "Lasiurus borealis/Perimyotis subflavus", + "common_name": "Eastern red bat/Tricolored bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "14", + "fields": { + "species_code": "LACI", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus cinereus", + "common_name": "Hoary bat", + "species_code_6": "LASCIN" + } + }, + { + "model": "core.Species", + "pk": "72", + "fields": { + "species_code": "LACILANO", + "family": "", + "genus": "", + "species": "Lasiurus cinereus/Lasionycteris noctivagans", + "common_name": "Hoary bat/Silver-haired bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "62", + "fields": { + "species_code": "LACITABR", + "family": "", + "genus": "", + "species": "Lasiurus cinereus/Tadarida brasiliensis", + "common_name": "Hoary bat/Mexican free-tailed bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "15", + "fields": { + "species_code": "LAEG", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus ega", + "common_name": "Southern yellow bat", + "species_code_6": "LASEGA" + } + }, + { + "model": "core.Species", + "pk": "16", + "fields": { + "species_code": "LAIN", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus intermedius", + "common_name": "Northern yellow bat", + "species_code_6": "LASINT" + } + }, + { + "model": "core.Species", + "pk": "48", + "fields": { + "species_code": "LAMI", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus minor", + "common_name": "Minor red bat", + "species_code_6": "LASMIN" + } + }, + { + "model": "core.Species", + "pk": "11", + "fields": { + "species_code": "LANO", + "family": "Vespertilionidae", + "genus": "Lasionycteris", + "species": "Lasionycteris noctivagans", + "common_name": "Silver-haired bat", + "species_code_6": "LASNOC" + } + }, + { + "model": "core.Species", + "pk": "61", + "fields": { + "species_code": "LANOTABR", + "family": "", + "genus": "", + "species": "Lasiurus noctivagans/Tadarida brasiliensis", + "common_name": "Silver-haired bat/Mexican free-tailed bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "17", + "fields": { + "species_code": "LASE", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus seminolus", + "common_name": "Seminole bat", + "species_code_6": "LASSEM" + } + }, + { + "model": "core.Species", + "pk": "18", + "fields": { + "species_code": "LAXA", + "family": "Vespertilionidae", + "genus": "Lasiurus", + "species": "Lasiurus xanthinus", + "common_name": "Western yellow bat", + "species_code_6": "LASXAN" + } + }, + { + "model": "core.Species", + "pk": "19", + "fields": { + "species_code": "LENI", + "family": "Phyllostomidae", + "genus": "Leptonycteris", + "species": "Leptonycteris nivalis", + "common_name": "Greater long-nosed bat", + "species_code_6": "LEPNIV" + } + }, + { + "model": "core.Species", + "pk": "74", + "fields": { + "species_code": "LESP", + "family": "", + "genus": "Leptonycteris", + "species": "", + "common_name": "Unidentifiable species of the Leptonycteris genus", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "20", + "fields": { + "species_code": "LEYE", + "family": "Phyllostomidae", + "genus": "Leptonycteris", + "species": "Leptonycteris yerbabuenae", + "common_name": "Lesser long-nosed bat", + "species_code_6": "LEPYER" + } + }, + { + "model": "core.Species", + "pk": "70", + "fields": { + "species_code": "LowF", + "family": "", + "genus": "", + "species": "", + "common_name": "Various species with pulses having a minimum frequency lower than 30 kHz.", + "species_code_6": "LoF" + } + }, + { + "model": "core.Species", + "pk": "75", + "fields": { + "species_code": "LUSO", + "family": "", + "genus": "Myotis", + "species": "Myotis lucifugus/Myotis sodalis", + "common_name": "Little brown bat/Indiana bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "21", + "fields": { + "species_code": "MACA", + "family": "Phyllostomidae", + "genus": "Macrotus", + "species": "Macrotus californicus", + "common_name": "California leaf-nosed bat", + "species_code_6": "MACCAL" + } + }, + { + "model": "core.Species", + "pk": "23", + "fields": { + "species_code": "MOME", + "family": "Mormoopidae", + "genus": "Mormoops", + "species": "Mormoops megalophylla", + "common_name": "Ghost-faced bat", + "species_code_6": "MORMEG" + } + }, + { + "model": "core.Species", + "pk": "22", + "fields": { + "species_code": "MOMO", + "family": "Molossidae", + "genus": "Molossus", + "species": "Molossus molossus", + "common_name": "Pallas's mastiff bat", + "species_code_6": "MOLMOL" + } + }, + { + "model": "core.Species", + "pk": "29", + "fields": { + "species_code": "MYGR", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis grisescens", + "common_name": "Gray bat", + "species_code_6": "MYOGRI" + } + }, + { + "model": "core.Species", + "pk": "32", + "fields": { + "species_code": "MYLU", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis lucifugus", + "common_name": "Little brown bat", + "species_code_6": "MYOLUC" + } + }, + { + "model": "core.Species", + "pk": "84", + "fields": { + "species_code": "MYLUMYSE", + "family": "", + "genus": "", + "species": "Myotis lucifugus/Myotis septentrionalis", + "common_name": "Little brown bat/Northern long-eared bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "33", + "fields": { + "species_code": "MYSE", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis septentrionalis", + "common_name": "Northern long-eared bat", + "species_code_6": "MYOSEP" + } + }, + { + "model": "core.Species", + "pk": "34", + "fields": { + "species_code": "MYSO", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis sodalis", + "common_name": "Indiana bat", + "species_code_6": "MYOSOD" + } + }, + { + "model": "core.Species", + "pk": "24", + "fields": { + "species_code": "MYAR", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis auriculus", + "common_name": "Southwestern bat", + "species_code_6": "MYOAUR" + } + }, + { + "model": "core.Species", + "pk": "69", + "fields": { + "species_code": "MYSP", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "", + "common_name": "General Myotis Spcies Code", + "species_code_6": "40kMyo" + } + }, + { + "model": "core.Species", + "pk": "25", + "fields": { + "species_code": "MYAU", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis austroriparius", + "common_name": "Southeastern bat", + "species_code_6": "MYOAUS" + } + }, + { + "model": "core.Species", + "pk": "26", + "fields": { + "species_code": "MYCA", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis californicus", + "common_name": "California bat ", + "species_code_6": "MYOCAL" + } + }, + { + "model": "core.Species", + "pk": "4", + "fields": { + "species_code": "COTO", + "family": "Vespertilionidae", + "genus": "Corynorhinus", + "species": "Corynorhinus townsendii", + "common_name": "Townsend's big-eared bat", + "species_code_6": "CORTOW" + } + }, + { + "model": "core.Species", + "pk": "86", + "fields": { + "species_code": "COTV", + "family": "Vespertilionidae", + "genus": "Corynorhinus", + "species": "Corynorhinus townsendii virginianus", + "common_name": "Virginia big-eared bat", + "species_code_6": "COTOVI" + } + }, + { + "model": "core.Species", + "pk": "66", + "fields": { + "species_code": "NoID", + "family": "", + "genus": "", + "species": "", + "common_name": "Bat but no grouping or user-defined category applies", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "53", + "fields": { + "species_code": "NOLE", + "family": "Noctilionidae", + "genus": "Noctilio", + "species": "Noctilio leporinus", + "common_name": "Greater bulldog bat", + "species_code_6": "NOCLEP" + } + }, + { + "model": "core.Species", + "pk": "40", + "fields": { + "species_code": "NYFE", + "family": "Molossidae", + "genus": "Nyctinomops", + "species": "Nyctinomops femorosaccus", + "common_name": "Pocketed free-tailed bat", + "species_code_6": "NYCFEM" + } + }, + { + "model": "core.Species", + "pk": "39", + "fields": { + "species_code": "NYHU", + "family": "Vespertilionidae", + "genus": "Nycticeius", + "species": "Nycticeius humeralis", + "common_name": "Evening bat", + "species_code_6": "NYCHUM" + } + }, + { + "model": "core.Species", + "pk": "41", + "fields": { + "species_code": "NYMA", + "family": "Molossidae", + "genus": "Nyctinomops", + "species": "Nyctinomops macrotis", + "common_name": "Big free-tailed bat", + "species_code_6": "NYCMAC" + } + }, + { + "model": "core.Species", + "pk": "42", + "fields": { + "species_code": "PAHE", + "family": "Vespertilionidae", + "genus": "Parastrellus", + "species": "Parastrellus hesperus", + "common_name": "Canyon bat", + "species_code_6": "PARHES" + } + }, + { + "model": "core.Species", + "pk": "43", + "fields": { + "species_code": "PESU", + "family": "Vespertilionidae", + "genus": "Perimyotis", + "species": "Perimyotis subflavus", + "common_name": "Tricolored bat", + "species_code_6": "PERSUB" + } + }, + { + "model": "core.Species", + "pk": "54", + "fields": { + "species_code": "STRU", + "family": "Phyllostomidae", + "genus": "Stenoderma", + "species": "Stenoderma rufum", + "common_name": "Red fruit bat", + "species_code_6": "STERUF" + } + }, + { + "model": "core.Species", + "pk": "44", + "fields": { + "species_code": "TABR", + "family": "Molossidae", + "genus": "Tadarida", + "species": "Tadarida brasiliensis", + "common_name": "Brazilian free-tailed bat", + "species_code_6": "TADBRA" + } + }, + { + "model": "core.Species", + "pk": "63", + "fields": { + "species_code": "LEMY", + "family": "", + "genus": "", + "species": "Myotis evotis/Myotis septentrionalis", + "common_name": "Western long-eared myotis/Keen's myotis/Northern long-eared bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "89", + "fields": { + "species_code": "NoBat", + "family": "", + "genus": "", + "species": "", + "common_name": "Code used for surveys that recorded 0 bats", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "78", + "fields": { + "species_code": "MYCAMYCI", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis californicus/Myotis ciliolabrum", + "common_name": "California bat/Western small-footed bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "64", + "fields": { + "species_code": "MYCAMYYU", + "family": "", + "genus": "", + "species": "Myotis californicus/Myotis yumanensis", + "common_name": "California bat/Yuma bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "27", + "fields": { + "species_code": "MYCI", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis ciliolabrum", + "common_name": "Western small-footed bat ", + "species_code_6": "MYOCIL" + } + }, + { + "model": "core.Species", + "pk": "73", + "fields": { + "species_code": "MYCIMYVO", + "family": "", + "genus": "", + "species": "Myotis ciliolabrum/Myotis volans", + "common_name": "Western small-footed bat/Long-legged bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "28", + "fields": { + "species_code": "MYEV", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis evotis", + "common_name": "Western long-eared bat", + "species_code_6": "MYOEVO" + } + }, + { + "model": "core.Species", + "pk": "80", + "fields": { + "species_code": "MYEVMYTH", + "family": "", + "genus": "Myotis", + "species": "Myotis evotis/Myotis thysanodes", + "common_name": "Western long-eared bat/Fringed bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "31", + "fields": { + "species_code": "MYLE", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis leibii", + "common_name": "Eastern small-footed bat", + "species_code_6": "MYOLEI" + } + }, + { + "model": "core.Species", + "pk": "81", + "fields": { + "species_code": "MYLUMYCI", + "family": "", + "genus": "Myotis", + "species": "Myotis lucifugus/Myotis ciliolabrum", + "common_name": "Little brown bat/Western small-footed bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "79", + "fields": { + "species_code": "MYLUMYVO", + "family": "", + "genus": "Myotis", + "species": "Myotis lucifugus/Myotis volans", + "common_name": "Little brown bat/Long-legged bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "52", + "fields": { + "species_code": "MYOC", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis occultus", + "common_name": "Arizona bat", + "species_code_6": "MYOOCC" + } + }, + { + "model": "core.Species", + "pk": "35", + "fields": { + "species_code": "MYTH", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis thysanodes", + "common_name": "Fringed bat", + "species_code_6": "MYOTHY" + } + }, + { + "model": "core.Species", + "pk": "36", + "fields": { + "species_code": "MYVE", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis velifer", + "common_name": "Cave bat", + "species_code_6": "MYOVEL" + } + }, + { + "model": "core.Species", + "pk": "37", + "fields": { + "species_code": "MYVO", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis volans", + "common_name": "Long-legged bat", + "species_code_6": "MYOVOL" + } + }, + { + "model": "core.Species", + "pk": "38", + "fields": { + "species_code": "MYYU", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis yumanensis", + "common_name": "Yuma bat ", + "species_code_6": "MYOYUM" + } + }, + { + "model": "core.Species", + "pk": "88", + "fields": { + "species_code": "MYYUMYLU", + "family": "Vespertilionidae", + "genus": "Myotis", + "species": "Myotis yumanensis/Myotis lucifugus", + "common_name": "Yuma bat/Little brown bat", + "species_code_6": "" + } + }, + { + "model": "core.Species", + "pk": "90", + "fields": { + "species_code": "COTI", + "family": "Vespertilionidae", + "genus": "Corynorhinus", + "species": "Corynorhinus townsendii ingens", + "common_name": "Ozark big-eared bat", + "species_code_6": "COTOIN" + } + }, + { + "model": "core.Species", + "pk": "77", + "fields": { + "species_code": "NYSP", + "family": "Molossidae", + "genus": "Nyctinomops", + "species": "", + "common_name": "Unidentifiable species of the Nyctinomops genus", + "species_code_6": "NYCSPP" + } + } +] diff --git a/bats_ai/core/migrations/0003_annotations.py b/bats_ai/core/migrations/0003_annotations.py new file mode 100644 index 0000000..edb65c4 --- /dev/null +++ b/bats_ai/core/migrations/0003_annotations.py @@ -0,0 +1,44 @@ +# Generated by Django 4.1.13 on 2024-01-05 19:43 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0002_species'), + ] + + operations = [ + migrations.CreateModel( + name='Annotations', + fields=[ + ( + 'id', + models.BigAutoField( + auto_created=True, primary_key=True, serialize=False, verbose_name='ID' + ), + ), + ('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)), + ('comments', models.TextField(blank=True, null=True)), + ( + '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')), + ], + ), + ] diff --git a/bats_ai/core/models/__init__.py b/bats_ai/core/models/__init__.py index 050566b..5610854 100644 --- a/bats_ai/core/models/__init__.py +++ b/bats_ai/core/models/__init__.py @@ -1,3 +1,4 @@ +from .annotations import Annotations from .image import Image from .recording import Recording from .recording_annotation_status import RecordingAnnotationStatus @@ -8,4 +9,5 @@ 'Recording', 'RecordingAnnotationStatus', 'Species', + 'Annotations', ] diff --git a/bats_ai/core/models/annotations.py b/bats_ai/core/models/annotations.py index 836b531..91c91db 100644 --- a/bats_ai/core/models/annotations.py +++ b/bats_ai/core/models/annotations.py @@ -5,7 +5,7 @@ from .species import Species -class Species(models.Model): +class Annotations(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) diff --git a/bats_ai/core/views/__init__.py b/bats_ai/core/views/__init__.py index 6c5ef46..6a0e84a 100644 --- a/bats_ai/core/views/__init__.py +++ b/bats_ai/core/views/__init__.py @@ -1,5 +1,7 @@ from .recording import router as RecordingRouter +from .species import router as SpeciesRouter __all__ = [ 'RecordingRouter', + 'SpeciesRouter', ] diff --git a/bats_ai/core/views/recording.py b/bats_ai/core/views/recording.py index c4526d1..6578fad 100644 --- a/bats_ai/core/views/recording.py +++ b/bats_ai/core/views/recording.py @@ -10,7 +10,8 @@ from ninja.pagination import RouterPaginated from oauth2_provider.models import AccessToken -from bats_ai.core.models import Recording +from bats_ai.core.models import Annotations, Recording, Species +from bats_ai.core.views.species import SpeciesSchema logger = logging.getLogger(__name__) @@ -37,20 +38,33 @@ class RecordingUploadSchema(Schema): comments: str | None -def get_owner_id(request: HttpRequest): - token = request.headers.get('Authorization').replace('Bearer ', '') - token_found = AccessToken.objects.get(token=token) - if not token_found: - raise HttpError(401, 'Authentication credentials were not provided.') +class AnnotationSchema(Schema): + start_time: int + end_time: int + low_freq: int + high_freq: int + species: list[SpeciesSchema] + comments: str + - return token_found.user.pk +def get_user(request: HttpRequest): + auth_header = request.headers.get('Authorization', None) + if auth_header is not None: + token = request.headers.get('Authorization').replace('Bearer ', '') + token_found = AccessToken.objects.get(token=token) + if not token_found: + raise HttpError(401, 'Authentication credentials were not provided.') + return token_found.user + elif request.user: + logger.warning(f'User: {request.user}') + return request.user @router.post('/') def create_recording( request: HttpRequest, payload: Form[RecordingUploadSchema], audio_file: File[UploadedFile] ): - user_id = get_owner_id(request) + user_id = get_user(request).pk converted_date = datetime.strptime(payload.recorded_date, '%Y-%m-%d') recording = Recording( name=payload.name, @@ -68,7 +82,7 @@ def create_recording( @router.get('/') def get_recordings(request: HttpRequest): # Check if the user is authenticated and get userId - user_id = get_owner_id(request) + user_id = get_user(request) # Filter recordings based on the owner's id recordings = Recording.objects.filter(owner=user_id).values() @@ -92,3 +106,119 @@ def get_spectrogram(request: HttpRequest, id: int): base64_spectrogram = recording.generate_spectrogram() return {'base64_spectrogram': base64_spectrogram} + + +@router.get('/{id}/annotations') +def get_annotations(request: HttpRequest, id: int): + user_id = get_user(request) + + try: + recording = Recording.objects.get(pk=id, owner=user_id) + except Recording.DoesNotExist: + return {'error': 'Recording not found'} + + # Query annotations associated with the recording + annotations_qs = Annotations.objects.filter(recording=recording) + + # Serialize the annotations using AnnotationSchema + annotations_data = [ + AnnotationSchema.from_orm(annotation).dict() for annotation in annotations_qs + ] + + return annotations_data + + +@router.put('/{id}/annotations') +def put_annotation( + request, + id: int, + annotation: AnnotationSchema, + species_ids: list[int], +): + user = get_user(request) + + try: + recording = Recording.objects.get(pk=id, owner=user.pk) + except Recording.DoesNotExist: + return {'error': 'Recording not found'} + + # Create a new annotation + new_annotation = Annotations.objects.create( + recording=recording, + owner=user, + start_time=annotation.start_time, + end_time=annotation.end_time, + low_freq=annotation.low_freq, + high_freq=annotation.high_freq, + comments=annotation.comments, + ) + + # Add species to the annotation based on the provided species_ids + for species_id in species_ids: + try: + species_obj = Species.objects.get(pk=species_id) + new_annotation.species.add(species_obj) + except Species.DoesNotExist: + # Handle the case where the species with the given ID doesn't exist + return {'error': f'Species with ID {species_id} not found'} + + return {'message': 'Annotation added successfully', 'id': new_annotation.pk} + + +@router.patch('/{recording_id}/annotations/{id}') +def patch_annotation( + request, + recording_id: int, + id: int, + annotation: AnnotationSchema, + species_ids: list[int], +): + user_id = get_user(request) + + try: + recording = Recording.objects.get(pk=recording_id, owner=user_id) + annotation_instance = Annotations.objects.get(pk=id, recording=recording) + except Recording.DoesNotExist: + return {'error': 'Recording not found'} + except Annotations.DoesNotExist: + return {'error': 'Annotation not found'} + + # Update annotation details + annotation_instance.start_time = annotation.start_time + annotation_instance.end_time = annotation.end_time + annotation_instance.low_freq = annotation.low_freq + annotation_instance.high_freq = annotation.high_freq + annotation_instance.comments = annotation.comments + annotation_instance.save() + + # Clear existing species associations + annotation_instance.species.clear() + + # Add species to the annotation based on the provided species_ids + for species_id in species_ids: + try: + species_obj = Species.objects.get(pk=species_id) + annotation_instance.species.add(species_obj) + except Species.DoesNotExist: + # Handle the case where the species with the given ID doesn't exist + return {'error': f'Species with ID {species_id} not found'} + + return {'message': 'Annotation updated successfully', 'id': annotation_instance.pk} + + +@router.delete('/{recording_id}/annotations/{id}') +def delete_annotation(request, recording_id: int, id: int): + user_id = get_user(request) + + try: + recording = Recording.objects.get(pk=recording_id, owner=user_id) + annotation_instance = Annotations.objects.get(pk=id, recording=recording) + except Recording.DoesNotExist: + return {'error': 'Recording not found'} + except Annotations.DoesNotExist: + return {'error': 'Annotation not found'} + + # Delete the annotation + annotation_instance.delete() + + return {'message': 'Annotation deleted successfully'} diff --git a/bats_ai/core/views/species.py b/bats_ai/core/views/species.py new file mode 100644 index 0000000..fe0bae1 --- /dev/null +++ b/bats_ai/core/views/species.py @@ -0,0 +1,29 @@ +import logging + +from django.http import HttpRequest +from ninja import Schema +from ninja.pagination import RouterPaginated + +from bats_ai.core.models import Species + +logger = logging.getLogger(__name__) + + +router = RouterPaginated() + + +class SpeciesSchema(Schema): + species_code: str | None + family: str | None + genus: str | None + species: str | None + common_name: str | None + species_code_6: str | None + + +@router.get('/') +def get_species(request: HttpRequest): + species = Species.objects.values() + + # Return the serialized data + return list(species) diff --git a/bats_ai/settings.py b/bats_ai/settings.py index 9a80343..9764a92 100644 --- a/bats_ai/settings.py +++ b/bats_ai/settings.py @@ -73,7 +73,7 @@ class DevelopmentConfiguration(BatsAiMixin, DevelopmentBaseConfiguration): MINIO_STORAGE_AUTO_CREATE_MEDIA_BUCKET = True MINIO_STORAGE_AUTO_CREATE_MEDIA_POLICY = 'READ_WRITE' MINIO_STORAGE_MEDIA_USE_PRESIGNED = True - MINIO_STORAGE_MEDIA_URL = 'http://127.0.0.1:9000/rdwatch' + MINIO_STORAGE_MEDIA_URL = 'http://127.0.0.1:9000/django-storage' class TestingConfiguration(BatsAiMixin, TestingBaseConfiguration): diff --git a/client/package.json b/client/package.json index ab69ce0..a6275bd 100644 --- a/client/package.json +++ b/client/package.json @@ -8,6 +8,7 @@ "preview": "vite preview", "format": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --fix src", "lint:eslint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --no-fix src", + "lint": "eslint --ext .js,.ts,.vue --ignore-path .gitignore --no-fix src & vue-tsc -noEmit", "test": "jest", "lint:typescript": "vue-tsc --noEmit" }, diff --git a/client/src/components/SpectrogramViewer.vue b/client/src/components/SpectrogramViewer.vue index ace305d..f6c73ad 100644 --- a/client/src/components/SpectrogramViewer.vue +++ b/client/src/components/SpectrogramViewer.vue @@ -1,6 +1,7 @@