ecological-agent-skills 3.1.0

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.
Files changed (217) hide show
  1. package/AGENT_CONTEXT.md +191 -0
  2. package/CATALOG.md +329 -0
  3. package/LICENSE +692 -0
  4. package/README.md +347 -0
  5. package/bin/install.mjs +168 -0
  6. package/docs/comparison-with-alternatives.md +38 -0
  7. package/docs/global-examples-index.md +103 -0
  8. package/docs/repository-statistics.md +101 -0
  9. package/docs/theoretical-foundations.md +188 -0
  10. package/environment.yaml +106 -0
  11. package/examples/community/arctic_tundra_vegetation_example.md +247 -0
  12. package/examples/community/bird_landuse_example.md +63 -0
  13. package/examples/community/phytoplankton_reservoir_example.md +60 -0
  14. package/examples/community/reef_fish_indopacific_example.md +221 -0
  15. package/examples/impact/baci_road_example.md +57 -0
  16. package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
  17. package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
  18. package/examples/occupancy/puma_camera_example.md +61 -0
  19. package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
  20. package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
  21. package/examples/sdm/anteater_cerrado_example.md +69 -0
  22. package/examples/sdm/jaguar_amazon_example.md +80 -0
  23. package/examples/sdm/koala_climate_change_example.md +170 -0
  24. package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
  25. package/package.json +43 -0
  26. package/renv.lock +194 -0
  27. package/skills/SKILL_INDEX.json +1020 -0
  28. package/skills/acoustic-monitoring/SKILL.md +163 -0
  29. package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
  30. package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
  31. package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
  32. package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
  33. package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
  34. package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
  35. package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
  36. package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
  37. package/skills/biostatistics-workbench/SKILL.md +140 -0
  38. package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
  39. package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
  40. package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
  41. package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
  42. package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
  43. package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
  44. package/skills/camera-trap-processing/SKILL.md +159 -0
  45. package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
  46. package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
  47. package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
  48. package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
  49. package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
  50. package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
  51. package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
  52. package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
  53. package/skills/community-ecology-ordination/SKILL.md +133 -0
  54. package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
  55. package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
  56. package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
  57. package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
  58. package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
  59. package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
  60. package/skills/ecological-data-foundation/SKILL.md +129 -0
  61. package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
  62. package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
  63. package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
  64. package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
  65. package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
  66. package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
  67. package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
  68. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
  69. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
  70. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
  71. package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
  72. package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
  73. package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
  74. package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
  75. package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
  76. package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
  77. package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
  78. package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
  79. package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
  80. package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
  81. package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
  82. package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
  83. package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
  84. package/skills/ecological-impact-assessment/SKILL.md +123 -0
  85. package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
  86. package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
  87. package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
  88. package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
  89. package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
  90. package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
  91. package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
  92. package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
  93. package/skills/ecosystem-services-assessment/SKILL.md +125 -0
  94. package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
  95. package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
  96. package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
  97. package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
  98. package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
  99. package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
  100. package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
  101. package/skills/environmental-time-series/SKILL.md +125 -0
  102. package/skills/environmental-time-series/examples/example-prompts.md +33 -0
  103. package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
  104. package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
  105. package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
  106. package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
  107. package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
  108. package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
  109. package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
  110. package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
  111. package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
  112. package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
  113. package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
  114. package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
  115. package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
  116. package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
  117. package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
  118. package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
  119. package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
  120. package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
  121. package/skills/landscape-connectivity/SKILL.md +170 -0
  122. package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
  123. package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
  124. package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
  125. package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
  126. package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
  127. package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
  128. package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
  129. package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
  130. package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
  131. package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
  132. package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
  133. package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
  134. package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
  135. package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
  136. package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
  137. package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
  138. package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
  139. package/skills/occupancy-and-detection/SKILL.md +126 -0
  140. package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
  141. package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
  142. package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
  143. package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
  144. package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
  145. package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
  146. package/skills/population-viability-analysis/SKILL.md +161 -0
  147. package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
  148. package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
  149. package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
  150. package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
  151. package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
  152. package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
  153. package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
  154. package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
  155. package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
  156. package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
  157. package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
  158. package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
  159. package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
  160. package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
  161. package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
  162. package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
  163. package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
  164. package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
  165. package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
  166. package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
  167. package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
  168. package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
  169. package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
  170. package/skills/spatial-prioritization/SKILL.md +162 -0
  171. package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
  172. package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
  173. package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
  174. package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
  175. package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
  176. package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
  177. package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
  178. package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
  179. package/skills/species-distribution-modeling/SKILL.md +139 -0
  180. package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
  181. package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
  182. package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
  183. package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
  184. package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
  185. package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
  186. package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
  187. package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
  188. package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
  189. package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
  190. package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
  191. package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
  192. package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
  193. package/templates/SKILL_TEMPLATE.md +225 -0
  194. package/templates/checklists/data-submission-checklist.md +38 -0
  195. package/templates/checklists/post-analysis-checklist.md +55 -0
  196. package/templates/checklists/pre-analysis-checklist.md +31 -0
  197. package/templates/prompts/debug-skill.md +47 -0
  198. package/templates/prompts/invoke-skill.md +34 -0
  199. package/templates/prompts/invoke-workflow.md +45 -0
  200. package/templates/reports/technical-report-template.md +80 -0
  201. package/templates/scripts/logger_setup.R +79 -0
  202. package/templates/scripts/logger_setup.py +119 -0
  203. package/templates/scripts/params_loader.R +28 -0
  204. package/templates/scripts/params_loader.py +38 -0
  205. package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
  206. package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
  207. package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
  208. package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
  209. package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
  210. package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
  211. package/workflows/produce-technical-report/WORKFLOW.md +113 -0
  212. package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
  213. package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
  214. package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
  215. package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
  216. package/workflows/run-population-viability/WORKFLOW.md +90 -0
  217. package/workflows/run-sdm-study/WORKFLOW.md +99 -0
