Image Classification
LiteRT
LiteRT
ONNX
English
vision
botany
western-australia
dinov3
mixture-of-experts
adaround
fp8
int8
android
biodiversity
flora
Instructions to use thenukegun10x/PLantDetect-WA with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use thenukegun10x/PLantDetect-WA with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
fix: CLI public release fallback to species_labels.json + safetensors
Browse filesPublic HF repo missing private data/wa_plants_200k/manifest CSVs and pt ckpts. Patch load_species_map to use species_labels.json, add _resolve_ckpt() for PlantDetect-*.safetensors, safetensors.torch.load_file support, Dense/MoE auto-detect, robust cmd_info. Tested: info + single/multi-view on RTX4050 BF16/FP8 AdaRound 104MB.
- plant_cli.py +83 -7
plant_cli.py
CHANGED
|
@@ -27,11 +27,31 @@ import torchvision.transforms as T
|
|
| 27 |
from src.data.plant import IMAGENET_MEAN, IMAGENET_STD
|
| 28 |
|
| 29 |
MANIFEST = BASE/"data/wa_plants_200k/manifest_for_train.csv"
|
|
|
|
| 30 |
CKPT_VISUAL = BASE/"data/plant_phase3b_otherblue.pt"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
CKPT_FUSION = BASE/"data/plant_phase5_fusion.pt"
|
| 32 |
E5_DIR = BASE/"data/plant_phase5_e5"
|
| 33 |
CKPT_OLD = BASE/"data/plant_phase2_200k.pt"
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
# fix PIL large image
|
| 36 |
Image.MAX_IMAGE_PIXELS = 300_000_000
|
| 37 |
|
|
@@ -41,6 +61,19 @@ def get_transform(img_size=336):
|
|
| 41 |
return T.Compose([T.Resize(int(img_size*1.14)), T.CenterCrop(img_size), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])
|
| 42 |
|
| 43 |
def load_species_map(manifest=MANIFEST):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
df=pd.read_csv(manifest)
|
| 45 |
spp=sorted(df[df["status"]=="downloaded"]["species"].unique())
|
| 46 |
s2i={s:i for i,s in enumerate(spp)}
|
|
@@ -70,10 +103,28 @@ def open_image_pil(path):
|
|
| 70 |
return im.convert("RGB")
|
| 71 |
|
| 72 |
def load_visual_model(ckpt=CKPT_VISUAL, n_classes=999, device="cuda"):
|
|
|
|
|
|
|
| 73 |
s2i,i2s,spp = load_species_map()
|
| 74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
if ckpt.exists():
|
| 76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
if isinstance(sd, dict) and "model" in sd: sd=sd["model"]
|
| 78 |
model.load_state_dict(sd, strict=False)
|
| 79 |
log(f"loaded {ckpt.name} {ckpt.stat().st_size/1e6:.1f}MB")
|
|
@@ -349,16 +400,41 @@ def cmd_batch(args):
|
|
| 349 |
def cmd_info(args):
|
| 350 |
print("WA Plant Identifier — Built with DINOv3")
|
| 351 |
print("Models:")
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
exists=p.exists()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
size=f"{p.stat().st_size/1e6:.1f}MB" if exists and p.is_file() else ("dir" if exists else "missing")
|
| 355 |
-
print(f" {
|
| 356 |
-
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
print(f"Device {'cuda '+torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'cpu'} torch {torch.__version__}")
|
| 359 |
print(f"Benchmark val 27673: visual 89.21% Top-1 96.19% Top-5 -> fusion with text 92.00% 97.51%")
|
| 360 |
-
print(f"Multi-view: 1-view 89.04% -> 4-view 99.33% (pseudo)")
|
| 361 |
print("\nLicensing: DINOv3 Meta commercial grant required (LICENSE.md), text e5 MIT, data GBIF per-image BY/BY-NC")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
|
| 363 |
def main():
|
| 364 |
parser=argparse.ArgumentParser(description="WA Plant Identifier — Built with DINOv3 | DSLR/phone seamless CLI", formatter_class=argparse.RawTextHelpFormatter)
|
|
|
|
| 27 |
from src.data.plant import IMAGENET_MEAN, IMAGENET_STD
|
| 28 |
|
| 29 |
MANIFEST = BASE/"data/wa_plants_200k/manifest_for_train.csv"
|
| 30 |
+
SPECIES_JSON = BASE/"species_labels.json"
|
| 31 |
CKPT_VISUAL = BASE/"data/plant_phase3b_otherblue.pt"
|
| 32 |
+
# Public HF release fallbacks (root *.safetensors) - used when private data/ ckpts missing
|
| 33 |
+
PUBLIC_CKPTS = [
|
| 34 |
+
BASE/"PlantDetect-FP8-AdaRound.safetensors",
|
| 35 |
+
BASE/"PlantDetect-BF16.safetensors",
|
| 36 |
+
BASE/"PlantDetect-Dense-FP8-AdaRound.safetensors",
|
| 37 |
+
BASE/"PlantDetect-Dense-BF16.safetensors",
|
| 38 |
+
BASE/"PlantDetect-4View-FP8-AdaRound.safetensors",
|
| 39 |
+
BASE/"PlantDetect-4View-BF16.safetensors",
|
| 40 |
+
BASE/"PlantDetect-Dense-4View-FP8-AdaRound.safetensors",
|
| 41 |
+
BASE/"PlantDetect-Dense-4View-BF16.safetensors",
|
| 42 |
+
]
|
| 43 |
CKPT_FUSION = BASE/"data/plant_phase5_fusion.pt"
|
| 44 |
E5_DIR = BASE/"data/plant_phase5_e5"
|
| 45 |
CKPT_OLD = BASE/"data/plant_phase2_200k.pt"
|
| 46 |
|
| 47 |
+
def _resolve_ckpt(preferred=CKPT_VISUAL):
|
| 48 |
+
if preferred and preferred.exists():
|
| 49 |
+
return preferred
|
| 50 |
+
for cand in PUBLIC_CKPTS:
|
| 51 |
+
if cand.exists():
|
| 52 |
+
return cand
|
| 53 |
+
return preferred
|
| 54 |
+
|
| 55 |
# fix PIL large image
|
| 56 |
Image.MAX_IMAGE_PIXELS = 300_000_000
|
| 57 |
|
|
|
|
| 61 |
return T.Compose([T.Resize(int(img_size*1.14)), T.CenterCrop(img_size), T.ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)])
|
| 62 |
|
| 63 |
def load_species_map(manifest=MANIFEST):
|
| 64 |
+
# Public release: species_labels.json (999 WA species) - preferred
|
| 65 |
+
if SPECIES_JSON.exists():
|
| 66 |
+
data=json.loads(SPECIES_JSON.read_text(encoding="utf-8"))
|
| 67 |
+
if "idx_to_species" in data:
|
| 68 |
+
i2s={int(k):v for k,v in data["idx_to_species"].items()}
|
| 69 |
+
spp=[i2s[i] for i in sorted(i2s)]
|
| 70 |
+
s2i={s:i for i,s in i2s.items()}
|
| 71 |
+
return s2i, i2s, spp
|
| 72 |
+
spp=data.get("species", [])
|
| 73 |
+
s2i={s:i for i,s in enumerate(spp)}
|
| 74 |
+
i2s={i:s for s,i in s2i.items()}
|
| 75 |
+
return s2i, i2s, spp
|
| 76 |
+
# Fallback: private manifest CSV (training)
|
| 77 |
df=pd.read_csv(manifest)
|
| 78 |
spp=sorted(df[df["status"]=="downloaded"]["species"].unique())
|
| 79 |
s2i={s:i for i,s in enumerate(spp)}
|
|
|
|
| 103 |
return im.convert("RGB")
|
| 104 |
|
| 105 |
def load_visual_model(ckpt=CKPT_VISUAL, n_classes=999, device="cuda"):
|
| 106 |
+
# auto-resolve to public safetensors if private ckpt missing
|
| 107 |
+
ckpt=_resolve_ckpt(ckpt)
|
| 108 |
s2i,i2s,spp = load_species_map()
|
| 109 |
+
# n_classes from species list (public 999) overrides arg if mismatch
|
| 110 |
+
if len(spp)!=n_classes:
|
| 111 |
+
n_classes=len(spp)
|
| 112 |
+
# Dense vs MoE auto-detect from ckpt name
|
| 113 |
+
is_dense = "Dense" in ckpt.name if ckpt else False
|
| 114 |
+
use_moe = None if is_dense else True
|
| 115 |
+
model=PlantViT(stem_name="vit_base_patch16_dinov3", n_classes=n_classes, use_moe=use_moe, num_ffn=16).to(device)
|
| 116 |
if ckpt.exists():
|
| 117 |
+
if ckpt.suffix==".safetensors":
|
| 118 |
+
try:
|
| 119 |
+
from safetensors.torch import load_file
|
| 120 |
+
try:
|
| 121 |
+
sd=load_file(str(ckpt), device=device)
|
| 122 |
+
except Exception:
|
| 123 |
+
sd=load_file(str(ckpt))
|
| 124 |
+
except ImportError:
|
| 125 |
+
raise RuntimeError("safetensors required: pip install safetensors")
|
| 126 |
+
else:
|
| 127 |
+
sd=torch.load(ckpt, map_location=device)
|
| 128 |
if isinstance(sd, dict) and "model" in sd: sd=sd["model"]
|
| 129 |
model.load_state_dict(sd, strict=False)
|
| 130 |
log(f"loaded {ckpt.name} {ckpt.stat().st_size/1e6:.1f}MB")
|
|
|
|
| 400 |
def cmd_info(args):
|
| 401 |
print("WA Plant Identifier — Built with DINOv3")
|
| 402 |
print("Models:")
|
| 403 |
+
# show both private and public candidates
|
| 404 |
+
cand_paths = [_resolve_ckpt(CKPT_VISUAL)] + PUBLIC_CKPTS
|
| 405 |
+
seen=set()
|
| 406 |
+
for p in cand_paths:
|
| 407 |
+
if str(p) in seen: continue
|
| 408 |
+
seen.add(str(p))
|
| 409 |
exists=p.exists()
|
| 410 |
+
try:
|
| 411 |
+
rel=p.relative_to(BASE)
|
| 412 |
+
except ValueError:
|
| 413 |
+
rel=p
|
| 414 |
size=f"{p.stat().st_size/1e6:.1f}MB" if exists and p.is_file() else ("dir" if exists else "missing")
|
| 415 |
+
print(f" {rel} {size} {'OK' if exists else 'MISSING'}")
|
| 416 |
+
for p in [CKPT_FUSION, E5_DIR, MANIFEST, SPECIES_JSON]:
|
| 417 |
+
exists=p.exists()
|
| 418 |
+
try:
|
| 419 |
+
rel=p.relative_to(BASE)
|
| 420 |
+
except ValueError:
|
| 421 |
+
rel=p
|
| 422 |
+
size=f"{p.stat().st_size/1e6:.1f}MB" if exists and p.is_file() else ("dir" if p.exists() and p.is_dir() else "missing")
|
| 423 |
+
print(f" {rel} {size} {'OK' if exists else 'MISSING'}")
|
| 424 |
+
try:
|
| 425 |
+
s2i,i2s,spp = load_species_map()
|
| 426 |
+
print(f"Species {len(spp)} ({len(spp)} loaded) e.g. {spp[0]}, {spp[10] if len(spp)>10 else spp[-1]}")
|
| 427 |
+
except Exception as e:
|
| 428 |
+
print(f"Species map failed: {e}")
|
| 429 |
+
spp=[]
|
| 430 |
print(f"Device {'cuda '+torch.cuda.get_device_name(0) if torch.cuda.is_available() else 'cpu'} torch {torch.__version__}")
|
| 431 |
print(f"Benchmark val 27673: visual 89.21% Top-1 96.19% Top-5 -> fusion with text 92.00% 97.51%")
|
| 432 |
+
print(f"Multi-view: 1-view 89.04% -> 4-view 99.33% (pseudo) | Public HF: MoE 89.31% / Dense 82.08% single, 99.42%/98.31% 4-view")
|
| 433 |
print("\nLicensing: DINOv3 Meta commercial grant required (LICENSE.md), text e5 MIT, data GBIF per-image BY/BY-NC")
|
| 434 |
+
if _resolve_ckpt(CKPT_VISUAL).exists():
|
| 435 |
+
print(f"Active ckpt: {_resolve_ckpt(CKPT_VISUAL).name} ({_resolve_ckpt(CKPT_VISUAL).stat().st_size/1e6:.1f}MB)")
|
| 436 |
+
else:
|
| 437 |
+
print("Active ckpt: MISSING - add PlantDetect-*.safetensors to project root")
|
| 438 |
|
| 439 |
def main():
|
| 440 |
parser=argparse.ArgumentParser(description="WA Plant Identifier — Built with DINOv3 | DSLR/phone seamless CLI", formatter_class=argparse.RawTextHelpFormatter)
|