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.
- package/AGENT_CONTEXT.md +191 -0
- package/CATALOG.md +329 -0
- package/LICENSE +692 -0
- package/README.md +347 -0
- package/bin/install.mjs +168 -0
- package/docs/comparison-with-alternatives.md +38 -0
- package/docs/global-examples-index.md +103 -0
- package/docs/repository-statistics.md +101 -0
- package/docs/theoretical-foundations.md +188 -0
- package/environment.yaml +106 -0
- package/examples/community/arctic_tundra_vegetation_example.md +247 -0
- package/examples/community/bird_landuse_example.md +63 -0
- package/examples/community/phytoplankton_reservoir_example.md +60 -0
- package/examples/community/reef_fish_indopacific_example.md +221 -0
- package/examples/impact/baci_road_example.md +57 -0
- package/examples/impact/ecosystem_services_atlantic_forest.md +83 -0
- package/examples/impact/forest_loss_borneo_timeseries_example.md +225 -0
- package/examples/occupancy/puma_camera_example.md +61 -0
- package/examples/occupancy/snow_leopard_himalayas_example.md +204 -0
- package/examples/reproducible/whittaker_biome_sdm_example.md +406 -0
- package/examples/sdm/anteater_cerrado_example.md +69 -0
- package/examples/sdm/jaguar_amazon_example.md +80 -0
- package/examples/sdm/koala_climate_change_example.md +170 -0
- package/examples/sdm/wolf_recolonization_europe_example.md +193 -0
- package/package.json +43 -0
- package/renv.lock +194 -0
- package/skills/SKILL_INDEX.json +1020 -0
- package/skills/acoustic-monitoring/SKILL.md +163 -0
- package/skills/acoustic-monitoring/examples/example-prompts.md +100 -0
- package/skills/acoustic-monitoring/examples/temperate_forest_birds_example.md +285 -0
- package/skills/acoustic-monitoring/resources/acoustic-indices-reference.md +93 -0
- package/skills/acoustic-monitoring/resources/soundscape-ecology-guide.md +90 -0
- package/skills/acoustic-monitoring/resources/species-id-tools-comparison.md +89 -0
- package/skills/acoustic-monitoring/scripts/batch_species_detection.py +360 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.R +235 -0
- package/skills/acoustic-monitoring/scripts/compute_acoustic_indices.py +374 -0
- package/skills/biostatistics-workbench/SKILL.md +140 -0
- package/skills/biostatistics-workbench/examples/example-prompts.md +39 -0
- package/skills/biostatistics-workbench/resources/effect-size-reference.md +81 -0
- package/skills/biostatistics-workbench/resources/glm-family-link-reference.md +47 -0
- package/skills/biostatistics-workbench/resources/test-selection-guide.md +93 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.R +78 -0
- package/skills/biostatistics-workbench/scripts/glm_pipeline.py +210 -0
- package/skills/camera-trap-processing/SKILL.md +159 -0
- package/skills/camera-trap-processing/examples/example-prompts.md +103 -0
- package/skills/camera-trap-processing/examples/leopard_serengeti_example.md +231 -0
- package/skills/camera-trap-processing/resources/activity-patterns-reference.md +113 -0
- package/skills/camera-trap-processing/resources/camtrapR-workflow-guide.md +130 -0
- package/skills/camera-trap-processing/resources/detection-event-definition-guide.md +89 -0
- package/skills/camera-trap-processing/scripts/estimate_activity.R +169 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.R +179 -0
- package/skills/camera-trap-processing/scripts/process_camtrap_data.py +192 -0
- package/skills/community-ecology-ordination/SKILL.md +133 -0
- package/skills/community-ecology-ordination/examples/example-prompts.md +35 -0
- package/skills/community-ecology-ordination/resources/dissimilarity-metric-guide.md +53 -0
- package/skills/community-ecology-ordination/resources/nmds-interpretation-guide.md +104 -0
- package/skills/community-ecology-ordination/scripts/__pycache__/community_analysis.cpython-311.pyc +0 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.R +143 -0
- package/skills/community-ecology-ordination/scripts/community_analysis.py +231 -0
- package/skills/ecological-data-foundation/SKILL.md +129 -0
- package/skills/ecological-data-foundation/examples/example-prompts.md +40 -0
- package/skills/ecological-data-foundation/resources/coordinate-cleaning-flags.md +66 -0
- package/skills/ecological-data-foundation/resources/darwin-core-glossary.md +91 -0
- package/skills/ecological-data-foundation/resources/data-citation-guide.md +265 -0
- package/skills/ecological-data-foundation/resources/gbif-data-citation-guide.md +193 -0
- package/skills/ecological-data-foundation/resources/qa-checklist.md +83 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/clean_occurrences.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_ebird.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_inat.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_iucn.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/__pycache__/download_from_obis.cpython-311.pyc +0 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.R +230 -0
- package/skills/ecological-data-foundation/scripts/clean_occurrences.py +268 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.R +251 -0
- package/skills/ecological-data-foundation/scripts/download_from_ebird.py +364 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.R +315 -0
- package/skills/ecological-data-foundation/scripts/download_from_gbif.py +407 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.R +238 -0
- package/skills/ecological-data-foundation/scripts/download_from_inat.py +304 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.R +273 -0
- package/skills/ecological-data-foundation/scripts/download_from_iucn.py +344 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.R +248 -0
- package/skills/ecological-data-foundation/scripts/download_from_obis.py +318 -0
- package/skills/ecological-impact-assessment/SKILL.md +123 -0
- package/skills/ecological-impact-assessment/examples/example-prompts.md +32 -0
- package/skills/ecological-impact-assessment/resources/baci-design-guide.md +55 -0
- package/skills/ecological-impact-assessment/resources/fragmentation-metrics-reference.md +86 -0
- package/skills/ecological-impact-assessment/resources/pressure-index-template.md +78 -0
- package/skills/ecological-impact-assessment/resources/study-design-guide.md +168 -0
- package/skills/ecological-impact-assessment/scripts/baci_analysis.R +161 -0
- package/skills/ecological-impact-assessment/scripts/fragmentation_analysis.py +141 -0
- package/skills/ecological-impact-assessment/scripts/power_analysis_baci.R +274 -0
- package/skills/ecosystem-services-assessment/SKILL.md +125 -0
- package/skills/ecosystem-services-assessment/examples/example-prompts.md +24 -0
- package/skills/ecosystem-services-assessment/resources/es-indicator-reference.md +45 -0
- package/skills/ecosystem-services-assessment/resources/invest-parameter-guide.md +86 -0
- package/skills/ecosystem-services-assessment/resources/rusle-coefficients.md +88 -0
- package/skills/ecosystem-services-assessment/scripts/__pycache__/compute_es.cpython-311.pyc +0 -0
- package/skills/ecosystem-services-assessment/scripts/compute_es.py +189 -0
- package/skills/ecosystem-services-assessment/scripts/tradeoff_analysis.R +161 -0
- package/skills/environmental-time-series/SKILL.md +125 -0
- package/skills/environmental-time-series/examples/example-prompts.md +33 -0
- package/skills/environmental-time-series/resources/anomaly-indices-reference.md +88 -0
- package/skills/environmental-time-series/resources/bfast-parameter-guide.md +69 -0
- package/skills/environmental-time-series/scripts/__pycache__/recovery_trajectory.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/__pycache__/trend_analysis.cpython-311.pyc +0 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.R +305 -0
- package/skills/environmental-time-series/scripts/recovery_trajectory.py +178 -0
- package/skills/environmental-time-series/scripts/trend_analysis.R +192 -0
- package/skills/environmental-time-series/scripts/trend_analysis.py +184 -0
- package/skills/geoprocessing-for-ecology/SKILL.md +123 -0
- package/skills/geoprocessing-for-ecology/examples/example-prompts.md +32 -0
- package/skills/geoprocessing-for-ecology/resources/crs-reference.md +62 -0
- package/skills/geoprocessing-for-ecology/resources/global-predictor-sources.md +331 -0
- package/skills/geoprocessing-for-ecology/resources/resampling-methods.md +57 -0
- package/skills/geoprocessing-for-ecology/scripts/__pycache__/download_predictors.cpython-311.pyc +0 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.R +239 -0
- package/skills/geoprocessing-for-ecology/scripts/download_predictors.py +379 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.R +224 -0
- package/skills/geoprocessing-for-ecology/scripts/stack_and_extract.py +172 -0
- package/skills/landscape-connectivity/SKILL.md +170 -0
- package/skills/landscape-connectivity/examples/example-prompts.md +96 -0
- package/skills/landscape-connectivity/examples/jaguar_mesoamerica_corridor_example.md +271 -0
- package/skills/landscape-connectivity/resources/circuitscape-parameter-guide.md +155 -0
- package/skills/landscape-connectivity/resources/graph-theory-for-ecology.md +134 -0
- package/skills/landscape-connectivity/resources/resistance-surface-guide.md +141 -0
- package/skills/landscape-connectivity/scripts/connectivity_analysis.py +387 -0
- package/skills/landscape-connectivity/scripts/connectivity_metrics.R +274 -0
- package/skills/landscape-connectivity/scripts/resistance_surface.R +239 -0
- package/skills/model-validation-and-uncertainty/SKILL.md +131 -0
- package/skills/model-validation-and-uncertainty/examples/example-prompts.md +30 -0
- package/skills/model-validation-and-uncertainty/resources/extrapolation-risk-guide.md +236 -0
- package/skills/model-validation-and-uncertainty/resources/metric-selection-guide.md +52 -0
- package/skills/model-validation-and-uncertainty/resources/threshold-selection-guide.md +64 -0
- package/skills/model-validation-and-uncertainty/scripts/__pycache__/validate_model.cpython-311.pyc +0 -0
- package/skills/model-validation-and-uncertainty/scripts/extrapolation_risk.R +315 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_model.py +226 -0
- package/skills/model-validation-and-uncertainty/scripts/validate_sdm.R +162 -0
- package/skills/occupancy-and-detection/SKILL.md +126 -0
- package/skills/occupancy-and-detection/examples/example-prompts.md +33 -0
- package/skills/occupancy-and-detection/resources/detection-history-format.md +100 -0
- package/skills/occupancy-and-detection/resources/occupancy-study-design.md +47 -0
- package/skills/occupancy-and-detection/scripts/__pycache__/occupancy_analysis.cpython-311.pyc +0 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.R +160 -0
- package/skills/occupancy-and-detection/scripts/occupancy_analysis.py +159 -0
- package/skills/population-viability-analysis/SKILL.md +161 -0
- package/skills/population-viability-analysis/examples/african_elephant_pva_example.md +266 -0
- package/skills/population-viability-analysis/examples/example-prompts.md +95 -0
- package/skills/population-viability-analysis/resources/extinction-risk-thresholds.md +128 -0
- package/skills/population-viability-analysis/resources/matrix-model-guide.md +139 -0
- package/skills/population-viability-analysis/resources/sensitivity-elasticity-reference.md +182 -0
- package/skills/population-viability-analysis/scripts/matrix_pva.R +258 -0
- package/skills/population-viability-analysis/scripts/pva_analysis.py +442 -0
- package/skills/population-viability-analysis/scripts/stochastic_pva.R +353 -0
- package/skills/predictive-modeling-best-practices/SKILL.md +136 -0
- package/skills/predictive-modeling-best-practices/examples/example-prompts.md +58 -0
- package/skills/predictive-modeling-best-practices/resources/collinearity-decision-tree.md +65 -0
- package/skills/predictive-modeling-best-practices/resources/sampling-bias-correction.md +267 -0
- package/skills/predictive-modeling-best-practices/resources/spatial-cv-guide.md +73 -0
- package/skills/predictive-modeling-best-practices/scripts/__pycache__/spatial_cv.cpython-311.pyc +0 -0
- package/skills/predictive-modeling-best-practices/scripts/collinearity_check.R +112 -0
- package/skills/predictive-modeling-best-practices/scripts/spatial_cv.py +182 -0
- package/skills/reproducible-ecology-pipeline/SKILL.md +139 -0
- package/skills/reproducible-ecology-pipeline/examples/example-prompts.md +35 -0
- package/skills/reproducible-ecology-pipeline/resources/directory-structure-template.md +94 -0
- package/skills/reproducible-ecology-pipeline/resources/params-yaml-template.yaml +84 -0
- package/skills/reproducible-ecology-pipeline/resources/reproducibility-checklist-template.md +66 -0
- package/skills/reproducible-ecology-pipeline/scripts/generate_file_manifest.py +110 -0
- package/skills/reproducible-ecology-pipeline/scripts/init_project.sh +53 -0
- package/skills/spatial-prioritization/SKILL.md +162 -0
- package/skills/spatial-prioritization/examples/biodiversity_hotspot_prioritization_example.md +289 -0
- package/skills/spatial-prioritization/examples/example-prompts.md +93 -0
- package/skills/spatial-prioritization/resources/cost-surface-reference.md +130 -0
- package/skills/spatial-prioritization/resources/marxan-vs-prioritizr-comparison.md +125 -0
- package/skills/spatial-prioritization/resources/prioritizr-formulation-guide.md +188 -0
- package/skills/spatial-prioritization/resources/representation-targets-guide.md +186 -0
- package/skills/spatial-prioritization/scripts/prioritization_sensitivity.R +320 -0
- package/skills/spatial-prioritization/scripts/run_prioritization.R +336 -0
- package/skills/species-distribution-modeling/SKILL.md +139 -0
- package/skills/species-distribution-modeling/examples/example-prompts.md +36 -0
- package/skills/species-distribution-modeling/resources/algorithm-comparison.md +25 -0
- package/skills/species-distribution-modeling/resources/calibration-area-guide.md +71 -0
- package/skills/species-distribution-modeling/resources/climate-scenario-preparation.md +170 -0
- package/skills/species-distribution-modeling/resources/maxent-calibration-guide.md +211 -0
- package/skills/species-distribution-modeling/resources/sdm-checklist.md +37 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.R +236 -0
- package/skills/species-distribution-modeling/scripts/predict_distribution.py +286 -0
- package/skills/species-distribution-modeling/scripts/prepare_future_layers.R +351 -0
- package/skills/species-distribution-modeling/scripts/project_scenarios.R +220 -0
- package/skills/species-distribution-modeling/scripts/run_ensemble_sdm.R +99 -0
- package/skills/species-distribution-modeling/scripts/sdm_pipeline.py +318 -0
- package/skills/species-distribution-modeling/scripts/tune_maxnet.R +344 -0
- package/templates/SKILL_TEMPLATE.md +225 -0
- package/templates/checklists/data-submission-checklist.md +38 -0
- package/templates/checklists/post-analysis-checklist.md +55 -0
- package/templates/checklists/pre-analysis-checklist.md +31 -0
- package/templates/prompts/debug-skill.md +47 -0
- package/templates/prompts/invoke-skill.md +34 -0
- package/templates/prompts/invoke-workflow.md +45 -0
- package/templates/reports/technical-report-template.md +80 -0
- package/templates/scripts/logger_setup.R +79 -0
- package/templates/scripts/logger_setup.py +119 -0
- package/templates/scripts/params_loader.R +28 -0
- package/templates/scripts/params_loader.py +38 -0
- package/workflows/analyze-community-structure/WORKFLOW.md +72 -0
- package/workflows/analyze-environmental-change/WORKFLOW.md +73 -0
- package/workflows/assess-ecological-impact/WORKFLOW.md +75 -0
- package/workflows/assess-ecosystem-services/WORKFLOW.md +68 -0
- package/workflows/assess-landscape-connectivity/WORKFLOW.md +84 -0
- package/workflows/build-fire-risk-map/WORKFLOW.md +79 -0
- package/workflows/produce-technical-report/WORKFLOW.md +113 -0
- package/workflows/run-camera-trap-occupancy/WORKFLOW.md +87 -0
- package/workflows/run-conservation-prioritization/WORKFLOW.md +89 -0
- package/workflows/run-multispecies-screening/WORKFLOW.md +197 -0
- package/workflows/run-occupancy-analysis/WORKFLOW.md +74 -0
- package/workflows/run-population-viability/WORKFLOW.md +90 -0
- package/workflows/run-sdm-study/WORKFLOW.md +99 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Anthropogenic Pressure Index — Template and Data Sources
|
|
2
|
+
|
|
3
|
+
## Concept
|
|
4
|
+
|
|
5
|
+
A composite pressure index aggregates multiple threatening processes into a single spatial layer representing cumulative human impact on biodiversity. Each pressure layer is normalised (0–1) and combined as a weighted sum.
|
|
6
|
+
|
|
7
|
+
## Standard Pressure Layers
|
|
8
|
+
|
|
9
|
+
| Pressure | Proxy variable | Resolution | Source |
|
|
10
|
+
|---------|---------------|-----------|--------|
|
|
11
|
+
| Land conversion | % natural habitat remaining | 30 m | MapBiomas / ESA CCI |
|
|
12
|
+
| Road proximity | Distance to paved roads (inverted) | 100 m | OpenStreetMap / DNIT |
|
|
13
|
+
| Human density | Population density | 100 m | WorldPop |
|
|
14
|
+
| Agricultural intensity | Cropland + pasture density | 30 m | MapBiomas |
|
|
15
|
+
| Fire frequency | Fire count per year (MODIS/VIIRS) | 500 m | INPE BDQueimadas |
|
|
16
|
+
| Deforestation rate | Annual forest loss | 30 m | PRODES / Hansen GFC |
|
|
17
|
+
| Human footprint | Composite human impact | 1 km | Wildlife Conservation Society (2022) |
|
|
18
|
+
| Night-time lights | Light pollution (VIIRS DNB) | 500 m | NASA Black Marble |
|
|
19
|
+
| Fragmentation | 1 − MESH (normalised) | Derived | landscapemetrics |
|
|
20
|
+
|
|
21
|
+
## Normalisation Method
|
|
22
|
+
|
|
23
|
+
For each pressure layer P_i:
|
|
24
|
+
|
|
25
|
+
P_norm = (P_i − P_min) / (P_max − P_min)
|
|
26
|
+
|
|
27
|
+
where P_min and P_max are the 1st and 99th percentiles of values within the study area (use percentiles to reduce outlier influence).
|
|
28
|
+
|
|
29
|
+
For distance-based layers (e.g., distance to roads), invert after normalisation:
|
|
30
|
+
P_norm = 1 − [(dist − dist_min) / (dist_max − dist_min)]
|
|
31
|
+
|
|
32
|
+
## Weighted Combination
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Pressure_Index = Σ(w_i × P_norm_i) / Σ(w_i)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Default equal weights: w_i = 1 for all layers.
|
|
39
|
+
|
|
40
|
+
Justify custom weights with literature or expert elicitation. Document all weights in `params.yaml`.
|
|
41
|
+
|
|
42
|
+
## Classification Thresholds (default)
|
|
43
|
+
|
|
44
|
+
| Pressure class | Pressure_Index range |
|
|
45
|
+
|---------------|---------------------|
|
|
46
|
+
| Low | 0.00 – 0.25 |
|
|
47
|
+
| Moderate | 0.25 – 0.50 |
|
|
48
|
+
| High | 0.50 – 0.75 |
|
|
49
|
+
| Critical | 0.75 – 1.00 |
|
|
50
|
+
|
|
51
|
+
## R Code Template
|
|
52
|
+
|
|
53
|
+
```r
|
|
54
|
+
library(terra)
|
|
55
|
+
|
|
56
|
+
# Load normalised pressure layers (all same extent, CRS, resolution)
|
|
57
|
+
layers <- rast(c("road_proximity_norm.tif",
|
|
58
|
+
"pop_density_norm.tif",
|
|
59
|
+
"fire_frequency_norm.tif",
|
|
60
|
+
"deforestation_norm.tif",
|
|
61
|
+
"habitat_loss_norm.tif"))
|
|
62
|
+
|
|
63
|
+
# Equal weights
|
|
64
|
+
weights <- rep(1, nlyr(layers))
|
|
65
|
+
pressure_index <- weighted.mean(layers, w = weights)
|
|
66
|
+
|
|
67
|
+
# Classify
|
|
68
|
+
m <- matrix(c(0, 0.25, 1,
|
|
69
|
+
0.25, 0.50, 2,
|
|
70
|
+
0.50, 0.75, 3,
|
|
71
|
+
0.75, 1.00, 4), ncol = 3, byrow = TRUE)
|
|
72
|
+
pressure_class <- classify(pressure_index, m)
|
|
73
|
+
levels(pressure_class) <- data.frame(id = 1:4,
|
|
74
|
+
class = c("Low", "Moderate", "High", "Critical"))
|
|
75
|
+
|
|
76
|
+
writeRaster(pressure_index, "outputs/pressure_index.tif", overwrite = TRUE)
|
|
77
|
+
writeRaster(pressure_class, "outputs/pressure_class.tif", overwrite = TRUE)
|
|
78
|
+
```
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# BACI Study Design Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A well-designed BACI (Before-After Control-Impact) study requires careful decisions about site selection, survey timing, and sample size before fieldwork begins. Underpowered studies cannot detect real impacts; overdesigned studies waste resources. This guide provides evidence-based recommendations for each decision.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. How Many Sites? Control vs. Impact
|
|
10
|
+
|
|
11
|
+
### Recommendation: ≥ 5 control + ≥ 5 impact sites
|
|
12
|
+
|
|
13
|
+
| Sites per group | Approx. power (d=0.5, 4 surveys, α=0.05) | Notes |
|
|
14
|
+
|----------------|------------------------------------------|-------|
|
|
15
|
+
| 3 | ~0.29 | Severely underpowered; only detect large effects |
|
|
16
|
+
| 5 | ~0.46 | Minimum acceptable for preliminary screening |
|
|
17
|
+
| 10 | ~0.74 | Adequate for medium-large effects |
|
|
18
|
+
| 15 | ~0.89 | Recommended for publication |
|
|
19
|
+
| 20+ | ~0.95+ | Required for small effects (d < 0.3) |
|
|
20
|
+
|
|
21
|
+
**Why spatial replication matters more than temporal:** Adding sites reduces pseudo-replication and provides genuine spatial independence. Multiple survey occasions at the same site are not independent — they increase precision within a site but cannot substitute for spatial replication.
|
|
22
|
+
|
|
23
|
+
**Equal vs. unequal allocation:** Balance (equal n control and impact) is optimal when variance is homogeneous. If one group is more variable, allocate more sites there (proportional to σ ratio).
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. How Many Surveys? Before and After
|
|
28
|
+
|
|
29
|
+
### Recommendation: ≥ 3 pre-impact + ≥ 3 post-impact occasions
|
|
30
|
+
|
|
31
|
+
| Survey design | Notes |
|
|
32
|
+
|--------------|-------|
|
|
33
|
+
| 1 before + 1 after | Inadequate — cannot estimate background variance |
|
|
34
|
+
| 2 before + 2 after | Marginal — acceptable only with many sites (n ≥ 15) |
|
|
35
|
+
| **3 before + 3 after** | **Minimum recommended** |
|
|
36
|
+
| 4 before + 4 after | Preferred for publication; allows seasonal coverage |
|
|
37
|
+
| 6+ before + 6+ after | Required for detecting gradual, delayed, or cyclical impacts |
|
|
38
|
+
|
|
39
|
+
**Asymmetric designs:** More pre-impact surveys are better than symmetric if baseline variance is unknown (collect baseline data opportunistically while awaiting impact).
|
|
40
|
+
|
|
41
|
+
**Temporal extent:** Surveys should span at least one full seasonal cycle (12 months) to avoid confounding phenological variation with impact effects.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 3. How to Select Control Sites (Matching Criteria)
|
|
46
|
+
|
|
47
|
+
Control sites must be ecologically equivalent to impact sites in every way **except** exposure to the stressor. Failure here is the most common source of BACI bias.
|
|
48
|
+
|
|
49
|
+
### Matching criteria checklist
|
|
50
|
+
|
|
51
|
+
| Criterion | Description | How to verify |
|
|
52
|
+
|-----------|-------------|---------------|
|
|
53
|
+
| **Habitat type** | Same vegetation class, canopy cover, soil type | Remote sensing / field survey |
|
|
54
|
+
| **Disturbance history** | No recent land-use change within ≥ 10 years | Historical imagery / land records |
|
|
55
|
+
| **Hydrology** | Similar drainage, upstream catchment area | GIS topographic analysis |
|
|
56
|
+
| **Human access** | Similar road density, hunting/gathering pressure | Map + community consultation |
|
|
57
|
+
| **Spatial isolation** | ≥ minimum dispersal distance from impact site | Species-specific dispersal data |
|
|
58
|
+
| **Environmental gradients** | Similar elevation, aspect, slope | DEM analysis |
|
|
59
|
+
| **Connectivity** | No shared corridor that allows spill-over | Landscape connectivity analysis |
|
|
60
|
+
|
|
61
|
+
### Matching algorithm (propensity score matching)
|
|
62
|
+
```r
|
|
63
|
+
# Using MatchIt package
|
|
64
|
+
library(MatchIt)
|
|
65
|
+
match_obj <- matchit(treated ~ habitat + elevation + slope + dist_road,
|
|
66
|
+
data = site_covariates,
|
|
67
|
+
method = "nearest",
|
|
68
|
+
distance = "logit",
|
|
69
|
+
ratio = 1) # 1:1 matching
|
|
70
|
+
summary(match_obj)
|
|
71
|
+
# Check standardised mean differences < 0.1 for all covariates
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 4. Survey Interval (Closure Period)
|
|
77
|
+
|
|
78
|
+
The time between consecutive survey occasions must balance:
|
|
79
|
+
|
|
80
|
+
| Concern | Recommendation |
|
|
81
|
+
|---------|---------------|
|
|
82
|
+
| **Independence** | Interval ≥ 2 × animal home-range crossing time |
|
|
83
|
+
| **Seasonal coverage** | Cover wet + dry season (or all 4 seasons) within a year |
|
|
84
|
+
| **Biological response time** | Post-impact surveys should begin after sufficient time for response (e.g., 6 months for vegetation recovery; 1–2 breeding seasons for birds) |
|
|
85
|
+
| **Minimum interval** | Never < 2 weeks for mobile species (risk of individual recapture) |
|
|
86
|
+
|
|
87
|
+
**Typical intervals by taxon:**
|
|
88
|
+
|
|
89
|
+
| Taxon | Minimum interval | Rationale |
|
|
90
|
+
|-------|----------------|-----------|
|
|
91
|
+
| Large mammals | 4–6 weeks | Home range traversal time |
|
|
92
|
+
| Birds (point counts) | 2–3 weeks | Territory turnover between surveys |
|
|
93
|
+
| Amphibians | 2–4 weeks | Breeding pond drying/filling cycles |
|
|
94
|
+
| Fish (electrofishing) | 4–8 weeks | Habitat recovery after survey disturbance |
|
|
95
|
+
| Plants | 3–12 months | Phenological completeness |
|
|
96
|
+
| Invertebrates | 2–4 weeks | Generation time consideration |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 5. How to Estimate Variance for Power Analysis
|
|
101
|
+
|
|
102
|
+
A variance estimate (σ²) is required to convert effect size to Cohen's d. Three approaches:
|
|
103
|
+
|
|
104
|
+
### Option A: From pilot data (preferred)
|
|
105
|
+
```r
|
|
106
|
+
# Compute pooled within-site, within-period variance from at least 5 sites × 2 occasions
|
|
107
|
+
pilot_data <- read.csv("pilot_sites.csv") # columns: site, period, y (response)
|
|
108
|
+
# Mixed model to extract residual variance
|
|
109
|
+
library(lme4)
|
|
110
|
+
mod <- lmer(y ~ (1 | site) + (1 | period), data = pilot_data)
|
|
111
|
+
sigma2 <- sigma(mod)^2 # residual (within-site) variance
|
|
112
|
+
cat("Variance estimate:", sigma2, "\n")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Option B: From literature
|
|
116
|
+
Search for studies with the same response variable (e.g., bird abundance, fish CPUE) and same habitat type. Extract reported SD and use σ² = SD².
|
|
117
|
+
|
|
118
|
+
| Common ecological responses | Typical CV | Approx. σ² (if mean = 10) |
|
|
119
|
+
|----------------------------|-----------|--------------------------|
|
|
120
|
+
| Bird point count abundance | 0.4–0.8 | 16–64 |
|
|
121
|
+
| Camera-trap RAI | 0.6–1.2 | 36–144 |
|
|
122
|
+
| Tree basal area (m²/ha) | 0.1–0.3 | 1–9 |
|
|
123
|
+
| Fish CPUE | 0.8–2.0 | 64–400 |
|
|
124
|
+
| Macroinvertebrate richness | 0.2–0.4 | 4–16 |
|
|
125
|
+
|
|
126
|
+
### Option C: Conservative default
|
|
127
|
+
Use σ² = 1.0, which means the effect_size argument to `power_analysis_baci.R` is directly Cohen's d (fully standardised).
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## 6. Ecologically Relevant Effect Sizes by Impact Type
|
|
132
|
+
|
|
133
|
+
Use these as starting points when no pilot data are available. Cohen's d values are for the BACI interaction term (difference-in-differences).
|
|
134
|
+
|
|
135
|
+
| Impact type | Typical detectable effect (d) | Response variable | Source |
|
|
136
|
+
|------------|------------------------------|-------------------|--------|
|
|
137
|
+
| **Road construction** | 0.4–0.8 | Bird/mammal abundance within 500 m | Trombulak & Frissell (2000) |
|
|
138
|
+
| **Dam / reservoir** | 0.6–1.2 | Fish species richness, benthic community | Poff et al. (2007) |
|
|
139
|
+
| **Deforestation (partial)** | 0.5–0.9 | Bird richness, amphibian abundance | Barlow et al. (2007) |
|
|
140
|
+
| **Mining (artisanal)** | 0.8–1.5 | Benthic invertebrate richness | Dudgeon et al. (2006) |
|
|
141
|
+
| **Invasive species introduction** | 0.3–0.7 | Native species richness | Blackburn et al. (2014) |
|
|
142
|
+
| **Agricultural expansion** | 0.5–1.0 | Pollinator abundance, bird richness | Benton et al. (2003) |
|
|
143
|
+
| **Protected area establishment** | 0.2–0.5 | Large mammal density (10–15 yr lag) | Craigie et al. (2010) |
|
|
144
|
+
| **Ecological restoration** | 0.3–0.6 | Vegetation cover, bird diversity | Benayas et al. (2009) |
|
|
145
|
+
|
|
146
|
+
**Note:** Larger Cohen's d values require fewer sites to detect; smaller values (d < 0.3) may require n > 20 sites per group and render BACI impractical without landscape-scale monitoring programs.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 7. Pitfalls and Common Mistakes
|
|
151
|
+
|
|
152
|
+
| Pitfall | Consequence | Prevention |
|
|
153
|
+
|---------|-------------|------------|
|
|
154
|
+
| Control sites too close to impact | Spillover contamination | Distance ≥ max dispersal distance |
|
|
155
|
+
| Surveys only in impact-accessible season | Seasonal confounding | Stratified survey schedule |
|
|
156
|
+
| Single impact site | No spatial replication — can't compute error | Always ≥ 3 impact sites |
|
|
157
|
+
| Starting surveys after impact begins | No before data | Anticipate impacts; survey pre-emptively |
|
|
158
|
+
| Ignoring spatial autocorrelation | Inflated degrees of freedom | Use spatial mixed models (glmmTMB) |
|
|
159
|
+
| Unbalanced loss of sites | Post-hoc unbalanced design | Monitor site accessibility; have spare sites |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## References
|
|
164
|
+
|
|
165
|
+
- Underwood, A.J. (1994). On beyond BACI. *Ecological Applications*, 4(1), 3–15. [https://doi.org/10.2307/1942083](https://doi.org/10.2307/1942083)
|
|
166
|
+
- Stewart-Oaten, A. & Bence, J.R. (2001). Temporal and spatial variation. *Ecological Monographs*, 71(2), 305–339. [https://doi.org/10.1890/0012-9615(2001)071[0305:TASVI]2.0.CO;2](https://doi.org/10.1890/0012-9615(2001)071[0305:TASVI]2.0.CO;2)
|
|
167
|
+
- Rosenbaum, P.R. & Rubin, D.B. (1983). The central role of the propensity score. *Biometrika*, 70(1), 41–55. [https://doi.org/10.1093/biomet/70.1.41](https://doi.org/10.1093/biomet/70.1.41)
|
|
168
|
+
- Cohen, J. (1988). *Statistical Power Analysis for the Behavioral Sciences* (2nd ed.). Lawrence Erlbaum Associates.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
2
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
# Usage: Rscript baci_analysis.R <baci_data.csv> <output_dir> [response_col] [random_effects]
|
|
5
|
+
# BACI mixed-effects model for ecological impact assessment
|
|
6
|
+
# Usage: Rscript baci_analysis.R <data_csv> <response_var> <output_dir>
|
|
7
|
+
# Requires: glmmTMB, emmeans, ggplot2, dplyr
|
|
8
|
+
|
|
9
|
+
# ── Inline logger ─────────────────────────────────────────────────────────────
|
|
10
|
+
SKILL_NAME <- "ecological-impact-assessment"
|
|
11
|
+
.log_ts <- function() format(Sys.time(), "[%Y-%m-%d %H:%M:%S]")
|
|
12
|
+
log_info <- function(...) message(.log_ts(), " [INFO] ", sprintf(...))
|
|
13
|
+
log_warn <- function(...) message(.log_ts(), " [WARN] ", sprintf(...))
|
|
14
|
+
log_error<- function(...) message(.log_ts(), " [ERROR] ", sprintf(...))
|
|
15
|
+
log_step <- function(n, d) log_info("-- STEP %d: %s", n, d)
|
|
16
|
+
log_decision <- function(v, val, why) log_info("DECISION | %s = %s | %s", v, val, why)
|
|
17
|
+
dir.create("logs", recursive=TRUE, showWarnings=FALSE)
|
|
18
|
+
|
|
19
|
+
suppressPackageStartupMessages({
|
|
20
|
+
library(glmmTMB)
|
|
21
|
+
library(emmeans)
|
|
22
|
+
library(ggplot2)
|
|
23
|
+
library(dplyr)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
27
|
+
data_file <- ifelse(length(args) >= 1, args[1], "data/baci_data.csv")
|
|
28
|
+
response_var <- ifelse(length(args) >= 2, args[2], "abundance")
|
|
29
|
+
output_dir <- ifelse(length(args) >= 3, args[3], "outputs/baci")
|
|
30
|
+
dir.create(output_dir, recursive = TRUE, showWarnings = FALSE)
|
|
31
|
+
|
|
32
|
+
log_info("Skill: %s | data_file=%s | response_var=%s | output_dir=%s",
|
|
33
|
+
SKILL_NAME, data_file, response_var, output_dir)
|
|
34
|
+
|
|
35
|
+
# ── Input precondition check ──────────────────────────────────────────────────
|
|
36
|
+
if (!file.exists(data_file)) {
|
|
37
|
+
log_error(
|
|
38
|
+
"Input nao encontrado: %s\nCausa provavel: caminho errado ou arquivo nao gerado pelo passo anterior.\nVerifique: se o arquivo CSV existe e o nome esta correto.\nSkill anterior: ecological-sampling-design ou coleta de campo.",
|
|
39
|
+
data_file
|
|
40
|
+
)
|
|
41
|
+
stop("Missing: ", data_file)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
log_step(1, "Carregar e validar dados BACI")
|
|
45
|
+
dat <- tryCatch({
|
|
46
|
+
read.csv(data_file)
|
|
47
|
+
}, error = function(e) {
|
|
48
|
+
log_error(
|
|
49
|
+
"Falha ao ler CSV: %s\nCausa provavel: arquivo corrompido ou encoding incorreto.\nVerifique: abra o arquivo em editor de texto e confira separadores.\nSkill anterior: ecological-sampling-design.",
|
|
50
|
+
conditionMessage(e)
|
|
51
|
+
)
|
|
52
|
+
stop(e)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
required_cols <- c("site", "period", "treatment", response_var)
|
|
56
|
+
missing_cols <- setdiff(required_cols, names(dat))
|
|
57
|
+
if (length(missing_cols) > 0) {
|
|
58
|
+
log_error(
|
|
59
|
+
"Colunas obrigatorias ausentes: %s\nCausa provavel: CSV nao segue o esquema BACI esperado.\nVerifique: o arquivo deve ter colunas site, period, treatment e a variavel resposta.\nSkill anterior: ecological-sampling-design.",
|
|
60
|
+
paste(missing_cols, collapse = ", ")
|
|
61
|
+
)
|
|
62
|
+
stop("Missing columns: ", paste(missing_cols, collapse = ", "))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
n_na <- sum(is.na(dat[[response_var]]))
|
|
66
|
+
if (n_na > 0) {
|
|
67
|
+
log_warn("Coluna '%s' contem %d valores NA — podem ser excluidos pela funcao de modelo.", response_var, n_na)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
dat$period <- factor(dat$period, levels = c("before", "after"))
|
|
71
|
+
dat$treatment <- factor(dat$treatment, levels = c("control", "impact"))
|
|
72
|
+
|
|
73
|
+
n_sites <- n_distinct(dat$site)
|
|
74
|
+
n_before <- sum(dat$period == "before")
|
|
75
|
+
n_after <- sum(dat$period == "after")
|
|
76
|
+
log_info("Sites: %d | Before: %d | After: %d", n_sites, n_before, n_after)
|
|
77
|
+
log_decision("response_var", response_var, "variavel resposta fornecida pelo usuario ou padrao 'abundance'")
|
|
78
|
+
log_decision("family", "nbinom2", "distribuicao binomial negativa adequada para contagens de abundancia com superdispersao")
|
|
79
|
+
|
|
80
|
+
if (n_sites < 3) {
|
|
81
|
+
log_warn("Apenas %d site(s) detectado(s) — efeitos aleatorios podem nao ser estimaveis.", n_sites)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
# ── Fit BACI model ────────────────────────────────────────────────────────────
|
|
85
|
+
log_step(2, "Ajustar modelo misto BACI (glmmTMB, nbinom2)")
|
|
86
|
+
formula_str <- paste(response_var, "~ period * treatment + (1|site)")
|
|
87
|
+
log_info("Formula: %s", formula_str)
|
|
88
|
+
|
|
89
|
+
m <- tryCatch({
|
|
90
|
+
glmmTMB(as.formula(formula_str), data = dat, family = nbinom2())
|
|
91
|
+
}, error = function(e) {
|
|
92
|
+
log_error(
|
|
93
|
+
"Falha ao ajustar modelo glmmTMB: %s\nCausa provavel: dados insuficientes, colunas mal codificadas ou singularidade no modelo.\nVerifique: numero de niveis por site/periodo e presenca de zeros excessivos.\nSkill anterior: ecological-sampling-design.",
|
|
94
|
+
conditionMessage(e)
|
|
95
|
+
)
|
|
96
|
+
stop(e)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
log_info("Modelo ajustado com sucesso.")
|
|
100
|
+
print(summary(m))
|
|
101
|
+
|
|
102
|
+
# ── Extract BACI interaction ───────────────────────────────────────────────────
|
|
103
|
+
log_step(3, "Extrair interacao BACI e calcular efeito na escala original")
|
|
104
|
+
coef_table <- tryCatch({
|
|
105
|
+
as.data.frame(coef(summary(m))$cond)
|
|
106
|
+
}, error = function(e) {
|
|
107
|
+
log_error(
|
|
108
|
+
"Falha ao extrair coeficientes do modelo: %s\nCausa provavel: modelo nao convergiu ou estrutura inesperada.\nVerifique: inspecione summary(m) manualmente.\nSkill anterior: nenhuma.",
|
|
109
|
+
conditionMessage(e)
|
|
110
|
+
)
|
|
111
|
+
stop(e)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
baci_row <- grep("period.*treatment|treatment.*period", rownames(coef_table))
|
|
115
|
+
|
|
116
|
+
if (length(baci_row) > 0) {
|
|
117
|
+
baci_est <- coef_table[baci_row, ]
|
|
118
|
+
log_info("=== Interacao BACI ===")
|
|
119
|
+
print(baci_est)
|
|
120
|
+
effect_multiplicative <- round(exp(baci_est[, "Estimate"]), 3)
|
|
121
|
+
log_info("Efeito na escala original (multiplicativo): %s", paste(effect_multiplicative, collapse = ", "))
|
|
122
|
+
tryCatch({
|
|
123
|
+
write.csv(baci_est, file.path(output_dir, "baci_results.csv"))
|
|
124
|
+
log_info("baci_results.csv salvo em: %s", output_dir)
|
|
125
|
+
}, error = function(e) {
|
|
126
|
+
log_error(
|
|
127
|
+
"Falha ao salvar baci_results.csv: %s\nCausa provavel: permissao negada ou disco cheio.\nVerifique: permissoes do diretorio de saida.\nSkill anterior: nenhuma.",
|
|
128
|
+
conditionMessage(e)
|
|
129
|
+
)
|
|
130
|
+
stop(e)
|
|
131
|
+
})
|
|
132
|
+
} else {
|
|
133
|
+
log_warn("Termo de interacao BACI (period:treatment) nao encontrado na tabela de coeficientes.")
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ── Before/After plot ──────────────────────────────────────────────────────────
|
|
137
|
+
log_step(4, "Gerar grafico BACI (controle vs impacto, antes vs depois)")
|
|
138
|
+
tryCatch({
|
|
139
|
+
p_baci <- dat |>
|
|
140
|
+
group_by(period, treatment) |>
|
|
141
|
+
summarise(mean_y = mean(.data[[response_var]], na.rm = TRUE),
|
|
142
|
+
se_y = sd(.data[[response_var]], na.rm = TRUE) / sqrt(n()),
|
|
143
|
+
.groups = "drop") |>
|
|
144
|
+
ggplot(aes(x = period, y = mean_y, colour = treatment, group = treatment)) +
|
|
145
|
+
geom_line(linewidth = 1) +
|
|
146
|
+
geom_point(size = 3) +
|
|
147
|
+
geom_errorbar(aes(ymin = mean_y - se_y, ymax = mean_y + se_y), width = 0.1) +
|
|
148
|
+
scale_colour_manual(values = c(control = "#2166ac", impact = "#d6604d")) +
|
|
149
|
+
labs(y = response_var, title = "BACI: Control vs Impact") +
|
|
150
|
+
theme_bw()
|
|
151
|
+
ggsave(file.path(output_dir, "baci_plot.png"), p_baci, width = 6, height = 5, dpi = 150)
|
|
152
|
+
log_info("baci_plot.png salvo em: %s", output_dir)
|
|
153
|
+
}, error = function(e) {
|
|
154
|
+
log_error(
|
|
155
|
+
"Falha ao gerar ou salvar grafico BACI: %s\nCausa provavel: dados insuficientes para sumarizacao ou diretorio sem permissao de escrita.\nVerifique: presenca de pelo menos um registro por combinacao period/treatment.\nSkill anterior: nenhuma.",
|
|
156
|
+
conditionMessage(e)
|
|
157
|
+
)
|
|
158
|
+
stop(e)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
log_info("Analise BACI concluida. Saidas em: %s", output_dir)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ecological-agent-skills / Copyright (C) 2026 Francisco Diego Barros Barata
|
|
3
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
fragmentation_analysis.py
|
|
7
|
+
Compute landscape fragmentation metrics from a land cover raster.
|
|
8
|
+
Usage: python fragmentation_analysis.py <landcover_tif> <habitat_class> <output_dir>
|
|
9
|
+
Requires: rasterio, numpy, pandas, scipy, skimage
|
|
10
|
+
"""
|
|
11
|
+
import logging
|
|
12
|
+
import sys
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
SKILL_NAME = "ecological-impact-assessment"
|
|
17
|
+
_LOG_DIR = Path("logs")
|
|
18
|
+
_LOG_DIR.mkdir(parents=True, exist_ok=True)
|
|
19
|
+
_log_file = _LOG_DIR / f"skill_{SKILL_NAME}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
20
|
+
logging.basicConfig(
|
|
21
|
+
level=logging.INFO,
|
|
22
|
+
format="[%(asctime)s] [%(levelname)s] [" + SKILL_NAME + "] %(message)s",
|
|
23
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
24
|
+
handlers=[
|
|
25
|
+
logging.StreamHandler(sys.stdout),
|
|
26
|
+
logging.FileHandler(_log_file, encoding="utf-8"),
|
|
27
|
+
],
|
|
28
|
+
)
|
|
29
|
+
logger = logging.getLogger(SKILL_NAME)
|
|
30
|
+
|
|
31
|
+
def log_step(n: int, desc: str) -> None:
|
|
32
|
+
logger.info("-- STEP %d: %s", n, desc)
|
|
33
|
+
|
|
34
|
+
def log_decision(var: str, val, why: str) -> None:
|
|
35
|
+
logger.info("DECISION | %s = %s | %s", var, val, why)
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
import pandas as pd
|
|
39
|
+
import rasterio
|
|
40
|
+
from scipy import ndimage
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_habitat_mask(tif_path: str, habitat_class: int) -> tuple:
|
|
44
|
+
with rasterio.open(tif_path) as src:
|
|
45
|
+
lc = src.read(1).astype(float)
|
|
46
|
+
res = src.res # (x_res, y_res) in map units
|
|
47
|
+
crs_is_projected = src.crs.is_projected if src.crs else False
|
|
48
|
+
mask = (lc == habitat_class).astype(int)
|
|
49
|
+
return mask, res, crs_is_projected
|
|
50
|
+
|
|
51
|
+
def label_patches(mask: np.ndarray):
|
|
52
|
+
struct = ndimage.generate_binary_structure(2, 2) # 8-connectivity
|
|
53
|
+
labeled, n_patches = ndimage.label(mask, structure=struct)
|
|
54
|
+
return labeled, n_patches
|
|
55
|
+
|
|
56
|
+
def compute_metrics(mask: np.ndarray, labeled: np.ndarray, n_patches: int,
|
|
57
|
+
cell_area_ha: float) -> pd.Series:
|
|
58
|
+
total_cells = mask.sum()
|
|
59
|
+
total_area_ha = total_cells * cell_area_ha
|
|
60
|
+
patch_sizes = ndimage.sum(mask, labeled, range(1, n_patches + 1))
|
|
61
|
+
patch_areas_ha = np.array(patch_sizes) * cell_area_ha
|
|
62
|
+
landscape_area_ha = mask.size * cell_area_ha
|
|
63
|
+
|
|
64
|
+
largest_patch_ha = patch_areas_ha.max() if len(patch_areas_ha) > 0 else 0
|
|
65
|
+
lpi = (largest_patch_ha / landscape_area_ha) * 100
|
|
66
|
+
|
|
67
|
+
# Effective mesh size: MESH = Σ(Ai²) / A_landscape
|
|
68
|
+
mesh = np.sum(patch_areas_ha ** 2) / landscape_area_ha
|
|
69
|
+
|
|
70
|
+
return pd.Series({
|
|
71
|
+
"total_habitat_ha": round(total_area_ha, 2),
|
|
72
|
+
"landscape_area_ha": round(landscape_area_ha, 2),
|
|
73
|
+
"habitat_cover_pct": round(100 * total_area_ha / landscape_area_ha, 2),
|
|
74
|
+
"n_patches": int(n_patches),
|
|
75
|
+
"mean_patch_size_ha": round(float(patch_areas_ha.mean()), 2) if len(patch_areas_ha) > 0 else 0,
|
|
76
|
+
"median_patch_size_ha": round(float(np.median(patch_areas_ha)), 2) if len(patch_areas_ha) > 0 else 0,
|
|
77
|
+
"largest_patch_ha": round(float(largest_patch_ha), 2),
|
|
78
|
+
"lpi_pct": round(float(lpi), 3),
|
|
79
|
+
"mesh_ha": round(float(mesh), 2),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
def main():
|
|
83
|
+
tif_file = sys.argv[1] if len(sys.argv) > 1 else "data/landcover.tif"
|
|
84
|
+
habitat_class = int(sys.argv[2]) if len(sys.argv) > 2 else 3
|
|
85
|
+
output_dir = Path(sys.argv[3]) if len(sys.argv) > 3 else Path("outputs/fragmentation")
|
|
86
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
|
|
88
|
+
log_decision("tif_file", tif_file, "Input land cover raster path")
|
|
89
|
+
log_decision("habitat_class", habitat_class, "Target habitat class code to extract")
|
|
90
|
+
log_decision("output_dir", str(output_dir), "Directory for fragmentation metric outputs")
|
|
91
|
+
|
|
92
|
+
if not Path(tif_file).exists():
|
|
93
|
+
logger.error(
|
|
94
|
+
"Input nao encontrado: %s\n"
|
|
95
|
+
" Causa provavel: passo anterior nao concluiu.\n"
|
|
96
|
+
" Skill anterior que deveria ter produzido este input: geoprocessing-for-ecology",
|
|
97
|
+
tif_file
|
|
98
|
+
)
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
log_step(1, "Loading habitat mask from raster")
|
|
103
|
+
logger.info("Loading: %s | Habitat class: %s", tif_file, habitat_class)
|
|
104
|
+
mask, (xres, yres), projected = load_habitat_mask(tif_file, habitat_class)
|
|
105
|
+
cell_area_ha = abs(xres * yres) / 10000 # m² → ha (assumes projected CRS)
|
|
106
|
+
log_decision("cell_area_ha", round(cell_area_ha, 6), "Derived from pixel resolution; assumes projected CRS")
|
|
107
|
+
if not projected:
|
|
108
|
+
logger.warning(
|
|
109
|
+
"Raster appears to be in geographic CRS. Area estimates will be approximate."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
log_step(2, "Labeling habitat patches with 8-connectivity")
|
|
113
|
+
labeled, n_patches = label_patches(mask)
|
|
114
|
+
logger.info("Patches found: %d", n_patches)
|
|
115
|
+
if n_patches == 0:
|
|
116
|
+
logger.warning("No patches found for habitat class %d. Check class code.", habitat_class)
|
|
117
|
+
|
|
118
|
+
log_step(3, "Computing fragmentation metrics")
|
|
119
|
+
metrics = compute_metrics(mask, labeled, n_patches, cell_area_ha)
|
|
120
|
+
logger.info("Fragmentation Metrics:\n%s", metrics.to_string())
|
|
121
|
+
|
|
122
|
+
log_step(4, "Writing outputs")
|
|
123
|
+
metrics_df = metrics.to_frame(name="value").reset_index().rename(columns={"index": "metric"})
|
|
124
|
+
out_csv = output_dir / "fragmentation_metrics.csv"
|
|
125
|
+
metrics_df.to_csv(out_csv, index=False)
|
|
126
|
+
logger.info("Saved to: %s", out_csv)
|
|
127
|
+
|
|
128
|
+
except FileNotFoundError as e:
|
|
129
|
+
logger.error(
|
|
130
|
+
"Input file not found: %s\n"
|
|
131
|
+
" Expected output from: geoprocessing-for-ecology\n"
|
|
132
|
+
" Check that previous step completed.",
|
|
133
|
+
e
|
|
134
|
+
)
|
|
135
|
+
raise
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error("Unexpected error in fragmentation analysis: %s", e)
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
if __name__ == "__main__":
|
|
141
|
+
main()
|