thenukegun10x commited on
Commit
cdc1714
·
verified ·
1 Parent(s): 237bb08

fix: CLI public release fallback to species_labels.json + safetensors

Browse files

Public 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.

Files changed (1) hide show
  1. 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
- model=PlantViT(stem_name="vit_base_patch16_dinov3", n_classes=n_classes, use_moe=True, num_ffn=16).to(device)
 
 
 
 
 
 
75
  if ckpt.exists():
76
- sd=torch.load(ckpt, map_location=device)
 
 
 
 
 
 
 
 
 
 
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
- for p in [CKPT_VISUAL, CKPT_FUSION, E5_DIR, MANIFEST]:
 
 
 
 
 
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" {p.relative_to(BASE)} {size} {'OK' if exists else 'MISSING'}")
356
- s2i,i2s,spp = load_species_map()
357
- print(f"Species 999 ({len(spp)} loaded) e.g. {spp[0]}, {spp[10]}")
 
 
 
 
 
 
 
 
 
 
 
 
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)