nocek 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nocek/__init__.py +3 -0
- nocek/__main__.py +5 -0
- nocek/cli.py +165 -0
- nocek/core/__init__.py +5 -0
- nocek/core/_data.py +13 -0
- nocek/core/audio.py +44 -0
- nocek/core/batdetect.py +320 -0
- nocek/core/battybird.py +383 -0
- nocek/core/consensus.py +190 -0
- nocek/core/data/batdetect2_classes.json +31 -0
- nocek/core/data/battybird_labels.json +34 -0
- nocek/core/data/comparison_groups.json +9 -0
- nocek/core/data/key_bands.json +24 -0
- nocek/core/data/regions.json +30 -0
- nocek/core/data/taxa.json +29 -0
- nocek/core/detection.py +336 -0
- nocek/core/key.py +159 -0
- nocek/core/nights.py +15 -0
- nocek/core/opinion.py +78 -0
- nocek/core/pipeline.py +194 -0
- nocek/core/region.py +116 -0
- nocek/core/spectrogram.py +70 -0
- nocek/core/taxonomy.py +77 -0
- nocek/icons/nocek.icns +0 -0
- nocek/icons/nocek.ico +0 -0
- nocek/icons/nocek.png +0 -0
- nocek/py.typed +0 -0
- nocek/recorders/__init__.py +5 -0
- nocek/recorders/base.py +108 -0
- nocek/recorders/checks.py +84 -0
- nocek/recorders/pippyg.py +98 -0
- nocek/recorders/registry.py +8 -0
- nocek/recorders/sun.py +89 -0
- nocek/shortcut.py +192 -0
- nocek/storage/__init__.py +1 -0
- nocek/storage/database.py +72 -0
- nocek/storage/importer.py +270 -0
- nocek/storage/library.py +72 -0
- nocek/storage/migrations/0001_initial.sql +92 -0
- nocek/storage/migrations/0002_site_numbers.sql +28 -0
- nocek/storage/repository.py +494 -0
- nocek/storage/serialize.py +304 -0
- nocek/storage/settings.py +123 -0
- nocek/storage/summaries.py +322 -0
- nocek/web/__init__.py +1 -0
- nocek/web/api.py +190 -0
- nocek/web/app.py +91 -0
- nocek/web/dialog.py +101 -0
- nocek/web/errors.py +25 -0
- nocek/web/export_api.py +152 -0
- nocek/web/folders.py +97 -0
- nocek/web/i18n.py +91 -0
- nocek/web/jobs.py +427 -0
- nocek/web/jobs_api.py +72 -0
- nocek/web/library_api.py +465 -0
- nocek/web/locales/de.json +426 -0
- nocek/web/locales/en.json +426 -0
- nocek/web/locales/pl.json +450 -0
- nocek/web/media.py +119 -0
- nocek/web/media_api.py +105 -0
- nocek/web/security.py +51 -0
- nocek/web/state.py +60 -0
- nocek/web/static/favicon.png +0 -0
- nocek/web/static/index.html +23 -0
- nocek/web/static/js/api.js +26 -0
- nocek/web/static/js/app.js +34 -0
- nocek/web/static/js/bat-colours.js +38 -0
- nocek/web/static/js/calendar.js +151 -0
- nocek/web/static/js/dialog.js +20 -0
- nocek/web/static/js/dom.js +35 -0
- nocek/web/static/js/folder-picker.js +71 -0
- nocek/web/static/js/header.js +80 -0
- nocek/web/static/js/i18n.js +69 -0
- nocek/web/static/js/jobs.js +122 -0
- nocek/web/static/js/magma.js +10 -0
- nocek/web/static/js/names.js +44 -0
- nocek/web/static/js/nightbar.js +330 -0
- nocek/web/static/js/notice.js +27 -0
- nocek/web/static/js/quit.js +43 -0
- nocek/web/static/js/router.js +33 -0
- nocek/web/static/js/spectrogram.js +175 -0
- nocek/web/static/js/theme-early.js +12 -0
- nocek/web/static/js/theme.js +28 -0
- nocek/web/static/js/views/devices.js +90 -0
- nocek/web/static/js/views/home.js +318 -0
- nocek/web/static/js/views/import.js +325 -0
- nocek/web/static/js/views/recording.js +277 -0
- nocek/web/static/js/views/settings.js +137 -0
- nocek/web/static/js/views/sites.js +177 -0
- nocek/web/static/js/views/welcome.js +88 -0
- nocek/web/static/style.css +464 -0
- nocek/web/static/vendor/README.txt +2 -0
- nocek/web/static/vendor/lucide/LICENSE +43 -0
- nocek/web/static/vendor/lucide/audio-lines.svg +20 -0
- nocek/web/static/vendor/lucide/check.svg +15 -0
- nocek/web/static/vendor/lucide/chevron-down.svg +15 -0
- nocek/web/static/vendor/lucide/chevron-left.svg +15 -0
- nocek/web/static/vendor/lucide/chevron-right.svg +15 -0
- nocek/web/static/vendor/lucide/circle-alert.svg +17 -0
- nocek/web/static/vendor/lucide/download.svg +17 -0
- nocek/web/static/vendor/lucide/ellipsis.svg +17 -0
- nocek/web/static/vendor/lucide/folder-open.svg +15 -0
- nocek/web/static/vendor/lucide/info.svg +17 -0
- nocek/web/static/vendor/lucide/languages.svg +20 -0
- nocek/web/static/vendor/lucide/map-pin.svg +16 -0
- nocek/web/static/vendor/lucide/monitor.svg +17 -0
- nocek/web/static/vendor/lucide/moon.svg +15 -0
- nocek/web/static/vendor/lucide/pause.svg +16 -0
- nocek/web/static/vendor/lucide/play.svg +15 -0
- nocek/web/static/vendor/lucide/power.svg +16 -0
- nocek/web/static/vendor/lucide/radio.svg +19 -0
- nocek/web/static/vendor/lucide/settings.svg +16 -0
- nocek/web/static/vendor/lucide/sun-moon.svg +19 -0
- nocek/web/static/vendor/lucide/sun.svg +23 -0
- nocek/web/static/vendor/lucide/x.svg +16 -0
- nocek/web/static/vendor/lucide/zoom-in.svg +18 -0
- nocek/web/static/vendor/lucide/zoom-out.svg +17 -0
- nocek/web/static/vendor/webawesome/LICENSE.md +7 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.26QE47KB.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2BXLTQVW.js +41 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2E2VZMOR.js +121 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2FVCYQY4.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2LXKNNNE.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2NOGQWD2.js +64 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2NV6QW6C.js +127 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2OUDDW5N.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2QMPIFI4.js +211 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2SO23SX3.js +72 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2WDGLBJO.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.2YEETXBR.js +112 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3774FK3E.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3BILHKLX.js +301 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3H2FQERJ.js +25 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3MALJW2P.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3MSWQ3RG.js +20 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.3NKIHICW.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.43IMJ6LM.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.457WG26C.js +440 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.46NHROTP.js +2239 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4AI6OCVC.js +261 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4EC7IRRU.js +353 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4IALDZ4Y.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4LIBWWJL.js +18 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4MQTK254.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4OSDOJJ4.js +312 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4UDEZ2KB.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4XYGFHPR.js +99 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.4ZAKP7NY.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.55AH5UAX.js +76 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5FPZSTUW.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5H6HB774.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5JBQCQFB.js +92 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5KKHTQSP.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5PFAYAIK.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5VKLVAP2.js +330 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.5ZSSLK3M.js +68 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6CIAVEBC.js +65 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6FQNTW2R.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6J5ZEH6X.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6SNQOYNK.js +61 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6TNHHCAM.js +37 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6VIHFQLB.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6VPDWW2I.js +274 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.6ZVWWDQT.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.7HGYKBWW.js +94 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.7QISORGK.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.7RXXO7LR.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.7ZCFTSGG.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.A5622CLY.js +209 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.A6PE4UC7.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AAHO5GAS.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AB2K5S2N.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AG44H7MD.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AJQ3S5VI.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AOZFN6E7.js +147 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AP3JK77D.js +184 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.AQZVKFHG.js +33 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ASU4ZOK4.js +235 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.B3QSBNFD.js +89 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.B6O732C7.js +31 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BAKBSVVU.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BAY6RJD3.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BGEID5TE.js +70 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BKE5EYM3.js +274 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BLGT2UQZ.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BMO76VKZ.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BOYYUMGV.js +256 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BQNDCXAL.js +51 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BSKW43C6.js +247 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BUCKADPY.js +41 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.BZMNT3VX.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.C435GZCM.js +118 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.CCRRQN4H.js +92 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.CEY3XO3U.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.CV35G2DF.js +305 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.CXF4H6Y3.js +117 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.CYF6RIMN.js +145 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D2XOEELP.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D5FEZLQU.js +42 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D5HA4OFX.js +189 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D5YFE5NT.js +1 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D6GXWYLR.js +93 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D7J2HJDE.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.D7T5MUIJ.js +395 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DEXB7B6F.js +547 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DITA2S5A.js +80 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DL7HG4WM.js +23 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DMDEL2LT.js +136 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DOVAIU4B.js +264 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.DP5YIFD7.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.E2VSSWZB.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ECEQUBWG.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EF25YJJE.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EFKLEU6F.js +1922 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EFUXUR2V.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EIWUDXXU.js +1333 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EP5NMLNE.js +101 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EUQGFY4Z.js +338 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.EV5QZWZG.js +155 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.F25QOBDY.js +18 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.F2PHWUN2.js +550 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.F4JHOXPG.js +122 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.FG2PKHMT.js +153 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.FMGPE4WI.js +17 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.FNFKITIN.js +25 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.FV2UJLMK.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.FYKN76UA.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.G6XOOSBC.js +65 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GDKEEBXF.js +94 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GDWSLLNY.js +85 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GJBQN6LP.js +31 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GNSEW4OW.js +33 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GWSUX3V5.js +43 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.GYYOD525.js +89 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.H23DVATU.js +36 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.H7TA73OO.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HD5OAVHA.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HE4U2ARF.js +228 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HGBRCPUS.js +55 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HJ65SLMP.js +51 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HJQBZY2G.js +372 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HKMBVADD.js +201 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HLGGDMDY.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HNCFWHCC.js +138 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HOKYDFUG.js +124 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HPULLNVR.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HU5FIEKB.js +173 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.HYVJ2NPG.js +224 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.I2Y4E7B6.js +94 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.I7YQOYV2.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ICDCAGCX.js +24 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IHXCBN37.js +95 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IIHGIRPB.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IJOBO25X.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IOZQCLLD.js +26 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IPQSINJJ.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IX2GQ2J6.js +342 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.IYKD6SDL.js +315 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.J2WRH4YL.js +144 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.J4UHUULC.js +184 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.J7EXAHCE.js +167 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JATRSH7G.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JB5Y2AN3.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JBSQSXSP.js +108 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JHZRD2LV.js +60 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JJKKSYRO.js +37 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JOAOXII7.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JTOY5KP3.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.JWREXNHC.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.K5EDTD7G.js +48 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.K5Q2EBKV.js +77 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KBLB7M2I.js +956 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KBS6YHTA.js +1 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KGCSF52Q.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KH7MR2OK.js +98 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KJ34WZZX.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KJKGFBJH.js +452 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KKI7M5DP.js +158 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KNPPWD7S.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KO4RVYVT.js +58 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KSVLZZAC.js +84 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.KWDPKKFO.js +46 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.L2PH6XPF.js +47 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.L6CIKOFQ.js +55 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.L7UUCOPZ.js +318 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LAIHMICP.js +241 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LBLI4KS5.js +353 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LCFSCRUJ.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LD7WPEY6.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LMUGFDHU.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LN7M2NWC.js +980 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LNFTFTSZ.js +77 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LOCSL3WJ.js +656 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LPHX3BNP.js +92 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.LRYJ2M5H.js +24 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.M3V3HAFX.js +339 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.M6MK4JNZ.js +189 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.M7O5VY4E.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MBU7SJFC.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MEATLWHD.js +1 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MF234WAF.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MGSBRQWA.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MHOTNIZ4.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MHY5F46I.js +417 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MO2KS3JW.js +239 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MOCFCD22.js +311 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MPFW6BBX.js +33 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MQODJ75V.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MT4KGKUD.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.MYS3OD37.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.N2TXQSKF.js +1 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NDZDW54F.js +287 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NH3YPZ5E.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NJV43B2Z.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NR2LFKJE.js +44 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NS4E73TC.js +15 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NSSMHWNA.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NT6HDXPW.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NWZSEH5M.js +122 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NY2PQ35L.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NY7ZFHLS.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.NZNAY3HQ.js +303 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.O3FF2LGE.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.O6IZ4I7T.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ONMWPE2G.js +75 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.OUXOGF7E.js +480 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.OVHJANOP.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.OZ5VT3R5.js +31 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.P3CFHBKJ.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PIQFM4OJ.js +31 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PP5RFEZX.js +109 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PPALL6KG.js +169 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PSADQYP2.js +66 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PUN7URRY.js +290 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PX3HMKF7.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PZ23AOZF.js +166 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.PZAN6FPN.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Q23D34AW.js +98 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Q4QAYIGQ.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Q6XMGFWJ.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QD7TM6E5.js +73 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QG644JIK.js +98 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QGF6GJ2E.js +227 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QKPMXIMG.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QNZSHXSH.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QVJVDGCX.js +364 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.QXPWLPKE.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.R62LX7HF.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.R7QX4M6R.js +47 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.REBCVBVJ.js +272 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.REHZ4JRM.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RGZE6BBW.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RKPV5RWB.js +85 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RMZ7BVDM.js +10 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RO57OJZD.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ROEDV6HF.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RPQJAXXR.js +21 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RQUTRD4U.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RRKG3AQG.js +34 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RV5MEM4L.js +132 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RWNXKUCF.js +66 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.RXATNCKH.js +363 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.S3XWKRZZ.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SD34Q4G4.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SG72JPY2.js +81 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SJUGO6JB.js +105 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SKLR37OM.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SLTXS4GR.js +248 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SUZGY3IK.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SUZTEJNL.js +88 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SVFNJFHB.js +270 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.SXR5N7XU.js +1376 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.T6GGTYHD.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.T7CCLJ4F.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.TDR7XG37.js +30 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.TLFIX76K.js +340 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.TSARWN6S.js +275 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.U36KZLSQ.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UDRJMWVZ.js +55 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UIKY7WPI.js +72 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UK2M7WPO.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UMPSPH77.js +45 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UOYQVA3T.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UUEMMZG6.js +178 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UV4Y72XP.js +37 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.UVIXNJVD.js +428 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.V3B4FDYO.js +715 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.VC3BPUZJ.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.VO4OKF3D.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.VRQU2Y2Z.js +198 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.VSYG3NGW.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.VVLIN6UY.js +259 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.W2C46OQX.js +353 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.W3JJ6XED.js +127 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.W55GO32K.js +932 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WC7A3EOY.js +741 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WCGXFNG4.js +95 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WDIIGUNP.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WGFDW2LC.js +25 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WKX3BKNK.js +18 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WLKYECLX.js +858 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.WYNTFJHW.js +116 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.X542U3B2.js +337 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.X55YNZ3B.js +20 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.X7T4SHKO.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XELEYQBQ.js +249 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XJ4PEGQL.js +135 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XJOHOSCS.js +345 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XLATYKDG.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XMNDC2OW.js +452 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XSPQ4QMK.js +29 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XU63UNPU.js +2183 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XW6BKGGI.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XZ235HUK.js +113 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.XZPLJ4VW.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YBFCQDTA.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YD2JFSBQ.js +382 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YDQCS2HK.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YJNHQL5U.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YLIESRP2.js +377 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YMGBKPRR.js +32 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YN7QRSZ7.js +170 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YNIJJMXK.js +112 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YPRC5XZF.js +101 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YQNBAO2Z.js +45 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.YXOWVBUA.js +19 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Z4KHDJB4.js +173 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Z6LULOQL.js +97 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.Z73LSB5Z.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZA5G6FAT.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZFMOLE6Z.js +27 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZFSRFTCP.js +83 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZSEFTQAO.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZSNU3QL4.js +78 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZT4OZS6F.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZWQCGLB5.js +12 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZY7BMFLO.js +13 -0
- nocek/web/static/vendor/webawesome/chunks/chunk.ZZPJSMJ4.js +13 -0
- nocek/web/static/vendor/webawesome/components/accordion/accordion.js +39 -0
- nocek/web/static/vendor/webawesome/components/accordion/accordion.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/accordion-item/accordion-item.js +33 -0
- nocek/web/static/vendor/webawesome/components/accordion-item/accordion-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/animated-image/animated-image.js +28 -0
- nocek/web/static/vendor/webawesome/components/animated-image/animated-image.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/animation/animation.js +17 -0
- nocek/web/static/vendor/webawesome/components/animation/animation.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/animation/animations.js +12 -0
- nocek/web/static/vendor/webawesome/components/avatar/avatar.js +23 -0
- nocek/web/static/vendor/webawesome/components/avatar/avatar.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/badge/badge.js +13 -0
- nocek/web/static/vendor/webawesome/components/badge/badge.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/breadcrumb/breadcrumb.js +26 -0
- nocek/web/static/vendor/webawesome/components/breadcrumb/breadcrumb.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/breadcrumb-item/breadcrumb-item.js +14 -0
- nocek/web/static/vendor/webawesome/components/breadcrumb-item/breadcrumb-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/button/button.js +38 -0
- nocek/web/static/vendor/webawesome/components/button/button.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/button-group/button-group.js +12 -0
- nocek/web/static/vendor/webawesome/components/button-group/button-group.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/callout/callout.js +16 -0
- nocek/web/static/vendor/webawesome/components/callout/callout.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/card/card.js +16 -0
- nocek/web/static/vendor/webawesome/components/card/card.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/carousel/autoplay-controller.js +8 -0
- nocek/web/static/vendor/webawesome/components/carousel/carousel.js +34 -0
- nocek/web/static/vendor/webawesome/components/carousel/carousel.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/carousel/scroll-controller.js +110 -0
- nocek/web/static/vendor/webawesome/components/carousel-item/carousel-item.js +12 -0
- nocek/web/static/vendor/webawesome/components/carousel-item/carousel-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/checkbox/checkbox.js +34 -0
- nocek/web/static/vendor/webawesome/components/checkbox/checkbox.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/checkbox-group/checkbox-group.js +36 -0
- nocek/web/static/vendor/webawesome/components/checkbox-group/checkbox-group.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/color-picker/color-picker.js +59 -0
- nocek/web/static/vendor/webawesome/components/color-picker/color-picker.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/comparison/comparison.js +30 -0
- nocek/web/static/vendor/webawesome/components/comparison/comparison.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/copy-button/copy-button.js +44 -0
- nocek/web/static/vendor/webawesome/components/copy-button/copy-button.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/details/details.js +36 -0
- nocek/web/static/vendor/webawesome/components/details/details.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/dialog/dialog.js +49 -0
- nocek/web/static/vendor/webawesome/components/dialog/dialog.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/divider/divider.js +14 -0
- nocek/web/static/vendor/webawesome/components/divider/divider.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/drawer/drawer.js +49 -0
- nocek/web/static/vendor/webawesome/components/drawer/drawer.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/dropdown/dropdown.js +46 -0
- nocek/web/static/vendor/webawesome/components/dropdown/dropdown.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/dropdown-item/dropdown-item.js +27 -0
- nocek/web/static/vendor/webawesome/components/dropdown-item/dropdown-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/format-bytes/format-bytes.js +14 -0
- nocek/web/static/vendor/webawesome/components/format-date/format-date.js +14 -0
- nocek/web/static/vendor/webawesome/components/format-number/format-number.js +14 -0
- nocek/web/static/vendor/webawesome/components/icon/icon.js +21 -0
- nocek/web/static/vendor/webawesome/components/icon/icon.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/icon/library.default.js +11 -0
- nocek/web/static/vendor/webawesome/components/icon/library.js +23 -0
- nocek/web/static/vendor/webawesome/components/icon/library.system.js +10 -0
- nocek/web/static/vendor/webawesome/components/include/include.js +16 -0
- nocek/web/static/vendor/webawesome/components/include/include.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/include/request.js +8 -0
- nocek/web/static/vendor/webawesome/components/input/input.js +39 -0
- nocek/web/static/vendor/webawesome/components/input/input.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/intersection-observer/intersection-observer.js +16 -0
- nocek/web/static/vendor/webawesome/components/intersection-observer/intersection-observer.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/known-date/internal/field-order.js +8 -0
- nocek/web/static/vendor/webawesome/components/known-date/internal/partial-date-validator.js +8 -0
- nocek/web/static/vendor/webawesome/components/known-date/internal/parts.js +16 -0
- nocek/web/static/vendor/webawesome/components/known-date/known-date.js +33 -0
- nocek/web/static/vendor/webawesome/components/known-date/known-date.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/markdown/markdown.js +12 -0
- nocek/web/static/vendor/webawesome/components/markdown/markdown.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/mutation-observer/mutation-observer.js +14 -0
- nocek/web/static/vendor/webawesome/components/mutation-observer/mutation-observer.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/number-input/number-input.js +38 -0
- nocek/web/static/vendor/webawesome/components/number-input/number-input.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/option/option.js +26 -0
- nocek/web/static/vendor/webawesome/components/option/option.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/otp-input/otp-input.js +28 -0
- nocek/web/static/vendor/webawesome/components/otp-input/otp-input.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/page/page.js +56 -0
- nocek/web/static/vendor/webawesome/components/page/page.mobile.styles.js +8 -0
- nocek/web/static/vendor/webawesome/components/page/page.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/pagination/pagination.js +34 -0
- nocek/web/static/vendor/webawesome/components/pagination/pagination.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/popover/popover.js +29 -0
- nocek/web/static/vendor/webawesome/components/popover/popover.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/popup/popup.js +18 -0
- nocek/web/static/vendor/webawesome/components/popup/popup.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/progress-bar/progress-bar.js +17 -0
- nocek/web/static/vendor/webawesome/components/progress-bar/progress-bar.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/progress-ring/progress-ring.js +17 -0
- nocek/web/static/vendor/webawesome/components/progress-ring/progress-ring.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/qr-code/qr-code.js +14 -0
- nocek/web/static/vendor/webawesome/components/qr-code/qr-code.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/radio/radio.js +28 -0
- nocek/web/static/vendor/webawesome/components/radio/radio.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/radio-group/radio-group.js +35 -0
- nocek/web/static/vendor/webawesome/components/radio-group/radio-group.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/random-content/random-content.js +17 -0
- nocek/web/static/vendor/webawesome/components/random-content/random-content.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/rating/rating.js +37 -0
- nocek/web/static/vendor/webawesome/components/rating/rating.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/relative-time/relative-time.js +14 -0
- nocek/web/static/vendor/webawesome/components/resize-observer/resize-observer.js +14 -0
- nocek/web/static/vendor/webawesome/components/resize-observer/resize-observer.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/scroller/scroller.js +15 -0
- nocek/web/static/vendor/webawesome/components/scroller/scroller.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/select/select.js +60 -0
- nocek/web/static/vendor/webawesome/components/select/select.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/skeleton/skeleton.js +12 -0
- nocek/web/static/vendor/webawesome/components/skeleton/skeleton.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/slider/slider.js +41 -0
- nocek/web/static/vendor/webawesome/components/slider/slider.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/spinner/spinner.js +15 -0
- nocek/web/static/vendor/webawesome/components/spinner/spinner.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/split-panel/split-panel.js +20 -0
- nocek/web/static/vendor/webawesome/components/split-panel/split-panel.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/step/step.js +33 -0
- nocek/web/static/vendor/webawesome/components/step/step.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/stepper/stepper.js +39 -0
- nocek/web/static/vendor/webawesome/components/stepper/stepper.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/switch/switch.js +28 -0
- nocek/web/static/vendor/webawesome/components/switch/switch.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tab/tab.js +15 -0
- nocek/web/static/vendor/webawesome/components/tab/tab.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tab-group/tab-group.js +47 -0
- nocek/web/static/vendor/webawesome/components/tab-group/tab-group.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tab-panel/tab-panel.js +15 -0
- nocek/web/static/vendor/webawesome/components/tab-panel/tab-panel.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tag/tag.js +41 -0
- nocek/web/static/vendor/webawesome/components/tag/tag.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tag-input/tag-input.js +50 -0
- nocek/web/static/vendor/webawesome/components/tag-input/tag-input.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/textarea/textarea.js +29 -0
- nocek/web/static/vendor/webawesome/components/textarea/textarea.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/time-input/internal/time-segments.js +38 -0
- nocek/web/static/vendor/webawesome/components/time-input/time-input.js +39 -0
- nocek/web/static/vendor/webawesome/components/time-input/time-input.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/toast/toast.js +43 -0
- nocek/web/static/vendor/webawesome/components/toast/toast.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/toast-item/toast-item.js +40 -0
- nocek/web/static/vendor/webawesome/components/toast-item/toast-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tooltip/tooltip.js +29 -0
- nocek/web/static/vendor/webawesome/components/tooltip/tooltip.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tree/tree.js +53 -0
- nocek/web/static/vendor/webawesome/components/tree/tree.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/tree-item/tree-item.js +51 -0
- nocek/web/static/vendor/webawesome/components/tree-item/tree-item.styles.js +10 -0
- nocek/web/static/vendor/webawesome/components/zoomable-frame/zoomable-frame.js +28 -0
- nocek/web/static/vendor/webawesome/components/zoomable-frame/zoomable-frame.styles.js +10 -0
- nocek/web/static/vendor/webawesome/events/accordion-after-collapse.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-after-expand.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-collapse.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-expand.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-item-collapsed.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-item-expanded.js +8 -0
- nocek/web/static/vendor/webawesome/events/accordion-item-trigger.js +8 -0
- nocek/web/static/vendor/webawesome/events/after-collapse.js +8 -0
- nocek/web/static/vendor/webawesome/events/after-expand.js +8 -0
- nocek/web/static/vendor/webawesome/events/after-hide.js +8 -0
- nocek/web/static/vendor/webawesome/events/after-show.js +8 -0
- nocek/web/static/vendor/webawesome/events/before-page-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/before-step-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/cancel.js +8 -0
- nocek/web/static/vendor/webawesome/events/cell-click.js +13 -0
- nocek/web/static/vendor/webawesome/events/cell-context-menu.js +13 -0
- nocek/web/static/vendor/webawesome/events/clear.js +8 -0
- nocek/web/static/vendor/webawesome/events/collapse.js +8 -0
- nocek/web/static/vendor/webawesome/events/column-move.js +13 -0
- nocek/web/static/vendor/webawesome/events/column-pin.js +13 -0
- nocek/web/static/vendor/webawesome/events/column-resize.js +13 -0
- nocek/web/static/vendor/webawesome/events/column-visibility-change.js +13 -0
- nocek/web/static/vendor/webawesome/events/complete.js +8 -0
- nocek/web/static/vendor/webawesome/events/copy.js +8 -0
- nocek/web/static/vendor/webawesome/events/create.js +8 -0
- nocek/web/static/vendor/webawesome/events/data-error.js +13 -0
- nocek/web/static/vendor/webawesome/events/data-request.js +13 -0
- nocek/web/static/vendor/webawesome/events/error.js +8 -0
- nocek/web/static/vendor/webawesome/events/events.js +2 -0
- nocek/web/static/vendor/webawesome/events/expand.js +8 -0
- nocek/web/static/vendor/webawesome/events/filter-change.js +13 -0
- nocek/web/static/vendor/webawesome/events/finish.js +8 -0
- nocek/web/static/vendor/webawesome/events/focus-day.js +13 -0
- nocek/web/static/vendor/webawesome/events/hide.js +8 -0
- nocek/web/static/vendor/webawesome/events/hover.js +8 -0
- nocek/web/static/vendor/webawesome/events/include-error.js +8 -0
- nocek/web/static/vendor/webawesome/events/intersect.js +8 -0
- nocek/web/static/vendor/webawesome/events/invalid.js +8 -0
- nocek/web/static/vendor/webawesome/events/lazy-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/lazy-load.js +8 -0
- nocek/web/static/vendor/webawesome/events/load.js +8 -0
- nocek/web/static/vendor/webawesome/events/mutation.js +8 -0
- nocek/web/static/vendor/webawesome/events/options-error.js +13 -0
- nocek/web/static/vendor/webawesome/events/options-request.js +13 -0
- nocek/web/static/vendor/webawesome/events/page-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/random-content-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/remove.js +8 -0
- nocek/web/static/vendor/webawesome/events/reposition.js +8 -0
- nocek/web/static/vendor/webawesome/events/resize.js +8 -0
- nocek/web/static/vendor/webawesome/events/row-collapse.js +13 -0
- nocek/web/static/vendor/webawesome/events/row-expand.js +13 -0
- nocek/web/static/vendor/webawesome/events/row-select.js +13 -0
- nocek/web/static/vendor/webawesome/events/select.js +8 -0
- nocek/web/static/vendor/webawesome/events/selection-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/show.js +8 -0
- nocek/web/static/vendor/webawesome/events/slide-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/sort-change.js +13 -0
- nocek/web/static/vendor/webawesome/events/start.js +8 -0
- nocek/web/static/vendor/webawesome/events/step-change.js +8 -0
- nocek/web/static/vendor/webawesome/events/tab-hide.js +8 -0
- nocek/web/static/vendor/webawesome/events/tab-show.js +8 -0
- nocek/web/static/vendor/webawesome/events/video-change.js +13 -0
- nocek/web/static/vendor/webawesome/events/view-change.js +13 -0
- nocek/web/static/vendor/webawesome/styles/color/palettes/base.css +44 -0
- nocek/web/static/vendor/webawesome/styles/color/palettes/bright.css +145 -0
- nocek/web/static/vendor/webawesome/styles/color/palettes/default.css +146 -0
- nocek/web/static/vendor/webawesome/styles/color/palettes/shoelace.css +146 -0
- nocek/web/static/vendor/webawesome/styles/color/variants/brand.css +162 -0
- nocek/web/static/vendor/webawesome/styles/color/variants/danger.css +162 -0
- nocek/web/static/vendor/webawesome/styles/color/variants/neutral.css +162 -0
- nocek/web/static/vendor/webawesome/styles/color/variants/success.css +162 -0
- nocek/web/static/vendor/webawesome/styles/color/variants/warning.css +162 -0
- nocek/web/static/vendor/webawesome/styles/color/variants.css +5 -0
- nocek/web/static/vendor/webawesome/styles/component/form-control.styles.ts +43 -0
- nocek/web/static/vendor/webawesome/styles/component/host.styles.ts +18 -0
- nocek/web/static/vendor/webawesome/styles/component/segmented-field.styles.ts +97 -0
- nocek/web/static/vendor/webawesome/styles/component/size.styles.ts +26 -0
- nocek/web/static/vendor/webawesome/styles/component/variants.styles.ts +69 -0
- nocek/web/static/vendor/webawesome/styles/component/visually-hidden.styles.ts +21 -0
- nocek/web/static/vendor/webawesome/styles/layers.css +21 -0
- nocek/web/static/vendor/webawesome/styles/native.css +1442 -0
- nocek/web/static/vendor/webawesome/styles/themes/awesome.css +616 -0
- nocek/web/static/vendor/webawesome/styles/themes/default.css +373 -0
- nocek/web/static/vendor/webawesome/styles/themes/shoelace.css +650 -0
- nocek/web/static/vendor/webawesome/styles/utilities/align-items.css +44 -0
- nocek/web/static/vendor/webawesome/styles/utilities/border-radius.css +20 -0
- nocek/web/static/vendor/webawesome/styles/utilities/flex-wrap.css +16 -0
- nocek/web/static/vendor/webawesome/styles/utilities/fouce.css +17 -0
- nocek/web/static/vendor/webawesome/styles/utilities/gap.css +56 -0
- nocek/web/static/vendor/webawesome/styles/utilities/justify-content.css +32 -0
- nocek/web/static/vendor/webawesome/styles/utilities/layout.css +159 -0
- nocek/web/static/vendor/webawesome/styles/utilities/placeholder.css +9 -0
- nocek/web/static/vendor/webawesome/styles/utilities/prose.css +161 -0
- nocek/web/static/vendor/webawesome/styles/utilities/scroll-lock.css +20 -0
- nocek/web/static/vendor/webawesome/styles/utilities/size.css +21 -0
- nocek/web/static/vendor/webawesome/styles/utilities/text.css +262 -0
- nocek/web/static/vendor/webawesome/styles/utilities/variants.css +63 -0
- nocek/web/static/vendor/webawesome/styles/utilities/visually-hidden.css +24 -0
- nocek/web/static/vendor/webawesome/styles/utilities.css +17 -0
- nocek/web/static/vendor/webawesome/styles/webawesome.css +11 -0
- nocek/web/static/vendor/webawesome/translations/de.js +11 -0
- nocek/web/static/vendor/webawesome/translations/en.js +9 -0
- nocek/web/static/vendor/webawesome/translations/pl.js +241 -0
- nocek/web/static/vendor/webawesome/utilities/animation.js +11 -0
- nocek/web/static/vendor/webawesome/utilities/autoloader.js +15 -0
- nocek/web/static/vendor/webawesome/utilities/base-path.js +18 -0
- nocek/web/static/vendor/webawesome/utilities/clone.js +54 -0
- nocek/web/static/vendor/webawesome/utilities/defined.js +8 -0
- nocek/web/static/vendor/webawesome/utilities/form.js +8 -0
- nocek/web/static/vendor/webawesome/utilities/localize.js +13 -0
- nocek/web/static/vendor/webawesome/utilities/ssr-hydration.js +7 -0
- nocek/web/static/vendor/webawesome/webawesome.js +66 -0
- nocek/web/static/vendor/webawesome/webawesome.loader.js +67 -0
- nocek-0.1.0.dist-info/METADATA +233 -0
- nocek-0.1.0.dist-info/RECORD +710 -0
- nocek-0.1.0.dist-info/WHEEL +4 -0
- nocek-0.1.0.dist-info/entry_points.txt +2 -0
- nocek-0.1.0.dist-info/licenses/LICENSE +75 -0
nocek/__init__.py
ADDED
nocek/__main__.py
ADDED
nocek/cli.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""Command-line entry point.
|
|
2
|
+
|
|
3
|
+
The application is used through the browser; the command line only starts it.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
import socket
|
|
13
|
+
import sys
|
|
14
|
+
import urllib.request
|
|
15
|
+
import webbrowser
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from logging.handlers import RotatingFileHandler
|
|
18
|
+
|
|
19
|
+
from nocek import __version__
|
|
20
|
+
from nocek.storage import settings
|
|
21
|
+
|
|
22
|
+
DEFAULT_PORT = 6475 # B-A-T-S on a phone keypad
|
|
23
|
+
HOST = "127.0.0.1"
|
|
24
|
+
|
|
25
|
+
log = logging.getLogger("nocek")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
29
|
+
parser = argparse.ArgumentParser(
|
|
30
|
+
prog="nocek",
|
|
31
|
+
description="Identify bats from ultrasonic recordings.",
|
|
32
|
+
)
|
|
33
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--port",
|
|
36
|
+
type=int,
|
|
37
|
+
default=DEFAULT_PORT,
|
|
38
|
+
help=f"port to listen on (default {DEFAULT_PORT}; the next free one if taken)",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument("--no-browser", action="store_true", help="do not open a browser")
|
|
41
|
+
commands = parser.add_subparsers(dest="command", metavar="command")
|
|
42
|
+
shortcut = commands.add_parser(
|
|
43
|
+
"shortcut", help="add Nocek to the system's menu, to start it without a terminal"
|
|
44
|
+
)
|
|
45
|
+
shortcut.add_argument("--remove", action="store_true", help="remove the shortcut instead")
|
|
46
|
+
return parser
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
50
|
+
args = build_parser().parse_args(argv)
|
|
51
|
+
if args.command == "shortcut":
|
|
52
|
+
return make_shortcut(remove=args.remove)
|
|
53
|
+
setup_logging()
|
|
54
|
+
if running(args.port):
|
|
55
|
+
# Started again, from a shortcut say: show the one already running.
|
|
56
|
+
log.info("Nocek is already running at http://%s:%d", HOST, args.port)
|
|
57
|
+
if not args.no_browser:
|
|
58
|
+
webbrowser.open(f"http://{HOST}:{args.port}/?token={settings.token()}")
|
|
59
|
+
return 0
|
|
60
|
+
serve(free_port(args.port), open_browser=not args.no_browser)
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def make_shortcut(remove: bool) -> int:
|
|
65
|
+
from nocek import shortcut
|
|
66
|
+
|
|
67
|
+
made = shortcut.for_this_system()
|
|
68
|
+
try:
|
|
69
|
+
if remove:
|
|
70
|
+
print(f"Removed {made.path}" if made.remove() else f"There was no {made.path}")
|
|
71
|
+
else:
|
|
72
|
+
made.create()
|
|
73
|
+
print(f"Nocek can now be started from {made.path}")
|
|
74
|
+
except (OSError, shortcut.ShortcutError) as error:
|
|
75
|
+
print(f"Could not {'remove' if remove else 'make'} the shortcut: {error}", file=sys.stderr)
|
|
76
|
+
return 1
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def running(port: int) -> bool:
|
|
81
|
+
"""Whether Nocek already answers on this port."""
|
|
82
|
+
try:
|
|
83
|
+
with urllib.request.urlopen(f"http://{HOST}:{port}/api/health", timeout=1) as response:
|
|
84
|
+
return "version" in json.loads(response.read())
|
|
85
|
+
except (OSError, ValueError):
|
|
86
|
+
return False
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def serve(port: int, open_browser: bool) -> None:
|
|
90
|
+
import uvicorn
|
|
91
|
+
|
|
92
|
+
from nocek.web.app import create_app
|
|
93
|
+
from nocek.web.state import AppState
|
|
94
|
+
|
|
95
|
+
token = settings.token()
|
|
96
|
+
state = AppState(settings.config_dir() / "settings.json", token, port)
|
|
97
|
+
address = f"http://{HOST}:{port}/?token={token}"
|
|
98
|
+
|
|
99
|
+
class Server(uvicorn.Server):
|
|
100
|
+
async def startup(self, sockets: list[socket.socket] | None = None) -> None:
|
|
101
|
+
await super().startup(sockets)
|
|
102
|
+
log.info("Nocek is running at http://%s:%d", HOST, port)
|
|
103
|
+
if open_browser:
|
|
104
|
+
webbrowser.open(address)
|
|
105
|
+
|
|
106
|
+
def stop_server() -> None:
|
|
107
|
+
log.info("Quit from the interface")
|
|
108
|
+
server.should_exit = True
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
app = create_app(state, on_quit=stop_server)
|
|
112
|
+
server = Server(uvicorn.Config(app, host=HOST, port=port, log_config=None))
|
|
113
|
+
server.run()
|
|
114
|
+
finally:
|
|
115
|
+
state.close()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def free_port(wanted: int, attempts: int = 20) -> int:
|
|
119
|
+
"""The wanted port, or the first free one after it.
|
|
120
|
+
|
|
121
|
+
Tested the way the server will bind it: with SO_REUSEADDR, so a port
|
|
122
|
+
still holding connections from a server that just stopped counts as
|
|
123
|
+
free, as it is to the server. (On Windows that option would let a port
|
|
124
|
+
in use be taken over, so there it is left off.)
|
|
125
|
+
"""
|
|
126
|
+
for port in range(wanted, wanted + attempts):
|
|
127
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
128
|
+
if sys.platform != "win32":
|
|
129
|
+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
130
|
+
try:
|
|
131
|
+
s.bind((HOST, port))
|
|
132
|
+
except OSError:
|
|
133
|
+
continue
|
|
134
|
+
return port
|
|
135
|
+
raise SystemExit(f"no free port between {wanted} and {wanted + attempts - 1}")
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class HideToken(logging.Filter):
|
|
139
|
+
"""Keep the access token out of the log: the opening address carries it."""
|
|
140
|
+
|
|
141
|
+
PATTERN = re.compile(r"token=[^&\s\"]+")
|
|
142
|
+
|
|
143
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
144
|
+
record.msg = self.PATTERN.sub("token=…", str(record.msg))
|
|
145
|
+
if record.args:
|
|
146
|
+
record.args = tuple(
|
|
147
|
+
self.PATTERN.sub("token=…", a) if isinstance(a, str) else a
|
|
148
|
+
for a in (record.args if isinstance(record.args, tuple) else (record.args,))
|
|
149
|
+
)
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def setup_logging() -> None:
|
|
154
|
+
folder = settings.log_dir()
|
|
155
|
+
folder.mkdir(parents=True, exist_ok=True)
|
|
156
|
+
file = RotatingFileHandler(folder / "nocek.log", maxBytes=1_000_000, backupCount=3)
|
|
157
|
+
file.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
|
|
158
|
+
handlers: list[logging.Handler] = [file]
|
|
159
|
+
if sys.stderr is not None: # started without a console, as pythonw does, there is none
|
|
160
|
+
console = logging.StreamHandler()
|
|
161
|
+
console.setFormatter(logging.Formatter("%(message)s"))
|
|
162
|
+
handlers.append(console)
|
|
163
|
+
for handler in handlers:
|
|
164
|
+
handler.addFilter(HideToken())
|
|
165
|
+
logging.basicConfig(level=logging.INFO, handlers=handlers, force=True)
|
nocek/core/__init__.py
ADDED
nocek/core/_data.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Data files shipped inside the package."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from importlib import resources
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def load_json(name: str) -> Any:
|
|
11
|
+
"""A JSON file from ``nocek/core/data``."""
|
|
12
|
+
path = resources.files("nocek.core").joinpath("data").joinpath(name)
|
|
13
|
+
return json.loads(path.read_text("utf-8"))
|
nocek/core/audio.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Loading recordings into memory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
10
|
+
import soundfile as sf
|
|
11
|
+
|
|
12
|
+
Samples = npt.NDArray[np.float32]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AudioError(Exception):
|
|
16
|
+
"""A file that cannot be read as audio."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# eq=False: comparing arrays field by field has no single truth value.
|
|
20
|
+
@dataclass(frozen=True, eq=False)
|
|
21
|
+
class Audio:
|
|
22
|
+
"""Mono samples in the range [-1, 1] and the rate they were recorded at."""
|
|
23
|
+
|
|
24
|
+
samples: Samples
|
|
25
|
+
sample_rate: int
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def duration_s(self) -> float:
|
|
29
|
+
return len(self.samples) / self.sample_rate
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def load(path: Path) -> Audio:
|
|
33
|
+
"""Read an audio file, mixing several channels down to one.
|
|
34
|
+
|
|
35
|
+
Any format libsndfile understands is accepted, so recordings from other
|
|
36
|
+
devices load the same way as WAV files from a bat recorder.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
data, sample_rate = sf.read(path, dtype="float32", always_2d=True)
|
|
40
|
+
except (sf.LibsndfileError, OSError) as error:
|
|
41
|
+
raise AudioError(f"{path.name}: {error}") from error
|
|
42
|
+
|
|
43
|
+
samples = data[:, 0] if data.shape[1] == 1 else data.mean(axis=1, dtype=np.float32)
|
|
44
|
+
return Audio(samples=np.ascontiguousarray(samples), sample_rate=int(sample_rate))
|
nocek/core/batdetect.py
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Identify species with BatDetect2, a neural network that finds and labels calls.
|
|
2
|
+
|
|
3
|
+
BatDetect2 (Mac Aodha et al., UCL) boxes every echolocation call in a
|
|
4
|
+
recording and gives each a class. It was trained on British bats, so two
|
|
5
|
+
things are added here. Each class stands for the set of taxa it can mean
|
|
6
|
+
locally (see data/batdetect2_classes.json), and a recording's calls are
|
|
7
|
+
split by frequency into sources, one per animal, each with its own opinion:
|
|
8
|
+
one file often holds more than one bat.
|
|
9
|
+
|
|
10
|
+
The model itself is loaded on first use and only through `Model`; everything
|
|
11
|
+
else works on plain call lists and needs no network to test.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Mapping, Sequence
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from functools import cache
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from types import MappingProxyType
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from nocek.core import taxonomy
|
|
24
|
+
from nocek.core._data import load_json
|
|
25
|
+
from nocek.core.opinion import Abstention, Candidate, Opinion
|
|
26
|
+
from nocek.core.taxonomy import Taxon
|
|
27
|
+
|
|
28
|
+
SOURCE = "batdetect2"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True)
|
|
32
|
+
class Params:
|
|
33
|
+
# Below about 0.3 noise floods in.
|
|
34
|
+
detection_threshold: float = 0.4
|
|
35
|
+
# Sources are found as peaks in a histogram of call centre frequencies.
|
|
36
|
+
cluster_floor_khz: float = 10.0
|
|
37
|
+
cluster_bin_khz: float = 5.0
|
|
38
|
+
# Peaks closer than this cannot be told from one bat's own spread.
|
|
39
|
+
cluster_min_separation_khz: float = 8.0
|
|
40
|
+
cluster_min_calls: int = 3
|
|
41
|
+
cluster_min_share: float = 0.10
|
|
42
|
+
# Calls voting this evenly between labels are one animal the model cannot
|
|
43
|
+
# place, not two animals.
|
|
44
|
+
min_agreement: float = 0.5
|
|
45
|
+
# A species that rarely occurs in a region must score at least this. On
|
|
46
|
+
# clean reference recordings the model's best call scores at worst 0.657,
|
|
47
|
+
# so a rare bat has to look as convincing as a known one.
|
|
48
|
+
rare_min_score: float = 0.65
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
DEFAULT_PARAMS = Params()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class ClassInfo:
|
|
56
|
+
taxa: frozenset[Taxon]
|
|
57
|
+
low_khz: tuple[float, float]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class Call:
|
|
62
|
+
"""One call as the model boxed and labelled it."""
|
|
63
|
+
|
|
64
|
+
start_s: float
|
|
65
|
+
end_s: float
|
|
66
|
+
low_khz: float
|
|
67
|
+
high_khz: float
|
|
68
|
+
model_class: str
|
|
69
|
+
class_prob: float
|
|
70
|
+
det_prob: float
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def centre_khz(self) -> float:
|
|
74
|
+
return (self.low_khz + self.high_khz) / 2
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class Source:
|
|
79
|
+
"""The calls of one animal, as far as frequency can tell, and their opinion."""
|
|
80
|
+
|
|
81
|
+
calls: tuple[Call, ...]
|
|
82
|
+
model_class: str
|
|
83
|
+
centre_khz: float
|
|
84
|
+
# Median low edge of the call boxes, close to the end frequency.
|
|
85
|
+
low_khz: float
|
|
86
|
+
# Distance between the frequency peaks merged into this source.
|
|
87
|
+
spread_khz: float
|
|
88
|
+
agreement: float
|
|
89
|
+
opinion: Opinion
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class Analysis:
|
|
94
|
+
sources: tuple[Source, ...]
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def n_calls(self) -> int:
|
|
98
|
+
return sum(len(s.calls) for s in self.sources)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class BatDetectError(Exception):
|
|
102
|
+
"""The model failed on a recording."""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@cache
|
|
106
|
+
def classes() -> Mapping[str, ClassInfo]:
|
|
107
|
+
"""What each model class stands for."""
|
|
108
|
+
return MappingProxyType(
|
|
109
|
+
{
|
|
110
|
+
name: ClassInfo(
|
|
111
|
+
taxa=frozenset(taxonomy.get(t) for t in info["taxa"]),
|
|
112
|
+
low_khz=(float(info["low_khz"][0]), float(info["low_khz"][1])),
|
|
113
|
+
)
|
|
114
|
+
for name, info in load_json("batdetect2_classes.json")["classes"].items()
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def interpret(calls: Sequence[Call], params: Params = DEFAULT_PARAMS) -> Analysis:
|
|
120
|
+
"""Split calls into sources and give each an opinion."""
|
|
121
|
+
unknown = {c.model_class for c in calls} - classes().keys()
|
|
122
|
+
if unknown:
|
|
123
|
+
raise ValueError(f"model classes missing from batdetect2_classes.json: {sorted(unknown)}")
|
|
124
|
+
|
|
125
|
+
sources = _merge([_summarise(cluster) for cluster in _cluster(calls, params)], params)
|
|
126
|
+
# Named sources first, then the ones with most calls.
|
|
127
|
+
sources.sort(key=lambda s: (s.opinion.abstention is not None, -len(s.calls)))
|
|
128
|
+
return Analysis(sources=tuple(sources))
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class Model:
|
|
132
|
+
"""BatDetect2, loaded on first use."""
|
|
133
|
+
|
|
134
|
+
def __init__(self, params: Params = DEFAULT_PARAMS, device: str | None = None) -> None:
|
|
135
|
+
self.params = params
|
|
136
|
+
self._device_name = device
|
|
137
|
+
self._loaded: tuple[Any, Any, Any] | None = None
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def device(self) -> str:
|
|
141
|
+
return str(self._load()[2])
|
|
142
|
+
|
|
143
|
+
def analyse(self, path: Path) -> Analysis:
|
|
144
|
+
from batdetect2.utils import detector_utils
|
|
145
|
+
|
|
146
|
+
model, config, device = self._load()
|
|
147
|
+
try:
|
|
148
|
+
run = detector_utils.process_file(str(path), model, config, device)
|
|
149
|
+
except Exception as error: # the model raises anything from torch to librosa
|
|
150
|
+
raise BatDetectError(f"{path.name}: {error}") from error
|
|
151
|
+
calls = [
|
|
152
|
+
Call(
|
|
153
|
+
start_s=float(a["start_time"]),
|
|
154
|
+
end_s=float(a["end_time"]),
|
|
155
|
+
low_khz=float(a["low_freq"]) / 1000,
|
|
156
|
+
high_khz=float(a["high_freq"]) / 1000,
|
|
157
|
+
model_class=str(a["class"]),
|
|
158
|
+
class_prob=float(a["class_prob"]),
|
|
159
|
+
det_prob=float(a["det_prob"]),
|
|
160
|
+
)
|
|
161
|
+
for a in run["pred_dict"]["annotation"]
|
|
162
|
+
if a.get("event", "Echolocation") == "Echolocation"
|
|
163
|
+
]
|
|
164
|
+
return interpret(calls, self.params)
|
|
165
|
+
|
|
166
|
+
def _load(self) -> tuple[Any, Any, Any]:
|
|
167
|
+
if self._loaded is None:
|
|
168
|
+
import torch
|
|
169
|
+
from batdetect2.detector import parameters
|
|
170
|
+
from batdetect2.utils import detector_utils
|
|
171
|
+
|
|
172
|
+
device = torch.device(self._device_name or _best_device())
|
|
173
|
+
model, model_params = detector_utils.load_model(
|
|
174
|
+
parameters.DEFAULT_MODEL_PATH, device=device
|
|
175
|
+
)
|
|
176
|
+
config = {
|
|
177
|
+
**parameters.DEFAULT_PROCESSING_CONFIGURATIONS,
|
|
178
|
+
**model_params,
|
|
179
|
+
"detection_threshold": self.params.detection_threshold,
|
|
180
|
+
"max_duration": None,
|
|
181
|
+
}
|
|
182
|
+
self._loaded = (model, config, device)
|
|
183
|
+
return self._loaded
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _best_device() -> str:
|
|
187
|
+
import torch
|
|
188
|
+
|
|
189
|
+
if torch.cuda.is_available():
|
|
190
|
+
return "cuda"
|
|
191
|
+
# Apple GPUs: about four times faster than the CPU, with the same calls.
|
|
192
|
+
if torch.backends.mps.is_available():
|
|
193
|
+
return "mps"
|
|
194
|
+
return "cpu"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _cluster(calls: Sequence[Call], params: Params) -> list[list[Call]]:
|
|
198
|
+
"""Split calls into one group per animal, by centre frequency.
|
|
199
|
+
|
|
200
|
+
Labels alone do not separate animals: the model jitters between
|
|
201
|
+
neighbouring classes on a single bat. Frequency does, because jitter does
|
|
202
|
+
not move a call and a second animal does.
|
|
203
|
+
"""
|
|
204
|
+
if not calls:
|
|
205
|
+
return []
|
|
206
|
+
n = len(calls)
|
|
207
|
+
counts: dict[int, int] = {}
|
|
208
|
+
for c in calls:
|
|
209
|
+
b = _bin(c.centre_khz, params)
|
|
210
|
+
counts[b] = counts.get(b, 0) + 1
|
|
211
|
+
|
|
212
|
+
peaks = [
|
|
213
|
+
(count, params.cluster_floor_khz + (b + 0.5) * params.cluster_bin_khz)
|
|
214
|
+
for b, count in counts.items()
|
|
215
|
+
if count >= params.cluster_min_calls
|
|
216
|
+
and count / n >= params.cluster_min_share
|
|
217
|
+
and count >= counts.get(b - 1, 0)
|
|
218
|
+
and count >= counts.get(b + 1, 0)
|
|
219
|
+
]
|
|
220
|
+
centres: list[float] = []
|
|
221
|
+
for _, centre in sorted(peaks, reverse=True):
|
|
222
|
+
if all(abs(centre - k) >= params.cluster_min_separation_khz for k in centres):
|
|
223
|
+
centres.append(centre)
|
|
224
|
+
if not centres:
|
|
225
|
+
centres = [_upper_median([c.centre_khz for c in calls])]
|
|
226
|
+
|
|
227
|
+
groups: list[list[Call]] = [[] for _ in centres]
|
|
228
|
+
for c in calls:
|
|
229
|
+
nearest = min(range(len(centres)), key=lambda k: abs(c.centre_khz - centres[k]))
|
|
230
|
+
groups[nearest].append(c)
|
|
231
|
+
return [g for g in groups if g]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _bin(khz: float, params: Params) -> int:
|
|
235
|
+
return int((khz - params.cluster_floor_khz) // params.cluster_bin_khz)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
@dataclass
|
|
239
|
+
class _Summary:
|
|
240
|
+
calls: list[Call]
|
|
241
|
+
taxa: frozenset[Taxon]
|
|
242
|
+
model_class: str
|
|
243
|
+
centres_khz: list[float]
|
|
244
|
+
low_khz: float
|
|
245
|
+
max_prob: float
|
|
246
|
+
agreement: float
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _summarise(calls: list[Call]) -> _Summary:
|
|
250
|
+
"""The label one cluster votes for, weighted by class probability."""
|
|
251
|
+
votes: dict[frozenset[Taxon], float] = {}
|
|
252
|
+
for c in calls:
|
|
253
|
+
taxa = classes()[c.model_class].taxa
|
|
254
|
+
votes[taxa] = votes.get(taxa, 0.0) + c.class_prob
|
|
255
|
+
best = max(votes, key=lambda t: votes[t])
|
|
256
|
+
in_best = [c for c in calls if classes()[c.model_class].taxa == best]
|
|
257
|
+
by_class: dict[str, float] = {}
|
|
258
|
+
for c in in_best:
|
|
259
|
+
by_class[c.model_class] = by_class.get(c.model_class, 0.0) + c.class_prob
|
|
260
|
+
return _Summary(
|
|
261
|
+
calls=calls,
|
|
262
|
+
taxa=best,
|
|
263
|
+
model_class=max(by_class, key=lambda k: by_class[k]),
|
|
264
|
+
centres_khz=[_upper_median([c.centre_khz for c in calls])],
|
|
265
|
+
low_khz=_upper_median([c.low_khz for c in calls]),
|
|
266
|
+
max_prob=max(c.class_prob for c in calls),
|
|
267
|
+
agreement=votes[best] / sum(votes.values()),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _merge(summaries: list[_Summary], params: Params) -> list[Source]:
|
|
272
|
+
"""Clusters with the same label are one source: two individuals, or one bat
|
|
273
|
+
boxed inconsistently, and the data cannot tell which."""
|
|
274
|
+
merged: dict[frozenset[Taxon], _Summary] = {}
|
|
275
|
+
for s in summaries:
|
|
276
|
+
m = merged.get(s.taxa)
|
|
277
|
+
if m is None:
|
|
278
|
+
merged[s.taxa] = s
|
|
279
|
+
continue
|
|
280
|
+
n_m, n_s = len(m.calls), len(s.calls)
|
|
281
|
+
m.agreement = (m.agreement * n_m + s.agreement * n_s) / (n_m + n_s)
|
|
282
|
+
m.low_khz = (m.low_khz * n_m + s.low_khz * n_s) / (n_m + n_s)
|
|
283
|
+
m.max_prob = max(m.max_prob, s.max_prob)
|
|
284
|
+
m.centres_khz += s.centres_khz
|
|
285
|
+
m.calls = m.calls + s.calls
|
|
286
|
+
return [_source(s, params) for s in merged.values()]
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _source(s: _Summary, params: Params) -> Source:
|
|
290
|
+
low, high = classes()[s.model_class].low_khz
|
|
291
|
+
declined = None
|
|
292
|
+
if not low <= s.low_khz <= high:
|
|
293
|
+
declined = Abstention.OUT_OF_BAND
|
|
294
|
+
elif s.agreement < params.min_agreement:
|
|
295
|
+
declined = Abstention.INCONCLUSIVE
|
|
296
|
+
candidate = Candidate(
|
|
297
|
+
taxa=s.taxa,
|
|
298
|
+
score=s.max_prob,
|
|
299
|
+
evidence={"agreement": s.agreement, "low_khz": s.low_khz},
|
|
300
|
+
)
|
|
301
|
+
return Source(
|
|
302
|
+
calls=tuple(sorted(s.calls, key=lambda c: c.start_s)),
|
|
303
|
+
model_class=s.model_class,
|
|
304
|
+
centre_khz=s.centres_khz[0],
|
|
305
|
+
low_khz=s.low_khz,
|
|
306
|
+
spread_khz=max(s.centres_khz) - min(s.centres_khz),
|
|
307
|
+
agreement=s.agreement,
|
|
308
|
+
opinion=Opinion(
|
|
309
|
+
source=SOURCE,
|
|
310
|
+
ranking=(candidate,),
|
|
311
|
+
min_score=0.0,
|
|
312
|
+
shortlist_ratio=1.0,
|
|
313
|
+
declined=declined,
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _upper_median(values: list[float]) -> float:
|
|
319
|
+
"""The middle value, or the upper of the two middle ones."""
|
|
320
|
+
return sorted(values)[len(values) // 2]
|