This repository contains the Python fullstack environment with a specialised backend for the AgniVed project. It implements:
Python Backend/main_api.py) orchestrating these pipelines and exposing HTTP endpointsAll code and experiments are designed to run locally in a Python 3.11 virtual environment.
At the top level:
.gitignore
agniv_requirements.txt
Base requirements snapshot (Linux-oriented). A trimmed set is installed into the 3.11 venv.
download_models.py
Helper script to pre-download PytorchWildlife detection/classification models and (optionally) vegetation models (Prithvi, BigEarthNet ResNet50). Mostly for model caching and testing.
agnived_env/
Python 3.11 virtual environment directory (created locally). Contains:
pyvenv.cfg, Lib/, Scripts/, etc.Final_Res_DW/
Production output directory for the Dynamic World + Sentinel-2 land-cover pipeline:
sentinel2_hyperspectral.tif – 12-band S2 compositeland_cover_classification.tif – DW class labels (0–8)land_cover_probabilities.tif – DW class probabilitiesvegetation_mask.tif – combined vegetation probability mask (trees/grass/crops/shrub/flooded_vegetation)agnived_cover_analysis.png – 6-panel land-cover visualizationmask_*.png – per-class binary masksmetadata.json – AOI + statisticsHyperspectral models/
Placeholder for advanced models:
bigearth/ – BigEarthNet experiments (rdnet/convnext/etc)Prithvi model/ – Prithvi EO 2.0 experiments (commented in download_models.py)Python Backend/
Main backend code:
main_api.py – FastAPI app (described in detail below)reben/, reben_publication/ – local clone/extract of the reBEN BigEarthNet v2.0 model code (rdnet, convnext, etc.)S2 Landcover pipeline/ – Dynamic World + Sentinel-2 land-cover pipelineS2 Vegetation Classification pipeline/ – BigEarthNet rdnet S2 vegetation classification pipelineVideo_inference_engine/ – YouTube video/live inference engine for wildlife detection & classificationvideo_results/ – JSON and other outputs from video inferenceTest_CameraTraps/
Developer/test scripts for the wildlife pipeline:
Test_models.py – validates single-image detection and species classification modelstest_video.py – reference implementation of local video real-time inferenceTest_results_DW/
Earlier test outputs from the land-cover pipeline (metadata.json, etc.)
Test_Satellite/
Prototyping and research notebooks/scripts for:
TestClassificationDownload.py)TestBigEarthrdnet.py, TestBigEarthS1S2.py)reben_publication/)agnived_env at the repo rootTo recreate:
python3.11 -m venv agnived_env
source agnived_env/bin/activate # On Windows: agnived_env\Scripts\activate
pip install -r agniv_requirements.txt # (or subset)
Environment includes (core):
fastapi, uvicornearthengine-api, geemap, rasterio, numpy, matplotlibPytorchWildlife, torch, torchvision, ultralyticsyt-dlp, opencv-pythonlightning, timm, transformers (for reBEN / future models)Note: agniv_requirements.txt is Linux-centric and includes ROS, GDAL, etc. For Windows, installation is typically done with a subset (only what's needed for these pipelines).
Implementation:
Python Backend/S2 Landcover pipeline/Download_Classify.py
Key public pieces:
s2_landcover.AOIConfigs2_landcover.DownloadConfigs2_landcover.run_landcover_pipeline(aoi_cfg, dl_cfg) – End-to-end pipelineSentinel-2: COPERNICUS/S2_SR_HARMONIZED
Bands used:
["B1","B2","B3","B4","B5","B6","B7","B8","B8A","B9","B11","B12"]
Dynamic World: GOOGLE/DYNAMICWORLD/V1
Classes 0–8:
Probability bands:
["water","trees","grass","flooded_vegetation","crops","shrub_and_scrub","built","bare","snow_and_ice"]
AOI is defined as:
(lon, lat)bounds() → rectangular AOIinit_ee (called from API):
ee.Initialize(project=ee_project)ee.Authenticate() → then ee.Initializecreate_aoi:
ee.Geometry.Point([lon, lat])buffer_km * 1000 m.bounds() for a rectangular patchload_sentinel2(aoi, cfg):
date_start–date_end, CLOUDY_PIXEL_PERCENTAGE < cloud_cover_maxcreate_sentinel2_composite(collection):
download_geotiff:
image.getDownloadURL with:
scale = cfg.scale (typically 10 m)region = aoifilePerBand = False, format = GEO_TIFFsentinel2_hyperspectral.tif in output_dirload_dynamic_world(aoi, cfg):
create_classification_composite(dw_collection):
create_probability_composite(dw_collection):
Download to:
land_cover_classification.tif (bands=['label'])land_cover_probabilities.tif (all probability bands)create_vegetation_mask(dw_collection, threshold):
Uses DW probability bands for vegetation-related classes:
Process:
Reducer.max()veg_mask = [p_max ≥ threshold]vegetation_mask.tifcalculate_statistics(classification_path, cfg):
np.uniquescale²pixels, area_km2, percentage, descriptioncreate_visualizations:
Creates AgniVed cover analysis 6-panel PNG:
Also:
create_class_masks to create per-class binary mask PNGssave_metadata(stats, aoi, cfg):
metadata.jsonrun_landcover_pipeline(aoi_cfg, dl_cfg):
sentinel2, classification, probabilities, vegetation_mask, visualization, metadataBackend implementation:
Python Backend/S2 Vegetation Classification pipeline/Vegetation_Classification_pipeline.py
Base reference for behaviour:
TestBigEarthrdnet.py and BigEarthNetv2_0_ImageClassifier.py
reBEN BigEarthNet v2.0 S2-only model: rdnet_base-s2-v0.2.0
Band order:
["B02","B03","B04","B05","B06","B07","B08","B8A","B11","B12"]
# (aliases ["B2", "B3", ...])
Classes (19):
(see BEN_CLASSES in the pipeline file)
Normalization:
DN-space band statistics [BEN_MEAN, BEN_STD] (copied from test script):
tile_norm = (tile - BEN_MEAN) / BEN_STD
Dataclasses:
AOIConfig:
lon: float
lat: float
buffer_m: int # buffer radius in meters
BigEarthConfig:
date_start: str
date_end: str
cloud_cover_max: float
scale: int = 10 # meters per pixel
BigEarthResult:
Contains:
aoi: AOIConfigcube_path: path to downloaded S2 stackviz_path: combined PNG (true color + rdnet class map + confidence)class_distribution: per-class tile percentagetile_counts: per-class tile countsavg_confidence: mean of top-1 probabilitiestiles_shape: number of tiles in grid (H_tiles, W_tiles)init_earth_engine mirrors TestBigEarthrdnet:
ee.Initialize(project="our-lamp-465108-a9") or ee.Authenticate()build_aoi:
buffer_m → .bounds()build_single_composite(aoi, cfg):
Uses COPERNICUS/S2_SR_HARMONIZED:
date_start–date_end, CLOUDY_PIXEL_PERCENTAGE < cloud_cover_maxselect(S2_BANDS)This replicates the behaviour of download_single_composite in TestBigEarthrdnet.py.
download_composite:
image.getDownloadURL → bigearth_s2_stack.tif in temporary dirread_cube:
rasterio.read() → (10, H, W) arraytile_cube:
PATCH_SIZE = 120H // 120 > 0 and W // 120 > 0 → approx 1.2 km × 1.2 km at 10 mnormalize_tiles:
BEN_MEAN and BEN_STD from reBENload_reben_model:
Uses local BigEarthNetv2_0_ImageClassifier.py.
Calls:
BigEarthNetv2_0_ImageClassifier(
model_name="rdnet_base",
bands="s2",
ckpt_path="...",
device=device
)
logits = model(tensor) → probs = sigmoid(logits)class_map (2D grid)conf_map (2D grid)Distribution & summary:
np.bincount over class_map → tile counts per BigEarthNet classTrue color: uses S2 bands B4 (R), B3 (G), B2 (B), normalized to [0,1]
Produces a 3-panel figure:
Saves to bigearth_rdnet_s2_results.png under a Results/ folder in the S2 Vegetation pipeline.
run_bigearth_rdnet(aoi_cfg, be_cfg=None, device=None, out_dir=None, veg_mask_path=None)
(internally extended in main_api.py to optionally intersect with a vegetation_mask.tif if required)
Code references:
Test_models.pytest_video.pydownload_models.pyBackend engine:
Python Backend/Video_inference_engine/Video_inference.py
Models:
Detection:
pw_detection.MegaDetectorV6(device=..., pretrained=True, version="MDV6-yolov10-e")
Detects: Animal, Person, VehicleClassification:
test_video.py defines VideoWildlifeDetector with:
load_models():
detect_frame:
FRAME_SKIP'th frame:
/tmp/frame_<idx>.jpgMegaDetectorV6.single_image_detection on that filematch_detection_to_track:
classify_detection_realtime:
/tmp/classify_crop.jpgsingle_image_classificationFrame buffer per track:
CLASSIFICATION_FRAMES_PER_TRACK), classification is performed using the best detection framedraw_realtime_annotations:
process_video_realtime:
cv2.VideoCapture, fps, total_framescv2.imshowsave_resultsImplementation:
Python Backend/Video_inference_engine/Video_inference.py
FRAME_SKIP = 2
CONF_THRESHOLD = 0.1
IOU_THRESHOLD = 0.3
TRACK_TIMEOUT = 30
CLASSIFICATION_FRAMES_PER_TRACK = 3
BUFFER_MINUTES = 10
VideoWildlifeDetector mirrors the logic of the test script:
load_models()
iou(box1, box2)
detect_animals(frame_rgb, frame_idx)
/tmp/frame_{frame_idx}.jpgcategory_id == 0 (Animal) detectionsmatch_detection_to_track(detection, frame_idx)
TRACK_TIMEOUT frames memoryclassify_detection(frame_rgb, bbox)
/tmp/classify_crop.jpgsingle_image_classificationdraw_annotations(frame_rgb, frame_idx, total_frames=None)
process_stream_frame(frame_rgb, frame_idx, total_frames=None)
Called for each new frame by the YouTube loop.
Every FRAME_SKIP frames:
detect_animalsOn intermediate frames:
Returns annotated RGB frame.
save_results(video_name, fps)
video_results/<video_name>_results.jsonget_youtube_stream_url(youtube_url: str) -> str
yt_dlp to resolve the best MP4 stream URL (≤720p)run_youtube_live_inference(youtube_url: str)
Process:
get_youtube_stream_urlcv2.VideoCapture on the stream URL(frame_idx, frame) covering up to BUFFER_MINUTES * 60 * fps frames (≈ last 10 minutes)In each loop:
detector.process_stream_frame(frame_rgb, frame_idx)Future enhancement: second pass over the buffered frames, akin to processing a small 10-minute clip with better context.
Current behaviour: near-real-time inference on incoming frames with a rolling buffer.
Location:
Python Backend/main_api.py
This file exposes all major functionality as REST endpoints.
To avoid import-path issues and keep test code separate, the backend dynamically loads the land-cover and vegetation modules via:
load_module_from_path
It binds:
s2_landcover.AOIConfig, DownloadConfig, run_landcover_pipelines2_vegetation.AOIConfig, run_bigearth_rdnet (lazy-loaded)Pydantic models:
LandcoverRequest:
lon: float
lat: float
buffer_km: float = 0.6 # defaults 0.6 → 600 m AOI radius
date_start: str
date_end: str
scale: int
cloud_cover_max: float
VegetationRequest:
lon: float
lat: float
buffer_m: int = 600
use_mask: bool # whether to intersect with vegetation_mask.tif from land-cover step
VideoRequest:
youtube_url: str
PipelineRequest:
Combined config for both land-cover and vegetation:
lon: float
lat: float
buffer_km: float # for DW
buffer_m: int # for BigEarth
date_start: str
date_end: str
scale: int
cloud_cover_max: float
/landcover/dw (POST)Runs the Dynamic World + Sentinel-2 land-cover pipeline.
Input: LandcoverRequest
Implementation:
@app.post("/landcover/dw")
async def run_landcover_analysis(req: LandcoverRequest):
# Build AOI and DownloadConfig
aoi_cfg = DW_AOIConfig(lon=req.lon, lat=req.lat, buffer_km=req.buffer_km)
dl_cfg = DownloadConfig(
date_start=req.date_start,
date_end=req.date_end,
scale=req.scale,
cloud_cover_max=req.cloud_cover_max,
output_dir="Final_Res_DW"
)
# Run pipeline
results = run_landcover_pipeline(aoi_cfg, dl_cfg)
# Return paths
return {
"status": "success",
"results": results
}
Returns:
{
"status": "success",
"results": {
"sentinel2": "path/to/sentinel2_hyperspectral.tif",
"classification": "path/to/land_cover_classification.tif",
"probabilities": "path/to/land_cover_probabilities.tif",
"vegetation_mask": "path/to/vegetation_mask.tif",
"visualization": "path/to/agnived_cover_analysis.png",
"metadata": "path/to/metadata.json"
}
}
/vegetation/bigearth (POST)Runs the BigEarthNet rdnet S2 vegetation classifier.
Input: VegetationRequest
Optionally uses vegetation mask from land-cover step:
veg_mask_path = None
if req.use_mask:
veg_mask_path = "Final_Res_DW/vegetation_mask.tif"
Implementation:
@app.post("/vegetation/bigearth")
async def run_vegetation_classification(req: VegetationRequest):
# Lazy-load vegetation module
if s2_vegetation is None:
load_vegetation_module()
# Build AOI
aoi_cfg = VEG_AOIConfig(lon=req.lon, lat=req.lat, buffer_m=req.buffer_m)
# Determine mask path
veg_mask_path = "Final_Res_DW/vegetation_mask.tif" if req.use_mask else None
# Run BigEarth pipeline
result = s2_vegetation.run_bigearth_rdnet(
aoi_cfg,
veg_mask_path=veg_mask_path
)
return {
"status": "success",
"result": result
}
Returns:
{
"status": "success",
"result": {
"aoi": {...},
"cube_path": "path/to/bigearth_s2_stack.tif",
"viz_path": "path/to/bigearth_rdnet_s2_results.png",
"class_distribution": {...},
"tile_counts": {...},
"avg_confidence": 0.85,
"tiles_shape": [10, 10]
}
}
Note: This endpoint expects that /landcover/dw has already been run if use_mask is True.
/video/classify (POST)Starts a background YouTube live inference session.
Input: VideoRequest with youtube_url
Implementation:
@app.post("/video/classify")
async def classify_video(req: VideoRequest):
# Start inference in background thread
thread = threading.Thread(
target=run_youtube_live_inference,
args=(req.youtube_url,),
daemon=True
)
thread.start()
return {
"status": "started",
"message": "YouTube inference running in background",
"url": req.youtube_url
}
Returns immediately:
{
"status": "started",
"message": "YouTube inference running in background",
"url": "https://youtube.com/watch?v=..."
}
A local OpenCV window will appear on the machine where the backend is running, showing the annotated video. This is intended for local prototyping.
/files/image (GET)Serves any generated image/GeoTIFF under the project root.
Query param: path (absolute or project-relative)
Validates: that the resolved path stays under PROJECT_ROOT
Returns: a FileResponse
Useful for the frontend dashboard to display:
agnived_cover_analysis.pngvegetation_mask.tifExample:
GET /files/image?path=Final_Res_DW/agnived_cover_analysis.png
/pipeline/run (POST)Runs both land-cover and vegetation sequences in order.
Input: PipelineRequest
Steps:
/landcover/dw logic:
DW_AOIConfig, DownloadConfig, run_landcover_pipelinevegetation_mask.tif pathrun_bigearth_rdnet with AOI in meters and veg_mask_pathReturns combined JSON:
{
"status": "success",
"landcover": {...},
"vegetation": {...}
}
From the repo root:
cd "Python Backend"
python main_api.py
This runs:
uvicorn.run("main_api:app", host="0.0.0.0", port=8000, reload=True)
API will be available at http://localhost:8000
Interactive docs: http://localhost:8000/docs
User picks AOI on frontend:
(lon, lat), buffer (km)Backend /landcover/dw:
Final_Res_DWFrontend:
/files/image to fetch:
agnived_cover_analysis.png (6-panel view)Backend /vegetation/bigearth or /pipeline/run:
Frontend:
User chooses a YouTube livestream or video in the UI
Frontend posts to /video/classify with youtube_url
Backend:
run_youtube_live_inference in backgroundFuture enhancements:
Earth Engine project: our-lamp-465108-a9 is used in all EE initialization calls
Band scaling:
Sentinel-2 SR values (0–10000 or reflectance) are assumed numerically compatible with reBEN DN normalization. This matches the reference scripts.
AOI sizes:
YouTube streaming:
yt_dlp to resolve and open one MP4 stream (≤720p)Local only:
The video inference is currently designed for local desktop use (display window on the backend machine)
Planned or easy future extensions:
Temporal options for land-cover and BigEarth:
Multiple AOI tiles:
Prithvi + SAR/optical fusion:
Hyperspectral models/Prithvi model/ + TestBigEarthS1S2.pyWebSocket-based video streaming:
Authentication & user data:
For issues or questions, please refer to the project documentation or contact the development team.
15 commits
Python
78.2%
JavaScript
8.9%
TypeScript
6.8%
CSS
6.0%
This repository contains the Python fullstack environment with a specialised backend for the AgniVed project. It implements:
Python Backend/main_api.py) orchestrating these pipelines and exposing HTTP endpointsAll code and experiments are designed to run locally in a Python 3.11 virtual environment.
At the top level:
.gitignore
agniv_requirements.txt
Base requirements snapshot (Linux-oriented). A trimmed set is installed into the 3.11 venv.
download_models.py
Helper script to pre-download PytorchWildlife detection/classification models and (optionally) vegetation models (Prithvi, BigEarthNet ResNet50). Mostly for model caching and testing.
agnived_env/
Python 3.11 virtual environment directory (created locally). Contains:
pyvenv.cfg, Lib/, Scripts/, etc.Final_Res_DW/
Production output directory for the Dynamic World + Sentinel-2 land-cover pipeline:
sentinel2_hyperspectral.tif – 12-band S2 compositeland_cover_classification.tif – DW class labels (0–8)land_cover_probabilities.tif – DW class probabilitiesvegetation_mask.tif – combined vegetation probability mask (trees/grass/crops/shrub/flooded_vegetation)agnived_cover_analysis.png – 6-panel land-cover visualizationmask_*.png – per-class binary masksmetadata.json – AOI + statisticsHyperspectral models/
Placeholder for advanced models:
bigearth/ – BigEarthNet experiments (rdnet/convnext/etc)Prithvi model/ – Prithvi EO 2.0 experiments (commented in download_models.py)Python Backend/
Main backend code:
main_api.py – FastAPI app (described in detail below)reben/, reben_publication/ – local clone/extract of the reBEN BigEarthNet v2.0 model code (rdnet, convnext, etc.)S2 Landcover pipeline/ – Dynamic World + Sentinel-2 land-cover pipelineS2 Vegetation Classification pipeline/ – BigEarthNet rdnet S2 vegetation classification pipelineVideo_inference_engine/ – YouTube video/live inference engine for wildlife detection & classificationvideo_results/ – JSON and other outputs from video inferenceTest_CameraTraps/
Developer/test scripts for the wildlife pipeline:
Test_models.py – validates single-image detection and species classification modelstest_video.py – reference implementation of local video real-time inferenceTest_results_DW/
Earlier test outputs from the land-cover pipeline (metadata.json, etc.)
Test_Satellite/
Prototyping and research notebooks/scripts for:
TestClassificationDownload.py)TestBigEarthrdnet.py, TestBigEarthS1S2.py)reben_publication/)agnived_env at the repo rootTo recreate:
python3.11 -m venv agnived_env
source agnived_env/bin/activate # On Windows: agnived_env\Scripts\activate
pip install -r agniv_requirements.txt # (or subset)
Environment includes (core):
fastapi, uvicornearthengine-api, geemap, rasterio, numpy, matplotlibPytorchWildlife, torch, torchvision, ultralyticsyt-dlp, opencv-pythonlightning, timm, transformers (for reBEN / future models)Note: agniv_requirements.txt is Linux-centric and includes ROS, GDAL, etc. For Windows, installation is typically done with a subset (only what's needed for these pipelines).
Implementation:
Python Backend/S2 Landcover pipeline/Download_Classify.py
Key public pieces:
s2_landcover.AOIConfigs2_landcover.DownloadConfigs2_landcover.run_landcover_pipeline(aoi_cfg, dl_cfg) – End-to-end pipelineSentinel-2: COPERNICUS/S2_SR_HARMONIZED
Bands used:
["B1","B2","B3","B4","B5","B6","B7","B8","B8A","B9","B11","B12"]
Dynamic World: GOOGLE/DYNAMICWORLD/V1
Classes 0–8:
Probability bands:
["water","trees","grass","flooded_vegetation","crops","shrub_and_scrub","built","bare","snow_and_ice"]
AOI is defined as:
(lon, lat)bounds() → rectangular AOIinit_ee (called from API):
ee.Initialize(project=ee_project)ee.Authenticate() → then ee.Initializecreate_aoi:
ee.Geometry.Point([lon, lat])buffer_km * 1000 m.bounds() for a rectangular patchload_sentinel2(aoi, cfg):
date_start–date_end, CLOUDY_PIXEL_PERCENTAGE < cloud_cover_maxcreate_sentinel2_composite(collection):
download_geotiff:
image.getDownloadURL with:
scale = cfg.scale (typically 10 m)region = aoifilePerBand = False, format = GEO_TIFFsentinel2_hyperspectral.tif in output_dirload_dynamic_world(aoi, cfg):
create_classification_composite(dw_collection):
create_probability_composite(dw_collection):
Download to:
land_cover_classification.tif (bands=['label'])land_cover_probabilities.tif (all probability bands)create_vegetation_mask(dw_collection, threshold):
Uses DW probability bands for vegetation-related classes:
Process:
Reducer.max()veg_mask = [p_max ≥ threshold]vegetation_mask.tifcalculate_statistics(classification_path, cfg):
np.uniquescale²pixels, area_km2, percentage, descriptioncreate_visualizations:
Creates AgniVed cover analysis 6-panel PNG:
Also:
create_class_masks to create per-class binary mask PNGssave_metadata(stats, aoi, cfg):
metadata.jsonrun_landcover_pipeline(aoi_cfg, dl_cfg):
sentinel2, classification, probabilities, vegetation_mask, visualization, metadataBackend implementation:
Python Backend/S2 Vegetation Classification pipeline/Vegetation_Classification_pipeline.py
Base reference for behaviour:
TestBigEarthrdnet.py and BigEarthNetv2_0_ImageClassifier.py
reBEN BigEarthNet v2.0 S2-only model: rdnet_base-s2-v0.2.0
Band order:
["B02","B03","B04","B05","B06","B07","B08","B8A","B11","B12"]
# (aliases ["B2", "B3", ...])
Classes (19):
(see BEN_CLASSES in the pipeline file)
Normalization:
DN-space band statistics [BEN_MEAN, BEN_STD] (copied from test script):
tile_norm = (tile - BEN_MEAN) / BEN_STD
Dataclasses:
AOIConfig:
lon: float
lat: float
buffer_m: int # buffer radius in meters
BigEarthConfig:
date_start: str
date_end: str
cloud_cover_max: float
scale: int = 10 # meters per pixel
BigEarthResult:
Contains:
aoi: AOIConfigcube_path: path to downloaded S2 stackviz_path: combined PNG (true color + rdnet class map + confidence)class_distribution: per-class tile percentagetile_counts: per-class tile countsavg_confidence: mean of top-1 probabilitiestiles_shape: number of tiles in grid (H_tiles, W_tiles)init_earth_engine mirrors TestBigEarthrdnet:
ee.Initialize(project="our-lamp-465108-a9") or ee.Authenticate()build_aoi:
buffer_m → .bounds()build_single_composite(aoi, cfg):
Uses COPERNICUS/S2_SR_HARMONIZED:
date_start–date_end, CLOUDY_PIXEL_PERCENTAGE < cloud_cover_maxselect(S2_BANDS)This replicates the behaviour of download_single_composite in TestBigEarthrdnet.py.
download_composite:
image.getDownloadURL → bigearth_s2_stack.tif in temporary dirread_cube:
rasterio.read() → (10, H, W) arraytile_cube:
PATCH_SIZE = 120H // 120 > 0 and W // 120 > 0 → approx 1.2 km × 1.2 km at 10 mnormalize_tiles:
BEN_MEAN and BEN_STD from reBENload_reben_model:
Uses local BigEarthNetv2_0_ImageClassifier.py.
Calls:
BigEarthNetv2_0_ImageClassifier(
model_name="rdnet_base",
bands="s2",
ckpt_path="...",
device=device
)
logits = model(tensor) → probs = sigmoid(logits)class_map (2D grid)conf_map (2D grid)Distribution & summary:
np.bincount over class_map → tile counts per BigEarthNet classTrue color: uses S2 bands B4 (R), B3 (G), B2 (B), normalized to [0,1]
Produces a 3-panel figure:
Saves to bigearth_rdnet_s2_results.png under a Results/ folder in the S2 Vegetation pipeline.
run_bigearth_rdnet(aoi_cfg, be_cfg=None, device=None, out_dir=None, veg_mask_path=None)
(internally extended in main_api.py to optionally intersect with a vegetation_mask.tif if required)
Code references:
Test_models.pytest_video.pydownload_models.pyBackend engine:
Python Backend/Video_inference_engine/Video_inference.py
Models:
Detection:
pw_detection.MegaDetectorV6(device=..., pretrained=True, version="MDV6-yolov10-e")
Detects: Animal, Person, VehicleClassification:
test_video.py defines VideoWildlifeDetector with:
load_models():
detect_frame:
FRAME_SKIP'th frame:
/tmp/frame_<idx>.jpgMegaDetectorV6.single_image_detection on that filematch_detection_to_track:
classify_detection_realtime:
/tmp/classify_crop.jpgsingle_image_classificationFrame buffer per track:
CLASSIFICATION_FRAMES_PER_TRACK), classification is performed using the best detection framedraw_realtime_annotations:
process_video_realtime:
cv2.VideoCapture, fps, total_framescv2.imshowsave_resultsImplementation:
Python Backend/Video_inference_engine/Video_inference.py
FRAME_SKIP = 2
CONF_THRESHOLD = 0.1
IOU_THRESHOLD = 0.3
TRACK_TIMEOUT = 30
CLASSIFICATION_FRAMES_PER_TRACK = 3
BUFFER_MINUTES = 10
VideoWildlifeDetector mirrors the logic of the test script:
load_models()
iou(box1, box2)
detect_animals(frame_rgb, frame_idx)
/tmp/frame_{frame_idx}.jpgcategory_id == 0 (Animal) detectionsmatch_detection_to_track(detection, frame_idx)
TRACK_TIMEOUT frames memoryclassify_detection(frame_rgb, bbox)
/tmp/classify_crop.jpgsingle_image_classificationdraw_annotations(frame_rgb, frame_idx, total_frames=None)
process_stream_frame(frame_rgb, frame_idx, total_frames=None)
Called for each new frame by the YouTube loop.
Every FRAME_SKIP frames:
detect_animalsOn intermediate frames:
Returns annotated RGB frame.
save_results(video_name, fps)
video_results/<video_name>_results.jsonget_youtube_stream_url(youtube_url: str) -> str
yt_dlp to resolve the best MP4 stream URL (≤720p)run_youtube_live_inference(youtube_url: str)
Process:
get_youtube_stream_urlcv2.VideoCapture on the stream URL(frame_idx, frame) covering up to BUFFER_MINUTES * 60 * fps frames (≈ last 10 minutes)In each loop:
detector.process_stream_frame(frame_rgb, frame_idx)Future enhancement: second pass over the buffered frames, akin to processing a small 10-minute clip with better context.
Current behaviour: near-real-time inference on incoming frames with a rolling buffer.
Location:
Python Backend/main_api.py
This file exposes all major functionality as REST endpoints.
To avoid import-path issues and keep test code separate, the backend dynamically loads the land-cover and vegetation modules via:
load_module_from_path
It binds:
s2_landcover.AOIConfig, DownloadConfig, run_landcover_pipelines2_vegetation.AOIConfig, run_bigearth_rdnet (lazy-loaded)Pydantic models:
LandcoverRequest:
lon: float
lat: float
buffer_km: float = 0.6 # defaults 0.6 → 600 m AOI radius
date_start: str
date_end: str
scale: int
cloud_cover_max: float
VegetationRequest:
lon: float
lat: float
buffer_m: int = 600
use_mask: bool # whether to intersect with vegetation_mask.tif from land-cover step
VideoRequest:
youtube_url: str
PipelineRequest:
Combined config for both land-cover and vegetation:
lon: float
lat: float
buffer_km: float # for DW
buffer_m: int # for BigEarth
date_start: str
date_end: str
scale: int
cloud_cover_max: float
/landcover/dw (POST)Runs the Dynamic World + Sentinel-2 land-cover pipeline.
Input: LandcoverRequest
Implementation:
@app.post("/landcover/dw")
async def run_landcover_analysis(req: LandcoverRequest):
# Build AOI and DownloadConfig
aoi_cfg = DW_AOIConfig(lon=req.lon, lat=req.lat, buffer_km=req.buffer_km)
dl_cfg = DownloadConfig(
date_start=req.date_start,
date_end=req.date_end,
scale=req.scale,
cloud_cover_max=req.cloud_cover_max,
output_dir="Final_Res_DW"
)
# Run pipeline
results = run_landcover_pipeline(aoi_cfg, dl_cfg)
# Return paths
return {
"status": "success",
"results": results
}
Returns:
{
"status": "success",
"results": {
"sentinel2": "path/to/sentinel2_hyperspectral.tif",
"classification": "path/to/land_cover_classification.tif",
"probabilities": "path/to/land_cover_probabilities.tif",
"vegetation_mask": "path/to/vegetation_mask.tif",
"visualization": "path/to/agnived_cover_analysis.png",
"metadata": "path/to/metadata.json"
}
}
/vegetation/bigearth (POST)Runs the BigEarthNet rdnet S2 vegetation classifier.
Input: VegetationRequest
Optionally uses vegetation mask from land-cover step:
veg_mask_path = None
if req.use_mask:
veg_mask_path = "Final_Res_DW/vegetation_mask.tif"
Implementation:
@app.post("/vegetation/bigearth")
async def run_vegetation_classification(req: VegetationRequest):
# Lazy-load vegetation module
if s2_vegetation is None:
load_vegetation_module()
# Build AOI
aoi_cfg = VEG_AOIConfig(lon=req.lon, lat=req.lat, buffer_m=req.buffer_m)
# Determine mask path
veg_mask_path = "Final_Res_DW/vegetation_mask.tif" if req.use_mask else None
# Run BigEarth pipeline
result = s2_vegetation.run_bigearth_rdnet(
aoi_cfg,
veg_mask_path=veg_mask_path
)
return {
"status": "success",
"result": result
}
Returns:
{
"status": "success",
"result": {
"aoi": {...},
"cube_path": "path/to/bigearth_s2_stack.tif",
"viz_path": "path/to/bigearth_rdnet_s2_results.png",
"class_distribution": {...},
"tile_counts": {...},
"avg_confidence": 0.85,
"tiles_shape": [10, 10]
}
}
Note: This endpoint expects that /landcover/dw has already been run if use_mask is True.
/video/classify (POST)Starts a background YouTube live inference session.
Input: VideoRequest with youtube_url
Implementation:
@app.post("/video/classify")
async def classify_video(req: VideoRequest):
# Start inference in background thread
thread = threading.Thread(
target=run_youtube_live_inference,
args=(req.youtube_url,),
daemon=True
)
thread.start()
return {
"status": "started",
"message": "YouTube inference running in background",
"url": req.youtube_url
}
Returns immediately:
{
"status": "started",
"message": "YouTube inference running in background",
"url": "https://youtube.com/watch?v=..."
}
A local OpenCV window will appear on the machine where the backend is running, showing the annotated video. This is intended for local prototyping.
/files/image (GET)Serves any generated image/GeoTIFF under the project root.
Query param: path (absolute or project-relative)
Validates: that the resolved path stays under PROJECT_ROOT
Returns: a FileResponse
Useful for the frontend dashboard to display:
agnived_cover_analysis.pngvegetation_mask.tifExample:
GET /files/image?path=Final_Res_DW/agnived_cover_analysis.png
/pipeline/run (POST)Runs both land-cover and vegetation sequences in order.
Input: PipelineRequest
Steps:
/landcover/dw logic:
DW_AOIConfig, DownloadConfig, run_landcover_pipelinevegetation_mask.tif pathrun_bigearth_rdnet with AOI in meters and veg_mask_pathReturns combined JSON:
{
"status": "success",
"landcover": {...},
"vegetation": {...}
}
From the repo root:
cd "Python Backend"
python main_api.py
This runs:
uvicorn.run("main_api:app", host="0.0.0.0", port=8000, reload=True)
API will be available at http://localhost:8000
Interactive docs: http://localhost:8000/docs
User picks AOI on frontend:
(lon, lat), buffer (km)Backend /landcover/dw:
Final_Res_DWFrontend:
/files/image to fetch:
agnived_cover_analysis.png (6-panel view)Backend /vegetation/bigearth or /pipeline/run:
Frontend:
User chooses a YouTube livestream or video in the UI
Frontend posts to /video/classify with youtube_url
Backend:
run_youtube_live_inference in backgroundFuture enhancements:
Earth Engine project: our-lamp-465108-a9 is used in all EE initialization calls
Band scaling:
Sentinel-2 SR values (0–10000 or reflectance) are assumed numerically compatible with reBEN DN normalization. This matches the reference scripts.
AOI sizes:
YouTube streaming:
yt_dlp to resolve and open one MP4 stream (≤720p)Local only:
The video inference is currently designed for local desktop use (display window on the backend machine)
Planned or easy future extensions:
Temporal options for land-cover and BigEarth:
Multiple AOI tiles:
Prithvi + SAR/optical fusion:
Hyperspectral models/Prithvi model/ + TestBigEarthS1S2.pyWebSocket-based video streaming:
Authentication & user data:
For issues or questions, please refer to the project documentation or contact the development team.
15 commits
Python
78.2%
JavaScript
8.9%
TypeScript
6.8%
CSS
6.0%