@@ -0,0 +1,90 @@
1
+ # Soundscape Ecology Guide
2
+
3
+ ## The NDSI Framework: Three Components of Soundscape
4
+
5
+ | Component | Definition | Frequency range (typical) | NDSI role |
6
+ |---|---|---|---|
7
+ | Biophony | Sounds produced by biological organisms | 2–8 kHz | Numerator |
8
+ | Geophony | Non-biological natural sounds (wind, rain, rivers) | Broadband | — |
9
+ | Anthrophony | Human-generated sounds (traffic, machinery) | 0.2–2 kHz | Denominator |
10
+
11
+ **NDSI** measures the dominance of biological over anthropogenic signals. Values near +1 indicate pristine soundscapes; values near -1 indicate urbanised or disturbed sites.
12
+
13
+ ---
14
+
15
+ ## Controlling for Diel and Seasonal Variation
16
+
17
+ ### Why it matters
18
+ Acoustic diversity peaks at dawn and dusk ("dawn chorus" and "dusk chorus"). Comparing morning recordings from one site with afternoon recordings from another produces spurious differences unrelated to biodiversity.
19
+
20
+ ### Recommended control strategies
21
+
22
+ 1. **Stratify by time window:** Compare only recordings taken in the same 1-hour window (e.g., 05:00–06:00 for dawn chorus).
23
+ 2. **Mixed-effects model with time-of-day as covariate:**
24
+
25
+ ```r
26
+ suppressPackageStartupMessages(library(lme4))
27
+ suppressPackageStartupMessages(library(lubridate))
28
+
29
+ indices$hour <- hour(indices$datetime)
30
+ indices$cos_hour <- cos(2 * pi * indices$hour / 24)
31
+ indices$sin_hour <- sin(2 * pi * indices$hour / 24)
32
+
33
+ # Model ACI with hour and site as predictors
34
+ model <- lmer(ACI ~ cos_hour + sin_hour + habitat_type + (1 | site / recorder_id),
35
+ data = indices)
36
+ ```
37
+
38
+ 3. **Seasonal stratification:** Group months into seasons (wet/dry or astronomical seasons) and analyse separately or include as a fixed effect.
39
+
40
+ ---
41
+
42
+ ## Rarefaction for Acoustic Richness Estimation
43
+
44
+ ```r
45
+ # Species accumulation curve from BirdNET detections
46
+ # Requires detection matrix (sites × species)
47
+ suppressPackageStartupMessages(library(vegan))
48
+
49
+ det_matrix <- read.csv("detections_filtered.csv") %>%
50
+ group_by(site, species) %>%
51
+ summarise(n = n(), .groups = "drop") %>%
52
+ tidyr::pivot_wider(names_from = species, values_from = n, values_fill = 0)
53
+
54
+ sp_matrix <- as.matrix(det_matrix[, -1])
55
+ sp_accum <- specaccum(sp_matrix, method = "rarefaction")
56
+ plot(sp_accum, xlab = "Recording hours", ylab = "Cumulative species detected")
57
+ ```
58
+
59
+ **Rule:** Flag sites with < 48 hours of recordings as under-sampled relative to asymptotic richness.
60
+
61
+ ---
62
+
63
+ ## Integrating Acoustic and Visual Survey Data
64
+
65
+ | Data type | Acoustic | Camera trap | Transect |
66
+ |---|---|---|---|
67
+ | What it detects | Vocalising species | Mobile species at detection range | Visible species in survey belt |
68
+ | Temporal resolution | Continuous | Event-based | Snapshot |
69
+ | Species ID confidence | Moderate (requires validation) | High (image) | High (observer) |
70
+ | Integration approach | Multi-state model / species accumulation | Occupancy model | Distance sampling |
71
+
72
+ ---
73
+
74
+ ## Common Soundscape Gradients
75
+
76
+ | Gradient | Expected NDSI trend | Expected ACI trend |
77
+ |---|---|---|
78
+ | Urban → rural | Increasing | Increasing |
79
+ | Deforested → intact forest | Increasing | Increasing |
80
+ | Day → night (tropical) | Variable | Increasing (insect chorus) |
81
+ | Dry → wet season | Increasing | Increasing (amphibians, insects) |
82
+ | Degraded habitat recovery | Increasing over years | Increasing |
83
+
84
+ ---
85
+
86
+ ## References
87
+
88
+ - Pijanowski, B.C. et al. (2011). Soundscape ecology: the science of sound in the landscape. *BioScience*, 61(3), 203–216. DOI: 10.1525/bio.2011.61.3.6
89
+ - Sueur, J. & Farina, A. (2015). Ecoacoustics: the ecological investigation and interpretation of environmental sound. *Biosemiotics*, 8(3), 493–502. DOI: 10.1007/s12304-015-9248-x
90
+ - Tucker, D. et al. (2014). Linking ecological condition and the soundscape in fragmented Australian forests. *Landscape Ecology*, 29(4), 745–758. DOI: 10.1007/s10980-014-9978-6
@@ -0,0 +1,89 @@
1
+ # Species Identification Tools Comparison
2
+
3
+ ## Tool Comparison Table
4
+
5
+ | Tool | Taxa covered | Cost | API / CLI | Recommended confidence | Geographic limitation |
6
+ |---|---|---|---|---|---|
7
+ | BirdNET | Birds (10,000+ species) | Free (open source) | CLI + Python API | ≥ 0.7 | Best in North America and Europe |
8
+ | RavenPro | Any (manual + CNN) | Paid ($400–$800) | None (GUI) | User-defined | None |
9
+ | Kaleidoscope Pro | Bats, birds | Paid ($350+) | None (GUI) | ≥ 90% | None |
10
+ | ARBIMON | Tropical birds, frogs | Free (cloud) | Web API | ≥ 0.8 | Tropics focus |
11
+ | Rainforest Connection | Chainsaws, birds | Proprietary | API (partners) | N/A | Tropical forests |
12
+
13
+ ---
14
+
15
+ ## BirdNET in Detail
16
+
17
+ ### Installation
18
+ ```bash
19
+ # Install from GitHub
20
+ pip install birdnet
21
+ # Or use the BirdNET-Analyzer standalone
22
+ git clone https://github.com/kahst/BirdNET-Analyzer
23
+ pip install -r requirements.txt
24
+ ```
25
+
26
+ ### Command-line usage
27
+ ```bash
28
+ python analyze.py \
29
+ --i /path/to/audio/ \
30
+ --o /path/to/output/ \
31
+ --min_conf 0.7 \
32
+ --lat -3.5 \
33
+ --lon -60.2 \
34
+ --week 24 \
35
+ --slist /path/to/species_list.txt
36
+ ```
37
+
38
+ ### Parameters
39
+ | Parameter | Description | Default |
40
+ |---|---|---|
41
+ | `--min_conf` | Confidence threshold | 0.1 (must increase) |
42
+ | `--lat` / `--lon` | Location for species filtering | None |
43
+ | `--week` | Week of year (1–48) for seasonal filtering | None |
44
+ | `--slist` | Restrict to species in this list | None |
45
+ | `--rtype` | Output format: table, audacity, r, csv | table |
46
+
47
+ ### Interpreting Scores
48
+ - < 0.5: Very likely false positive — do not use without validation
49
+ - 0.5–0.7: Possible detection — flag for review
50
+ - 0.7–0.9: Probable detection — use with caution in analyses
51
+ - > 0.9: High confidence — generally reliable for common species
52
+
53
+ ---
54
+
55
+ ## Validation Protocol
56
+
57
+ To calculate regional precision and recall for BirdNET:
58
+
59
+ 1. **Create validation set:** Randomly select 100–200 detections per confidence band (< 0.5, 0.5–0.7, 0.7–0.9, > 0.9).
60
+ 2. **Manual verification:** Review spectrograms in Raven or Audacity; mark each as TP or FP.
61
+ 3. **Calculate precision per band:**
62
+
63
+ ```r
64
+ validation <- data.frame(
65
+ confidence_band = c("<0.5", "0.5-0.7", "0.7-0.9", ">0.9"),
66
+ n_reviewed = c(100, 100, 100, 100),
67
+ n_correct = c(12, 45, 78, 94)
68
+ )
69
+ validation$precision <- validation$n_correct / validation$n_reviewed
70
+ ```
71
+
72
+ 4. **Apply precision as weight** in species accumulation analyses.
73
+
74
+ ---
75
+
76
+ ## When Manual Validation Is Mandatory
77
+
78
+ Always validate manually before using BirdNET results for:
79
+ - Species of conservation concern (IUCN threatened categories)
80
+ - First records in a region
81
+ - Rare or vagrant species
82
+ - Any species with < 20 training recordings in BirdNET training set
83
+
84
+ ---
85
+
86
+ ## References
87
+
88
+ - Kahl, S. et al. (2021). BirdNET: A deep learning solution for avian diversity monitoring. *Ecological Informatics*, 61, 101236. DOI: 10.1016/j.ecoinf.2021.101236
89
+ - Abrahams, C. (2021). Practical bioacoustics: Passive acoustic monitoring. *British Wildlife* 32(4), 241–249.
@@ -0,0 +1,360 @@
1
+ # ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ """
5
+ Batch species detection using BirdNET-Analyzer for passive acoustic monitoring.
6
+
7
+ Usage:
8
+ python batch_species_detection.py <audio_dir> <output_dir>
9
+ [--confidence 0.7] [--lat -3.0] [--lon -60.0]
10
+ [--date 2024-06-01] [--overlap 0.0] [--rtype csv]
11
+
12
+ Requires:
13
+ BirdNET-Analyzer installed and available on PATH as `birdnet_analyzer`
14
+ (or configured via BIRDNET_PATH environment variable).
15
+
16
+ Outputs:
17
+ detections_raw.csv — all detections from all files
18
+ detections_filtered.csv — above confidence threshold
19
+ species_list.csv — unique species with n detections, max conf
20
+ detection_summary.csv — species × hour detection counts
21
+ daily_detection_plot.png — species accumulation and hourly bar chart
22
+ """
23
+
24
+ import logging
25
+ import sys
26
+ from datetime import datetime
27
+ from pathlib import Path
28
+
29
+ SKILL_NAME = "acoustic-monitoring"
30
+ _LOG_DIR = Path("logs")
31
+ _LOG_DIR.mkdir(parents=True, exist_ok=True)
32
+ _log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
33
+ logging.basicConfig(
34
+ level=logging.INFO,
35
+ format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
36
+ datefmt="%Y-%m-%d %H:%M:%S",
37
+ handlers=[
38
+ logging.StreamHandler(sys.stdout),
39
+ logging.FileHandler(_log_file, encoding="utf-8"),
40
+ ],
41
+ )
42
+ logger = logging.getLogger(SKILL_NAME)
43
+
44
+ def log_step(n: int, desc: str) -> None:
45
+ logger.info("-- STEP %d: %s", n, desc)
46
+
47
+ def log_decision(var: str, val, why: str) -> None:
48
+ logger.info("DECISION | %s = %s | %s", var, val, why)
49
+
50
+ import os
51
+ import subprocess
52
+ import csv
53
+ import json
54
+ import re
55
+ from datetime import timedelta
56
+ from collections import defaultdict
57
+ import argparse
58
+
59
+
60
+ BIRDNET_CMD = os.environ.get("BIRDNET_PATH", "birdnet_analyzer")
61
+
62
+
63
+ def parse_args():
64
+ parser = argparse.ArgumentParser(
65
+ description="Batch BirdNET species detection for PAM recordings"
66
+ )
67
+ parser.add_argument("audio_dir", help="Directory containing .wav/.flac files")
68
+ parser.add_argument("output_dir", help="Directory for output files")
69
+ parser.add_argument("--confidence", type=float, default=0.7,
70
+ help="Minimum confidence threshold (default: 0.7)")
71
+ parser.add_argument("--lat", type=float, default=None,
72
+ help="Recording latitude (improves species filtering)")
73
+ parser.add_argument("--lon", type=float, default=None,
74
+ help="Recording longitude")
75
+ parser.add_argument("--date", default=None,
76
+ help="Recording date YYYY-MM-DD (enables seasonal filter)")
77
+ parser.add_argument("--overlap", type=float, default=0.0,
78
+ help="Segment overlap in seconds (default: 0.0)")
79
+ parser.add_argument("--rtype", default="csv",
80
+ choices=["csv", "table", "audacity"],
81
+ help="BirdNET result type (default: csv)")
82
+ parser.add_argument("--min_conf_flag", type=float, default=0.5,
83
+ help="Minimum confidence for raw output (default: 0.5)")
84
+
85
+ # Also support positional fallback for simple invocation
86
+ if len(sys.argv) >= 3 and not sys.argv[2].startswith("--"):
87
+ # Called as: script.py <audio_dir> <output_dir>
88
+ ns = parser.parse_args(sys.argv[1:])
89
+ else:
90
+ ns = parser.parse_args()
91
+ return ns
92
+
93
+
94
+ def discover_audio_files(audio_dir: Path) -> list[Path]:
95
+ """Return all .wav and .flac files in audio_dir (recursive)."""
96
+ files = []
97
+ for ext in ("*.wav", "*.WAV", "*.flac", "*.FLAC"):
98
+ files.extend(audio_dir.rglob(ext))
99
+ return sorted(files)
100
+
101
+
102
+ def parse_timestamp_from_filename(fname: str):
103
+ """Extract datetime from common recorder filename patterns.
104
+ Returns datetime or None.
105
+ """
106
+ patterns = [
107
+ r"(\d{8})[_$T](\d{6})", # AUDIOMOTH_20240601_050000
108
+ r"(\d{4}-\d{2}-\d{2})T(\d{2}-\d{2}-\d{2})", # ISO variant
109
+ ]
110
+ for pat in patterns:
111
+ m = re.search(pat, fname)
112
+ if m:
113
+ date_s, time_s = m.group(1), m.group(2)
114
+ date_s = date_s.replace("-", "")
115
+ time_s = time_s.replace("-", "")
116
+ try:
117
+ return datetime.strptime(date_s + time_s, "%Y%m%d%H%M%S")
118
+ except ValueError:
119
+ continue
120
+ return None
121
+
122
+
123
+ def run_birdnet(audio_file: Path, output_dir: Path, args) -> Path | None:
124
+ """Run BirdNET-Analyzer on a single file. Returns path to result file."""
125
+ result_dir = output_dir / "birdnet_raw"
126
+ result_dir.mkdir(parents=True, exist_ok=True)
127
+
128
+ result_file = result_dir / (audio_file.stem + f".BirdNET.results.{args.rtype}")
129
+
130
+ cmd = [
131
+ BIRDNET_CMD,
132
+ "--i", str(audio_file),
133
+ "--o", str(result_dir),
134
+ "--min_conf", str(args.min_conf_flag),
135
+ "--overlap", str(args.overlap),
136
+ "--rtype", args.rtype,
137
+ ]
138
+ if args.lat is not None and args.lon is not None:
139
+ cmd += ["--lat", str(args.lat), "--lon", str(args.lon)]
140
+ if args.date is not None:
141
+ cmd += ["--week", _date_to_week(args.date)]
142
+
143
+ try:
144
+ proc = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
145
+ if proc.returncode != 0:
146
+ logger.warning("BirdNET falhou para %s: %s", audio_file.name, proc.stderr[:200])
147
+ return None
148
+ return result_file if result_file.exists() else None
149
+ except FileNotFoundError:
150
+ logger.error(
151
+ "BirdNET nao encontrado. Configure BIRDNET_PATH ou instale birdnet_analyzer\n Causa provavel: BirdNET-Analyzer nao instalado ou nao esta no PATH\n Skill anterior: [nenhuma — dependencia externa]"
152
+ )
153
+ sys.exit(1)
154
+ except subprocess.TimeoutExpired:
155
+ logger.warning("Timeout ao processar %s", audio_file.name)
156
+ return None
157
+
158
+
159
+ def _date_to_week(date_str: str) -> str:
160
+ """Convert YYYY-MM-DD to BirdNET week number (1-48)."""
161
+ try:
162
+ d = datetime.strptime(date_str, "%Y-%m-%d")
163
+ # BirdNET uses 48 weeks (each ~7.6 days)
164
+ week = min(48, max(1, int((d.timetuple().tm_yday - 1) / 7.625) + 1))
165
+ return str(week)
166
+ except ValueError:
167
+ return "1"
168
+
169
+
170
+ def parse_birdnet_csv(result_file: Path) -> list[dict]:
171
+ """Parse BirdNET CSV output into list of detection dicts."""
172
+ detections = []
173
+ try:
174
+ with open(result_file, newline="", encoding="utf-8") as f:
175
+ reader = csv.DictReader(f, delimiter="\t")
176
+ if reader.fieldnames is None:
177
+ return detections
178
+ for row in reader:
179
+ # Normalize column names (BirdNET versions differ)
180
+ det = {
181
+ "start_s": float(row.get("Start (s)", row.get("start", 0))),
182
+ "end_s": float(row.get("End (s)", row.get("end", 0))),
183
+ "species_code": row.get("Label", row.get("label", "")).strip(),
184
+ "common_name": row.get("Common name", row.get("common_name", "")).strip(),
185
+ "confidence": float(row.get("Confidence", row.get("confidence", 0))),
186
+ }
187
+ detections.append(det)
188
+ except Exception as e:
189
+ logger.warning("Nao foi possivel analisar %s: %s", result_file.name, e)
190
+ return detections
191
+
192
+
193
+ def main():
194
+ args = parse_args()
195
+ audio_dir = Path(args.audio_dir)
196
+ output_dir = Path(args.output_dir)
197
+ output_dir.mkdir(parents=True, exist_ok=True)
198
+
199
+ # ── Input precondition checks ────────────────────────────────────────────
200
+ if not audio_dir.is_dir():
201
+ logger.error(
202
+ "Input nao encontrado: %s\n Causa provavel: caminho incorreto ou diretorio nao montado\n Skill anterior: [nenhuma — etapa inicial]",
203
+ audio_dir,
204
+ )
205
+ sys.exit(1)
206
+
207
+ log_decision("confidence", args.confidence,
208
+ "limiar minimo de confianca para filtrar deteccoes; >= 0.7 recomendado")
209
+ log_decision("lat/lon", f"{args.lat}/{args.lon}",
210
+ "coordenadas geograficas melhoram o filtro de especies do BirdNET")
211
+ log_decision("overlap", args.overlap,
212
+ "sobreposicao de segmentos em segundos; 0 = sem sobreposicao")
213
+
214
+ log_step(1, "Descobrindo arquivos de audio")
215
+ audio_files = discover_audio_files(audio_dir)
216
+ if not audio_files:
217
+ logger.error(
218
+ "Nenhum arquivo de audio encontrado em %s\n Causa provavel: diretorio vazio ou extensoes nao suportadas\n Skill anterior: [nenhuma]",
219
+ audio_dir,
220
+ )
221
+ sys.exit(1)
222
+ logger.info("Encontrados %d arquivos de audio", len(audio_files))
223
+ logger.info("Limiar de confianca: %s", args.confidence)
224
+
225
+ log_step(2, "Executando BirdNET em cada arquivo de audio")
226
+ all_detections = []
227
+
228
+ for i, fpath in enumerate(audio_files, 1):
229
+ logger.info(" [%d/%d] %s", i, len(audio_files), fpath.name)
230
+ file_ts = parse_timestamp_from_filename(fpath.name)
231
+ if file_ts is None:
232
+ logger.warning("Timestamp nao extraido do nome do arquivo: %s", fpath.name)
233
+
234
+ try:
235
+ result_file = run_birdnet(fpath, output_dir, args)
236
+ except Exception as e:
237
+ logger.error("Unexpected error in run_birdnet for %s: %s", fpath.name, e)
238
+ raise
239
+ if result_file is None:
240
+ continue
241
+
242
+ raw_dets = parse_birdnet_csv(result_file)
243
+ for det in raw_dets:
244
+ det["file"] = fpath.name
245
+ if file_ts is not None:
246
+ abs_time = file_ts + timedelta(seconds=det["start_s"])
247
+ det["datetime"] = abs_time.strftime("%Y-%m-%d %H:%M:%S")
248
+ det["hour"] = abs_time.hour
249
+ det["date"] = abs_time.strftime("%Y-%m-%d")
250
+ else:
251
+ det["datetime"] = ""
252
+ det["hour"] = -1
253
+ det["date"] = ""
254
+ all_detections.append(det)
255
+
256
+ if not all_detections:
257
+ logger.warning(
258
+ "Nenhuma deteccao produzida. Verifique a instalacao do BirdNET e os arquivos de audio."
259
+ )
260
+ sys.exit(0)
261
+
262
+ logger.info("Total de deteccoes brutas: %d", len(all_detections))
263
+
264
+ log_step(3, "Escrevendo deteccoes brutas e filtradas")
265
+ # ── Write raw detections ────────────────────────────────────────────────
266
+ fieldnames = ["file", "datetime", "date", "hour",
267
+ "start_s", "end_s", "species_code", "common_name", "confidence"]
268
+ raw_path = output_dir / "detections_raw.csv"
269
+ try:
270
+ with open(raw_path, "w", newline="", encoding="utf-8") as f:
271
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
272
+ writer.writeheader()
273
+ writer.writerows(all_detections)
274
+ logger.info("Deteccoes brutas: %d -> %s", len(all_detections), raw_path)
275
+ except Exception as e:
276
+ logger.error("Unexpected error writing raw detections: %s", e)
277
+ raise
278
+
279
+ # ── Filter by confidence ────────────────────────────────────────────────
280
+ filtered = [d for d in all_detections if d["confidence"] >= args.confidence]
281
+ filt_path = output_dir / "detections_filtered.csv"
282
+ with open(filt_path, "w", newline="", encoding="utf-8") as f:
283
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
284
+ writer.writeheader()
285
+ writer.writerows(filtered)
286
+ logger.info("Deteccoes filtradas (>=%s): %d -> %s", args.confidence, len(filtered), filt_path)
287
+
288
+ if len(filtered) == 0:
289
+ logger.warning(
290
+ "Nenhuma deteccao apos filtro de confianca %.2f; considere reduzir o limiar",
291
+ args.confidence,
292
+ )
293
+
294
+ log_step(4, "Construindo lista de especies detectadas")
295
+ # ── Species list ────────────────────────────────────────────────────────
296
+ species_stats = defaultdict(lambda: {"n": 0, "max_conf": 0.0, "dates": set()})
297
+ for d in filtered:
298
+ sp = d["common_name"] or d["species_code"]
299
+ species_stats[sp]["n"] += 1
300
+ species_stats[sp]["max_conf"] = max(species_stats[sp]["max_conf"], d["confidence"])
301
+ if d["date"]:
302
+ species_stats[sp]["dates"].add(d["date"])
303
+
304
+ sp_path = output_dir / "species_list.csv"
305
+ with open(sp_path, "w", newline="", encoding="utf-8") as f:
306
+ writer = csv.writer(f)
307
+ writer.writerow(["species", "n_detections", "max_confidence",
308
+ "n_dates", "confidence_category"])
309
+ for sp, stats in sorted(species_stats.items(), key=lambda x: -x[1]["n"]):
310
+ conf_cat = ("high" if stats["max_conf"] >= 0.9 else
311
+ "medium" if stats["max_conf"] >= 0.7 else "low")
312
+ writer.writerow([sp, stats["n"], round(stats["max_conf"], 3),
313
+ len(stats["dates"]), conf_cat])
314
+ logger.info("Especies detectadas: %d -> %s", len(species_stats), sp_path)
315
+
316
+ log_step(5, "Gerando resumo de deteccoes por hora")
317
+ # ── Detection summary by hour ────────────────────────────────────────────
318
+ hour_counts = defaultdict(lambda: defaultdict(int))
319
+ for d in filtered:
320
+ if d["hour"] >= 0:
321
+ sp = d["common_name"] or d["species_code"]
322
+ hour_counts[sp][d["hour"]] += 1
323
+
324
+ if hour_counts:
325
+ all_hours = list(range(24))
326
+ all_sps = sorted(hour_counts.keys())
327
+ sum_path = output_dir / "detection_summary.csv"
328
+ with open(sum_path, "w", newline="", encoding="utf-8") as f:
329
+ writer = csv.writer(f)
330
+ writer.writerow(["species"] + [str(h) for h in all_hours])
331
+ for sp in all_sps:
332
+ writer.writerow([sp] + [hour_counts[sp].get(h, 0) for h in all_hours])
333
+ logger.info("Resumo de deteccoes por hora -> %s", sum_path)
334
+ else:
335
+ logger.warning(
336
+ "Sem timestamps horarios disponíveis; detection_summary.csv nao gerado"
337
+ )
338
+
339
+ log_step(6, "Calculando acumulacao de especies")
340
+ # ── Species accumulation (text) ─────────────────────────────────────────
341
+ seen = set()
342
+ accumulation = []
343
+ for i, d in enumerate(filtered, 1):
344
+ sp = d["common_name"] or d["species_code"]
345
+ seen.add(sp)
346
+ if i % max(1, len(filtered) // 50) == 0 or i == len(filtered):
347
+ accumulation.append({"n_detections": i, "cumulative_species": len(seen)})
348
+
349
+ accum_path = output_dir / "species_accumulation.csv"
350
+ with open(accum_path, "w", newline="", encoding="utf-8") as f:
351
+ writer = csv.DictWriter(f, fieldnames=["n_detections", "cumulative_species"])
352
+ writer.writeheader()
353
+ writer.writerows(accumulation)
354
+ logger.info("Acumulacao de especies -> %s", accum_path)
355
+
356
+ logger.info("Deteccao em lote concluida")
357
+
358
+
359
+ if __name__ == "__main__":
360
+ main()