biodex 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- app.py +344 -0
- biodex/__init__.py +14 -0
- biodex/__main__.py +8 -0
- biodex-1.0.1.dist-info/METADATA +181 -0
- biodex-1.0.1.dist-info/RECORD +46 -0
- biodex-1.0.1.dist-info/WHEEL +4 -0
- biodex-1.0.1.dist-info/entry_points.txt +4 -0
- biodex-1.0.1.dist-info/licenses/LICENSE +21 -0
- core/__init__.py +55 -0
- core/analytics.py +113 -0
- core/audit_log.py +63 -0
- core/batch.py +259 -0
- core/batch_report.py +73 -0
- core/classifier.py +83 -0
- core/cli.py +464 -0
- core/config.py +99 -0
- core/detector.py +290 -0
- core/exif_utils.py +122 -0
- core/exports.py +635 -0
- core/models/__init__.py +38 -0
- core/models/base.py +72 -0
- core/models/edge.py +72 -0
- core/models/megadetector.py +151 -0
- core/models/registry.py +136 -0
- core/models/speciesnet.py +258 -0
- core/progress.py +79 -0
- core/quick_demo.py +152 -0
- core/types.py +268 -0
- core/ui_entry.py +16 -0
- core/video.py +230 -0
- core/visualization.py +364 -0
- desktop/README.md +12 -0
- desktop/__init__.py +1 -0
- desktop/launcher.py +110 -0
- ui/__init__.py +1 -0
- ui/api_menu.py +73 -0
- ui/components.py +543 -0
- ui/favicon.png +0 -0
- ui/handlers.py +759 -0
- ui/llm_review.py +142 -0
- ui/llm_settings.py +555 -0
- ui/settings_store.py +46 -0
- ui/styles.py +1389 -0
- ui/tabs.py +205 -0
- ui/tree_of_life_background.avif +0 -0
- ui/tree_of_life_background.jpg +0 -0
app.py
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BioDex — Local AI for Wildlife Camera Traps (v1.0)
|
|
3
|
+
|
|
4
|
+
Gradio web UI with tabbed Dashboard, Batch, Video, Analytics, and Settings.
|
|
5
|
+
All inference runs locally.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import threading
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, cast
|
|
14
|
+
|
|
15
|
+
import gradio as gr
|
|
16
|
+
from core.config import get_settings
|
|
17
|
+
from core.detector import warmup_models
|
|
18
|
+
from core.types import BIODEX_VERSION
|
|
19
|
+
from ui.api_menu import build_api_menu
|
|
20
|
+
from ui.components import footer_chips_html, footer_tagline_html, header_html
|
|
21
|
+
from ui.handlers import (
|
|
22
|
+
ai_review_frame,
|
|
23
|
+
analyze_batch,
|
|
24
|
+
analyze_spot_check,
|
|
25
|
+
analyze_video_ui,
|
|
26
|
+
apply_settings,
|
|
27
|
+
clear_batch_review,
|
|
28
|
+
clear_llm_settings,
|
|
29
|
+
compute_analytics,
|
|
30
|
+
load_lila_cache,
|
|
31
|
+
on_llm_provider_change,
|
|
32
|
+
refresh_species_status,
|
|
33
|
+
request_cancel,
|
|
34
|
+
run_quick_demo,
|
|
35
|
+
save_llm_settings,
|
|
36
|
+
select_batch_frame,
|
|
37
|
+
test_llm_settings,
|
|
38
|
+
toggle_api_menu,
|
|
39
|
+
)
|
|
40
|
+
from ui.styles import APP_THEME, CUSTOM_CSS, tree_background_css
|
|
41
|
+
from ui.tabs import (
|
|
42
|
+
build_analytics_tab,
|
|
43
|
+
build_batch_tab,
|
|
44
|
+
build_dashboard_tab,
|
|
45
|
+
build_settings_tab,
|
|
46
|
+
build_video_tab,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
LILA_CACHE_DIR = Path.home() / ".cache" / "biodex" / "channel-islands-demo"
|
|
50
|
+
FAVICON_PATH = Path(__file__).resolve().parent / "ui" / "favicon.png"
|
|
51
|
+
TREE_BACKGROUND_PATH = Path(__file__).resolve().parent / "ui" / "tree_of_life_background.jpg"
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_app() -> gr.Blocks:
|
|
56
|
+
"""Construct the tabbed BioDex Gradio application."""
|
|
57
|
+
settings = get_settings()
|
|
58
|
+
page_classes = ["biodex-page"]
|
|
59
|
+
|
|
60
|
+
with gr.Blocks(title=f"BioDex v{BIODEX_VERSION}") as demo:
|
|
61
|
+
review_state = gr.State([])
|
|
62
|
+
batch_paths_state = gr.State([])
|
|
63
|
+
last_batch = gr.State(None)
|
|
64
|
+
|
|
65
|
+
with gr.Column(elem_classes=[*page_classes, "field-device"]):
|
|
66
|
+
gr.HTML(header_html())
|
|
67
|
+
|
|
68
|
+
with gr.Tabs(elem_classes=["biodex-tabs"]):
|
|
69
|
+
build_dashboard_tab(last_batch)
|
|
70
|
+
batch_w = build_batch_tab(settings, review_state, batch_paths_state, last_batch)
|
|
71
|
+
video_w = build_video_tab()
|
|
72
|
+
analytics_w = build_analytics_tab(last_batch)
|
|
73
|
+
settings_w = build_settings_tab()
|
|
74
|
+
|
|
75
|
+
with gr.Column(elem_classes=["field-footer-section"]):
|
|
76
|
+
gr.HTML(footer_tagline_html())
|
|
77
|
+
api_open = gr.State(False)
|
|
78
|
+
api_w = build_api_menu()
|
|
79
|
+
with gr.Row(elem_classes=["field-footer-actions"]):
|
|
80
|
+
api_toggle_btn = gr.Button("Use via API", elem_classes=["field-api-toggle"])
|
|
81
|
+
gr.HTML(footer_chips_html())
|
|
82
|
+
|
|
83
|
+
api_toggle_btn.click(
|
|
84
|
+
fn=toggle_api_menu,
|
|
85
|
+
inputs=[api_open],
|
|
86
|
+
outputs=[
|
|
87
|
+
api_open,
|
|
88
|
+
api_w["api_menu"],
|
|
89
|
+
api_w["llm_provider"],
|
|
90
|
+
api_w["llm_api_key"],
|
|
91
|
+
api_w["llm_model"],
|
|
92
|
+
api_w["llm_base_url"],
|
|
93
|
+
api_w["llm_status"],
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
api_w["llm_provider"].change(
|
|
97
|
+
fn=on_llm_provider_change,
|
|
98
|
+
inputs=[api_w["llm_provider"]],
|
|
99
|
+
outputs=[api_w["llm_model"], api_w["llm_base_url"], api_w["llm_status"]],
|
|
100
|
+
)
|
|
101
|
+
api_w["llm_save_btn"].click(
|
|
102
|
+
fn=save_llm_settings,
|
|
103
|
+
inputs=[
|
|
104
|
+
api_w["llm_provider"],
|
|
105
|
+
api_w["llm_api_key"],
|
|
106
|
+
api_w["llm_model"],
|
|
107
|
+
api_w["llm_base_url"],
|
|
108
|
+
],
|
|
109
|
+
outputs=[api_w["llm_status"]],
|
|
110
|
+
)
|
|
111
|
+
api_w["llm_test_btn"].click(
|
|
112
|
+
fn=test_llm_settings,
|
|
113
|
+
inputs=[
|
|
114
|
+
api_w["llm_provider"],
|
|
115
|
+
api_w["llm_api_key"],
|
|
116
|
+
api_w["llm_model"],
|
|
117
|
+
api_w["llm_base_url"],
|
|
118
|
+
],
|
|
119
|
+
outputs=[api_w["llm_status"]],
|
|
120
|
+
)
|
|
121
|
+
api_w["llm_clear_btn"].click(
|
|
122
|
+
fn=clear_llm_settings,
|
|
123
|
+
outputs=[
|
|
124
|
+
api_w["llm_provider"],
|
|
125
|
+
api_w["llm_api_key"],
|
|
126
|
+
api_w["llm_model"],
|
|
127
|
+
api_w["llm_base_url"],
|
|
128
|
+
api_w["llm_status"],
|
|
129
|
+
],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# Batch tab wiring
|
|
133
|
+
batch_w["classify_species"].change(
|
|
134
|
+
fn=refresh_species_status,
|
|
135
|
+
inputs=[batch_w["classify_species"]],
|
|
136
|
+
outputs=[batch_w["species_status"]],
|
|
137
|
+
)
|
|
138
|
+
batch_w["load_cache_btn"].click(
|
|
139
|
+
fn=load_lila_cache,
|
|
140
|
+
outputs=[batch_w["batch_paths_state"], batch_w["batch_status"]],
|
|
141
|
+
)
|
|
142
|
+
batch_w["quick_demo_btn"].click(
|
|
143
|
+
fn=run_quick_demo,
|
|
144
|
+
inputs=[batch_w["batch_files"], batch_w["batch_paths_state"], batch_w["threshold"]],
|
|
145
|
+
outputs=[
|
|
146
|
+
batch_w["batch_stats"],
|
|
147
|
+
batch_w["batch_table"],
|
|
148
|
+
batch_w["batch_status"],
|
|
149
|
+
batch_w["species_status"],
|
|
150
|
+
batch_w["batch_csv_btn"],
|
|
151
|
+
batch_w["batch_json_btn"],
|
|
152
|
+
batch_w["batch_zip_btn"],
|
|
153
|
+
batch_w["batch_wi_btn"],
|
|
154
|
+
batch_w["batch_inat_btn"],
|
|
155
|
+
batch_w["batch_eco_btn"],
|
|
156
|
+
batch_w["review_state"],
|
|
157
|
+
batch_w["review_original"],
|
|
158
|
+
batch_w["review_annotated"],
|
|
159
|
+
batch_w["frame_label"],
|
|
160
|
+
batch_w["frame_detections"],
|
|
161
|
+
batch_w["review_panel"],
|
|
162
|
+
last_batch,
|
|
163
|
+
],
|
|
164
|
+
show_progress="minimal",
|
|
165
|
+
)
|
|
166
|
+
batch_w["batch_btn"].click(
|
|
167
|
+
fn=analyze_batch,
|
|
168
|
+
inputs=[
|
|
169
|
+
batch_w["batch_files"],
|
|
170
|
+
batch_w["batch_paths_state"],
|
|
171
|
+
batch_w["threshold"],
|
|
172
|
+
batch_w["classify_species"],
|
|
173
|
+
],
|
|
174
|
+
outputs=[
|
|
175
|
+
batch_w["batch_stats"],
|
|
176
|
+
batch_w["batch_table"],
|
|
177
|
+
batch_w["batch_status"],
|
|
178
|
+
batch_w["species_status"],
|
|
179
|
+
batch_w["batch_csv_btn"],
|
|
180
|
+
batch_w["batch_json_btn"],
|
|
181
|
+
batch_w["batch_zip_btn"],
|
|
182
|
+
batch_w["batch_wi_btn"],
|
|
183
|
+
batch_w["batch_inat_btn"],
|
|
184
|
+
batch_w["batch_eco_btn"],
|
|
185
|
+
batch_w["review_state"],
|
|
186
|
+
batch_w["review_original"],
|
|
187
|
+
batch_w["review_annotated"],
|
|
188
|
+
batch_w["frame_label"],
|
|
189
|
+
batch_w["frame_detections"],
|
|
190
|
+
batch_w["review_panel"],
|
|
191
|
+
last_batch,
|
|
192
|
+
],
|
|
193
|
+
show_progress="minimal",
|
|
194
|
+
)
|
|
195
|
+
for _reset_btn in (batch_w["batch_btn"], batch_w["quick_demo_btn"], batch_w["clear_btn"]):
|
|
196
|
+
_reset_btn.click(
|
|
197
|
+
fn=lambda: (None, ""),
|
|
198
|
+
outputs=[batch_w["selected_frame_index"], batch_w["ai_review_output"]],
|
|
199
|
+
)
|
|
200
|
+
batch_w["cancel_btn"].click(fn=request_cancel, outputs=[batch_w["batch_status"]])
|
|
201
|
+
batch_w["batch_table"].select(
|
|
202
|
+
fn=select_batch_frame,
|
|
203
|
+
inputs=[batch_w["review_state"]],
|
|
204
|
+
outputs=[
|
|
205
|
+
batch_w["review_original"],
|
|
206
|
+
batch_w["review_annotated"],
|
|
207
|
+
batch_w["frame_label"],
|
|
208
|
+
batch_w["frame_detections"],
|
|
209
|
+
batch_w["selected_frame_index"],
|
|
210
|
+
batch_w["ai_review_output"],
|
|
211
|
+
],
|
|
212
|
+
)
|
|
213
|
+
batch_w["ai_review_btn"].click(
|
|
214
|
+
fn=ai_review_frame,
|
|
215
|
+
inputs=[batch_w["review_state"], batch_w["selected_frame_index"]],
|
|
216
|
+
outputs=[batch_w["ai_review_output"]],
|
|
217
|
+
show_progress="minimal",
|
|
218
|
+
)
|
|
219
|
+
batch_w["clear_btn"].click(
|
|
220
|
+
fn=clear_batch_review,
|
|
221
|
+
outputs=[
|
|
222
|
+
batch_w["batch_stats"],
|
|
223
|
+
batch_w["batch_table"],
|
|
224
|
+
batch_w["batch_status"],
|
|
225
|
+
batch_w["species_status"],
|
|
226
|
+
batch_w["batch_csv_btn"],
|
|
227
|
+
batch_w["batch_json_btn"],
|
|
228
|
+
batch_w["batch_zip_btn"],
|
|
229
|
+
batch_w["batch_wi_btn"],
|
|
230
|
+
batch_w["batch_inat_btn"],
|
|
231
|
+
batch_w["batch_eco_btn"],
|
|
232
|
+
batch_w["review_state"],
|
|
233
|
+
batch_w["review_original"],
|
|
234
|
+
batch_w["review_annotated"],
|
|
235
|
+
batch_w["frame_label"],
|
|
236
|
+
batch_w["frame_detections"],
|
|
237
|
+
batch_w["review_panel"],
|
|
238
|
+
batch_w["batch_paths_state"],
|
|
239
|
+
last_batch,
|
|
240
|
+
],
|
|
241
|
+
)
|
|
242
|
+
batch_w["analyze_one_btn"].click(
|
|
243
|
+
fn=analyze_spot_check,
|
|
244
|
+
inputs=[batch_w["input_image"], batch_w["threshold"], batch_w["classify_species"]],
|
|
245
|
+
outputs=[
|
|
246
|
+
batch_w["spot_original"],
|
|
247
|
+
batch_w["spot_annotated"],
|
|
248
|
+
batch_w["spot_stats"],
|
|
249
|
+
batch_w["spot_table"],
|
|
250
|
+
batch_w["batch_status"],
|
|
251
|
+
],
|
|
252
|
+
show_progress="minimal",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Video tab
|
|
256
|
+
video_w["video_analyze_btn"].click(
|
|
257
|
+
fn=analyze_video_ui,
|
|
258
|
+
inputs=[
|
|
259
|
+
video_w["video_file"],
|
|
260
|
+
video_w["video_threshold"],
|
|
261
|
+
video_w["video_classify"],
|
|
262
|
+
video_w["video_fps"],
|
|
263
|
+
video_w["video_max_frames"],
|
|
264
|
+
],
|
|
265
|
+
outputs=[
|
|
266
|
+
video_w["video_status"],
|
|
267
|
+
video_w["video_timeline_btn"],
|
|
268
|
+
video_w["video_gallery"],
|
|
269
|
+
video_w["video_results_panel"],
|
|
270
|
+
],
|
|
271
|
+
show_progress="minimal",
|
|
272
|
+
)
|
|
273
|
+
video_w["video_cancel_btn"].click(fn=request_cancel, outputs=[video_w["video_status"]])
|
|
274
|
+
|
|
275
|
+
# Analytics tab
|
|
276
|
+
analytics_w["analytics_refresh"].click(
|
|
277
|
+
fn=compute_analytics,
|
|
278
|
+
inputs=[last_batch],
|
|
279
|
+
outputs=[
|
|
280
|
+
analytics_w["diversity_html"],
|
|
281
|
+
analytics_w["heatmap_image"],
|
|
282
|
+
analytics_w["species_chart"],
|
|
283
|
+
analytics_w["analytics_results_panel"],
|
|
284
|
+
],
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
# Settings tab
|
|
288
|
+
settings_w["settings_save"].click(
|
|
289
|
+
fn=apply_settings,
|
|
290
|
+
inputs=[
|
|
291
|
+
settings_w["settings_threshold"],
|
|
292
|
+
settings_w["settings_geofence"],
|
|
293
|
+
],
|
|
294
|
+
outputs=[
|
|
295
|
+
batch_w["threshold"],
|
|
296
|
+
settings_w["settings_status"],
|
|
297
|
+
],
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return cast(gr.Blocks, demo)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _start_model_warmup(*, species: bool = False) -> None:
|
|
304
|
+
"""Load MegaDetector in the background so Process Folder starts faster."""
|
|
305
|
+
|
|
306
|
+
def _run() -> None:
|
|
307
|
+
try:
|
|
308
|
+
warmup_models(species=species)
|
|
309
|
+
logger.info("Model warmup complete (species=%s).", species)
|
|
310
|
+
except Exception as exc:
|
|
311
|
+
logger.warning("Model warmup failed: %s", exc)
|
|
312
|
+
|
|
313
|
+
threading.Thread(target=_run, daemon=True, name="biodex-warmup").start()
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def launch_app() -> None:
|
|
317
|
+
"""Build and launch the Gradio UI (console entry: ``biodex-ui``)."""
|
|
318
|
+
settings = get_settings()
|
|
319
|
+
host = settings.host
|
|
320
|
+
port = settings.port
|
|
321
|
+
css = CUSTOM_CSS + tree_background_css()
|
|
322
|
+
print(f"BioDex v{BIODEX_VERSION} at http://{host}:{port}")
|
|
323
|
+
print("Open the Batch tab to process a folder, or try Quick demo.")
|
|
324
|
+
_start_model_warmup(species=True)
|
|
325
|
+
app = build_app()
|
|
326
|
+
if settings.enable_queue:
|
|
327
|
+
app.queue(default_concurrency_limit=2)
|
|
328
|
+
biodex_cache = Path.home() / ".cache" / "biodex"
|
|
329
|
+
launch_kwargs: dict[str, Any] = {
|
|
330
|
+
"server_name": host,
|
|
331
|
+
"server_port": port,
|
|
332
|
+
"theme": APP_THEME,
|
|
333
|
+
"css": css,
|
|
334
|
+
"auth": settings.gradio_auth,
|
|
335
|
+
"show_error": True,
|
|
336
|
+
"allowed_paths": [str(biodex_cache), str(LILA_CACHE_DIR), str(TREE_BACKGROUND_PATH.parent)],
|
|
337
|
+
}
|
|
338
|
+
if FAVICON_PATH.is_file():
|
|
339
|
+
launch_kwargs["favicon_path"] = str(FAVICON_PATH)
|
|
340
|
+
app.launch(**launch_kwargs)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == "__main__":
|
|
344
|
+
launch_app()
|
biodex/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""BioDex top-level package shim.
|
|
2
|
+
|
|
3
|
+
The implementation lives in the ``core``, ``ui``, and ``desktop`` packages.
|
|
4
|
+
This thin namespace re-exports the stable public surface and the version so
|
|
5
|
+
that ``import biodex`` and ``python -m biodex`` behave as users expect.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from core.cli import main
|
|
11
|
+
from core.types import BIODEX_VERSION
|
|
12
|
+
|
|
13
|
+
__version__ = BIODEX_VERSION
|
|
14
|
+
__all__ = ["main", "__version__"]
|
biodex/__main__.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: biodex
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Local, privacy-first AI for wildlife camera trap analysis and conservation monitoring
|
|
5
|
+
Author: FratresMedAI
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: biodiversity,camera-trap,conservation,defensive-monitoring,megadetector,speciesnet,wildlife,wildlife-insights
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
|
20
|
+
Requires-Python: <3.13,>=3.10
|
|
21
|
+
Requires-Dist: numpy<2.0,>=1.26.4
|
|
22
|
+
Requires-Dist: pandas>=2.1
|
|
23
|
+
Requires-Dist: pillow>=9.5
|
|
24
|
+
Requires-Dist: setuptools<81.0,>=65.0
|
|
25
|
+
Requires-Dist: tqdm>=4.64
|
|
26
|
+
Requires-Dist: typer>=0.12
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: gradio<7.0,>=5.0; extra == 'all'
|
|
29
|
+
Requires-Dist: matplotlib>=3.7; extra == 'all'
|
|
30
|
+
Requires-Dist: megadetector<11.0,>=10.0; extra == 'all'
|
|
31
|
+
Requires-Dist: mypy>=1.10; extra == 'all'
|
|
32
|
+
Requires-Dist: onnxruntime>=1.16; extra == 'all'
|
|
33
|
+
Requires-Dist: opencv-python-headless>=4.8; extra == 'all'
|
|
34
|
+
Requires-Dist: pyinstaller>=6.0; extra == 'all'
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == 'all'
|
|
36
|
+
Requires-Dist: ruff>=0.4; extra == 'all'
|
|
37
|
+
Requires-Dist: seaborn>=0.13; extra == 'all'
|
|
38
|
+
Requires-Dist: speciesnet<6.0,>=5.0; extra == 'all'
|
|
39
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
40
|
+
Requires-Dist: types-pillow; extra == 'all'
|
|
41
|
+
Provides-Extra: analytics
|
|
42
|
+
Requires-Dist: matplotlib>=3.7; extra == 'analytics'
|
|
43
|
+
Requires-Dist: seaborn>=0.13; extra == 'analytics'
|
|
44
|
+
Provides-Extra: desktop
|
|
45
|
+
Requires-Dist: pyinstaller>=6.0; extra == 'desktop'
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
48
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
49
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
50
|
+
Requires-Dist: types-pillow; extra == 'dev'
|
|
51
|
+
Provides-Extra: edge
|
|
52
|
+
Requires-Dist: onnxruntime>=1.16; (platform_system != 'Darwin' or platform_machine != 'arm64') and extra == 'edge'
|
|
53
|
+
Provides-Extra: models
|
|
54
|
+
Requires-Dist: megadetector<11.0,>=10.0; extra == 'models'
|
|
55
|
+
Requires-Dist: speciesnet<6.0,>=5.0; extra == 'models'
|
|
56
|
+
Requires-Dist: torch>=2.0; extra == 'models'
|
|
57
|
+
Provides-Extra: ui
|
|
58
|
+
Requires-Dist: gradio<7.0,>=5.0; extra == 'ui'
|
|
59
|
+
Provides-Extra: video
|
|
60
|
+
Requires-Dist: opencv-python-headless>=4.8; extra == 'video'
|
|
61
|
+
Description-Content-Type: text/markdown
|
|
62
|
+
|
|
63
|
+
# BioDex
|
|
64
|
+
|
|
65
|
+
**Local AI for wildlife camera traps.** Detect animals, filter blanks, identify species, export results — on your machine, not in the cloud.
|
|
66
|
+
|
|
67
|
+
[](https://www.python.org/)
|
|
68
|
+
[](LICENSE)
|
|
69
|
+
[](CHANGELOG.md)
|
|
70
|
+
[](https://github.com/FratresMedAI/BioDex/actions/workflows/ci.yml)
|
|
71
|
+
[](https://github.com/FratresMedAI/BioDex/actions/workflows/release.yml)
|
|
72
|
+
|
|
73
|
+
Built for conservation research, field review, and defensive wildlife monitoring (Fratres / EcoSentinel integration hooks).
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Run locally (do this)
|
|
78
|
+
|
|
79
|
+
**Mac / Linux**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git clone https://github.com/FratresMedAI/BioDex.git
|
|
83
|
+
cd BioDex
|
|
84
|
+
./run_biodex.sh
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Windows**
|
|
88
|
+
|
|
89
|
+
```bat
|
|
90
|
+
git clone https://github.com/FratresMedAI/BioDex.git
|
|
91
|
+
cd BioDex
|
|
92
|
+
run_biodex.bat
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Your browser opens **http://127.0.0.1:7860**. Use the **Batch** tab to process a folder, or **Quick demo** for a fast preview.
|
|
96
|
+
|
|
97
|
+
First analysis downloads models once (~500 MB). After that, everything stays offline on your computer.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## v1.0 highlights
|
|
102
|
+
|
|
103
|
+
- **Pluggable models** — registry architecture (`core/models/`) with MegaDetector + SpeciesNet adapters
|
|
104
|
+
- **Batch performance** — chunking, cancel, ETA progress, optional `torch.compile`
|
|
105
|
+
- **Video foundations** — frame sampling + timeline export (`biodex video`, requires `[video]` extra)
|
|
106
|
+
- **Advanced exports** — Wildlife Insights, iNaturalist drafts, timelapse JSON, SQLite, EcoSentinel hook
|
|
107
|
+
- **Tabbed UI** — Dashboard, Batch, Video, Analytics, Settings
|
|
108
|
+
- **Optional AI review (BYOK)** — per-frame LLM notes after batch runs (see below)
|
|
109
|
+
- **Docker** — CPU and GPU images for deployment
|
|
110
|
+
- **Release maturity** — stable API surface, strict typing/linting, and CI-gated quality
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Optional AI review (BYOK)
|
|
115
|
+
|
|
116
|
+
Core detection runs **fully offline** on your machine. AI review is an **optional** power feature:
|
|
117
|
+
|
|
118
|
+
1. Open **Use via API** in the footer.
|
|
119
|
+
2. Choose a provider, paste your API key, pick a model (or type a custom model ID), then **Save**.
|
|
120
|
+
3. After a batch run, select a frame and click **AI review (LLM)** for a field note: scene summary, species second opinion, and data-quality flags.
|
|
121
|
+
|
|
122
|
+
**Privacy:** API keys are stored locally in `~/.cache/biodex/settings.json` and sent only to the provider you choose — never to BioDex servers. See [SECURITY.md](SECURITY.md).
|
|
123
|
+
|
|
124
|
+
**Scope in v1.0.1:** batch frame review only. Single-image spot check, video key frames, and batch-level summaries are planned for v1.1. Not every model slug in the dropdown is guaranteed to work with every provider — use a custom model ID if needed.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Extras install matrix
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pip install -e ".[ui,models]" # Web UI + inference (default)
|
|
132
|
+
pip install -e ".[video]" # OpenCV video support
|
|
133
|
+
pip install -e ".[analytics]" # Heatmaps + diversity metrics
|
|
134
|
+
pip install -e ".[edge]" # ONNX stubs (future edge deploy)
|
|
135
|
+
pip install -e ".[all]" # Everything
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Batch CLI
|
|
141
|
+
|
|
142
|
+
For large folders (100+ images), no browser:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
biodex batch /path/to/images -o ./results --classify-species --recursive
|
|
146
|
+
biodex batch /path/to/images -o ./results --chunk-size 500 --torch-compile
|
|
147
|
+
biodex video /path/to/clip.mp4 -o ./video-results --fps 1 --max-frames 120
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Environment variables: `BIODEX_DETECTOR_MODEL`, `BIODEX_TORCH_COMPILE`, `BIODEX_GEOFENCE_REGION`, `BIODEX_AUDIT_LOG=1`
|
|
151
|
+
|
|
152
|
+
## Docker quick start
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
docker build -t biodex:cpu -f Dockerfile .
|
|
156
|
+
docker run --rm -p 7860:7860 biodex:cpu
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
GPU:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
docker build -t biodex:gpu -f Dockerfile.gpu .
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Developers
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pip install -e ".[ui,models,dev]"
|
|
171
|
+
pytest tests/ -v -m "not slow"
|
|
172
|
+
ruff check core app.py ui
|
|
173
|
+
mypy core app.py ui
|
|
174
|
+
pre-commit install # optional
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [CHANGELOG.md](CHANGELOG.md), [docs/roadmap.md](docs/roadmap.md), [CONTRIBUTING.md](CONTRIBUTING.md), and [SECURITY.md](SECURITY.md).
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
MIT License. Uses [MegaDetector](https://github.com/agentmorris/MegaDetector) and [SpeciesNet](https://github.com/google/cameratrapai).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
biodex/__init__.py,sha256=yZhDYkaRQ0IibctDfn1Z8wQijw6iYiFqYka_1O40JQM,428
|
|
2
|
+
biodex/__main__.py,sha256=-QUOWVDd3uyUY7OaH55Te9gqRBtFCtAu1CbfKrSAtsY,162
|
|
3
|
+
core/__init__.py,sha256=LymJRor95Bw_GZfnBmP7xACxc25zsy7SydfX9As8sR0,1298
|
|
4
|
+
core/analytics.py,sha256=FfaqQYPePL665lC99YBt3v0U-QiVjP8bp6VajnpX6EQ,4001
|
|
5
|
+
core/audit_log.py,sha256=pbpWVc8ORzw7vkA1zUMNWCxjTluf1y4oSkfoAEYijGE,1851
|
|
6
|
+
core/batch.py,sha256=JuAjUUrUu1-NJN8poM7TUvl8Kb_iyZr7wyR-P3avyiw,8548
|
|
7
|
+
core/batch_report.py,sha256=Dx9UaAT2LmrQeVOF-YDyZjZ0DkIVSUPNgS6krSSo9jo,2539
|
|
8
|
+
core/classifier.py,sha256=G8QWDTlRN_KRyram4No8aqI7dxKEo8a_773ACX1OOTk,2111
|
|
9
|
+
core/cli.py,sha256=tzx4GKBZtoGry5pThxcwraPDWagdWPTvmt4ndhSzlgw,15892
|
|
10
|
+
core/config.py,sha256=qU2RGZ_5UaASou4mVJqtsddm-YbnWmJYDsFhY6w-wg0,2994
|
|
11
|
+
core/detector.py,sha256=zh9uQY-M_CE6n_Ve5WaZnew2dfGtmAnHh0_n7bvEYiE,9417
|
|
12
|
+
core/exif_utils.py,sha256=7Vh6_-QmO5BWhmH0p7AGffrBS9Nq-wTxsuYZeu2zz9E,3586
|
|
13
|
+
core/exports.py,sha256=mIhtSm1TbUFuaO8BydZrNdES5kHf23B23GuH0pWxLkA,19865
|
|
14
|
+
core/progress.py,sha256=VntwzW-On8Ae90UQJnaeXRWaCRdSmKzNrTUDuMTOZew,2532
|
|
15
|
+
core/quick_demo.py,sha256=OI4vg-ZAXKANi7XtPk1zj7sgbBRvdR8gKavQAmThTx4,4238
|
|
16
|
+
core/types.py,sha256=SMhYjLDMXPBG-zKykSO9jbvVEPsrC9GhHYtH9PYYh6I,7321
|
|
17
|
+
core/ui_entry.py,sha256=Pm9MMbN3f5jWo4cCCsLwLmprRQRwBChB3mJqhruRvLo,264
|
|
18
|
+
core/video.py,sha256=uXPmaQc1AGVO87prlmBui2RD-qdbWU9THNGU8jZHh8E,6913
|
|
19
|
+
core/visualization.py,sha256=sUqjv0KJh0IbivNzSdCGWstYIjRVVEq8JBeg8_BrbLI,11566
|
|
20
|
+
core/models/__init__.py,sha256=mMsnqfuPlsZiepmo3gEAXeFH1_zDrsy42MsBKEULvvk,1061
|
|
21
|
+
core/models/base.py,sha256=LZpxN1j-ZymWrSZTbxQpiLEmhPWzEix6Aoefo693u2w,1829
|
|
22
|
+
core/models/edge.py,sha256=xpIGzWUVuP1h69yyljCzflhhCiQNT1rQFB5pTq-UmAY,2232
|
|
23
|
+
core/models/megadetector.py,sha256=3-i3vIvn2xfVGnAyhDYfrljzB94EnJUXMpbDz3o0epY,5138
|
|
24
|
+
core/models/registry.py,sha256=fsL_JPOklVFpzUBz_hhIERdHj7Xjc_DvF9QXWF-MS_4,3876
|
|
25
|
+
core/models/speciesnet.py,sha256=8SLK-HdayoTu7Ik0dhS3zsFFHNlMTy2AWOBkb4wTn0U,8485
|
|
26
|
+
desktop/README.md,sha256=SLB_HIw6xkOXxJmY_I13EdxS4Rw0Yzlvz-zm855ovC4,370
|
|
27
|
+
desktop/__init__.py,sha256=wcZGxqfxuRPXS0ggC59UwivykN0Uu7g-ReaUa5QpePk,43
|
|
28
|
+
desktop/launcher.py,sha256=5QE1FztcQhGtYoV23g5qP0JV0DcDKxCSRicYhOwGjvM,3527
|
|
29
|
+
ui/__init__.py,sha256=fCBSWPVAFRPivSQtPvTele7ZNJMSPDu8Mz0P7Iq4F9Y,32
|
|
30
|
+
ui/api_menu.py,sha256=fwHcq0dRVt1kLRmS7gFCwtb1yXGF6L8AIC4glaINF8g,2678
|
|
31
|
+
ui/components.py,sha256=-bToo0QNul4O7KrCYrtJGoea8Gwc-cjwgvut5SnIdfw,18778
|
|
32
|
+
ui/favicon.png,sha256=jmrkHUTToBEHGZowNX1U2tuULXxPqu13OkRVuZF6-SU,126563
|
|
33
|
+
ui/handlers.py,sha256=SPp5r_7i4WnRVgQh6tHGVNIOuDd8sv8-f1Pk5_wgAqc,24405
|
|
34
|
+
ui/llm_review.py,sha256=xa3bdnU1tx9eoo9DzennbHO6Z2HFR2GabQEDxfXuCks,5585
|
|
35
|
+
ui/llm_settings.py,sha256=5pkJawTgTltZHN1F2pJvEF-ZlEh0vdMsmOpOo5Bv9aU,17839
|
|
36
|
+
ui/settings_store.py,sha256=12UF8juPQNE_Peg_mdCbfE9yYn6c43UUOFKbeIIQWi0,1356
|
|
37
|
+
ui/styles.py,sha256=UluaVxD7eq_3EfkmB8qcm5fVo1hn4HYo5tVq50RkyK8,36895
|
|
38
|
+
ui/tabs.py,sha256=e48IUIxWzXViu0zsJ9uWvnXv7v8PljLHqoho0U6UO1Q,9318
|
|
39
|
+
ui/tree_of_life_background.avif,sha256=eahqA_GlTN11vqt64h6-za3lo60OmHoB2lfixiye8vU,667171
|
|
40
|
+
ui/tree_of_life_background.jpg,sha256=ButfoDF5PPmClSNz6w3xYaGyQB47-Uo7ekV7Fj_JvIM,103634
|
|
41
|
+
app.py,sha256=ULI5rF0c7AQHpYs3Dk_2FG5G2TNs_aBofGAgFpEEKCw,11928
|
|
42
|
+
biodex-1.0.1.dist-info/METADATA,sha256=auIH0eCgQrwjJdH_ZXIEJirGjQ-IRxlL1SwlzmiohXI,7006
|
|
43
|
+
biodex-1.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
44
|
+
biodex-1.0.1.dist-info/entry_points.txt,sha256=o17nKvCdhiMUV3nQgjw4m02mVN31pIS_wn32BDVqZw0,111
|
|
45
|
+
biodex-1.0.1.dist-info/licenses/LICENSE,sha256=IbHSMRrvJuGumMZjY3m6GVnxbrGFlYr1Qp_UqPHb6U0,1076
|
|
46
|
+
biodex-1.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BioDex Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
core/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""BioDex core modules for detection, classification, visualization, and export."""
|
|
2
|
+
|
|
3
|
+
import core.models # noqa: F401 — register default detector/classifier adapters
|
|
4
|
+
from core.batch import run_batch
|
|
5
|
+
from core.classifier import DEFAULT_SPECIES_MIN_CONFIDENCE, enrich_with_species, get_classifier
|
|
6
|
+
from core.detector import (
|
|
7
|
+
CATEGORY_MAP,
|
|
8
|
+
MODEL_ID,
|
|
9
|
+
analyze_single_image,
|
|
10
|
+
get_category_label,
|
|
11
|
+
get_detector,
|
|
12
|
+
run_analysis,
|
|
13
|
+
run_detection,
|
|
14
|
+
)
|
|
15
|
+
from core.exports import (
|
|
16
|
+
batch_to_csv,
|
|
17
|
+
detections_to_csv,
|
|
18
|
+
export_batch_json,
|
|
19
|
+
export_bundle,
|
|
20
|
+
export_json,
|
|
21
|
+
save_annotated_image,
|
|
22
|
+
)
|
|
23
|
+
from core.types import (
|
|
24
|
+
BIODEX_VERSION,
|
|
25
|
+
AnalysisResult,
|
|
26
|
+
BatchResult,
|
|
27
|
+
DetectionRecord,
|
|
28
|
+
SpeciesPrediction,
|
|
29
|
+
)
|
|
30
|
+
from core.visualization import draw_detections
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"BIODEX_VERSION",
|
|
34
|
+
"CATEGORY_MAP",
|
|
35
|
+
"MODEL_ID",
|
|
36
|
+
"AnalysisResult",
|
|
37
|
+
"BatchResult",
|
|
38
|
+
"DetectionRecord",
|
|
39
|
+
"SpeciesPrediction",
|
|
40
|
+
"analyze_single_image",
|
|
41
|
+
"batch_to_csv",
|
|
42
|
+
"draw_detections",
|
|
43
|
+
"detections_to_csv",
|
|
44
|
+
"enrich_with_species",
|
|
45
|
+
"export_batch_json",
|
|
46
|
+
"export_bundle",
|
|
47
|
+
"export_json",
|
|
48
|
+
"get_category_label",
|
|
49
|
+
"get_classifier",
|
|
50
|
+
"get_detector",
|
|
51
|
+
"run_analysis",
|
|
52
|
+
"run_batch",
|
|
53
|
+
"run_detection",
|
|
54
|
+
"save_annotated_image",
|
|
55
|
+
]
|