File size: 3,657 Bytes
1563787
 
06a7680
 
 
1563787
 
 
 
06a7680
1563787
 
06a7680
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1563787
06a7680
 
1563787
06a7680
 
 
 
 
 
 
 
 
 
 
 
 
1563787
 
 
 
 
 
06a7680
 
1563787
 
 
 
 
 
 
 
06a7680
1563787
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
"""OpenArm SciEdu ๋‚˜๋ญ‡์žŽ ์นด๋“œ ํŒ์ •๊ธฐ: ๋ณต์—ฝ(compound) / ๋‹จ์—ฝ(simple) / ์—†์Œ(none: ๋นˆ ์ค„์ด๊ฑฐ๋‚˜ ์นด๋“œ๊ฐ€ ๊ฐ€๋ ค์ง).
DINOv2-base ํŠน์ง• + 3๋ถ€๋ฅ˜ ์†Œํ”„ํŠธ๋งฅ์Šค ํšŒ๊ท€. CPU๋กœ ๋ˆ๋‹ค.

    from leaf_kind import LeafKind
    clf = LeafKind()                                  # classifier.json๊ณผ facebook/dinov2-base๋ฅผ ๋ฐ›๋Š”๋‹ค
    r = clf.judge([frame1, frame2, frame3])           # ๊ฐ™์€ ๋•Œ ์ฐ์€ d455f 640x480 RGB uint8 ํ”„๋ ˆ์ž„ ๋ช‡ ์žฅ
    # r["kind"]: "compound" | "simple" โ†’ ์˜ฎ๊ธด๋‹ค
    #            "none" โ†’ ์นด๋“œ๊ฐ€ ์—†๊ฑฐ๋‚˜ ๊ฐ€๋ ค์กŒ๋‹ค. ํŒ์ •ํ•˜์ง€ ์•Š๊ณ  1์ดˆ ๋’ค ๋‹ค์‹œ ๋ณธ๋‹ค
    #            None   โ†’ ์นด๋“œ๋Š” ์žˆ๋Š”๋ฐ ํ™•์‹ ์ด ๋‚ฎ๋‹ค(ํ”„๋ ˆ์ž„ ๋‹ต์ด ๊ฐˆ๋ฆฌ๊ฑฐ๋‚˜ ํ‰๊ท  ํ™•์‹  < ๋ฌธํ„ฑ). ์„ ์ƒ๋‹˜์—๊ฒŒ ๋ฌป๋Š”๋‹ค

- ์‚ฌ์ง„์€ ์นด๋“œ๊ฐ€ ์ค„ ์œ„์— ํ˜ผ์ž ์„œ ์žˆ๊ณ  ๋‘ ํŒ”์ด ์‰ฌ๊ณ  ์žˆ์„ ๋•Œ ์ฐ๋Š”๋‹ค. ํ•™์Šตํ•œ ์นด๋“œ ์‚ฌ์ง„์€ ์นด๋“œ๋ฅผ ์˜ฎ๊ธด ํŒ”์ด ์ถœ๋ฐœํ•˜๊ธฐ 5ํ”„๋ ˆ์ž„ ์ „์ด๋‹ค.
- ํ•œ ํ”„๋ ˆ์ž„์ด๋ผ๋„ none์ด๋ฉด kind๋Š” "none"์ด๋‹ค. ์—†๋Š” ์นด๋“œ๋ฅผ ์ง‘์œผ๋Ÿฌ ๊ฐ€์ง€ ์•Š๋Š” ์ชฝ์œผ๋กœ ์ •ํ–ˆ๋‹ค.
H200 ์„ธ์…˜(leap-06)์ด 2026-10-04์— ๋งŒ๋“ค์—ˆ๋‹ค. ๊ฒ€์ฆ๊ณผ ํ•™์Šต ์ž๋ฃŒ๋Š” README.md์— ์žˆ๋‹ค.
"""

from __future__ import annotations

import json
from pathlib import Path

import numpy as np
import torch
from transformers import AutoImageProcessor, AutoModel

HERE = Path(__file__).resolve().parent


class LeafKind:
    def __init__(self, classifier: str | Path = HERE / "classifier.json", device: str = "cpu", threshold: float = 0.9):
        cfg = json.loads(Path(classifier).read_text(encoding="utf-8"))
        self.classes = cfg["classes"]                       # ["compound", "simple", "none"]
        self.crop = cfg["crop_xyxy"]
        self.mean, self.std = np.asarray(cfg["mean"]), np.asarray(cfg["std"])
        self.W, self.b = np.asarray(cfg["W"]), np.asarray(cfg["b"])
        self.threshold = threshold
        self.device = device
        self.processor = AutoImageProcessor.from_pretrained(cfg["feature_model"])
        self.model = AutoModel.from_pretrained(cfg["feature_model"]).to(device).eval()

    def features(self, frames: list[np.ndarray]) -> np.ndarray:
        """d455f ํ”„๋ ˆ์ž„(H x W x 3, RGB uint8, 640x480)๋“ค์„ ์ž˜๋ผ DINOv2 CLS(pooler_output)๋ฅผ ๋‚ธ๋‹ค."""
        x0, y0, x1, y1 = self.crop
        crops = [np.ascontiguousarray(f[y0:y1, x0:x1]) for f in frames]
        with torch.no_grad():
            inputs = self.processor(images=crops, return_tensors="pt").to(self.device)
            return self.model(**inputs).pooler_output.float().cpu().numpy()

    def proba(self, frames: list[np.ndarray]) -> np.ndarray:
        """ํ”„๋ ˆ์ž„๋งˆ๋‹ค [compound, simple, none] ํ™•๋ฅ ."""
        z = ((self.features(frames) - self.mean) / self.std) @ self.W.T + self.b
        z = z - z.max(axis=1, keepdims=True)
        e = np.exp(z)
        return e / e.sum(axis=1, keepdims=True)

    def judge(self, frames: list[np.ndarray]) -> dict:
        p = self.proba(frames)
        votes = [self.classes[i] for i in p.argmax(axis=1)]
        mean = p.mean(axis=0)
        out = {"probs": dict(zip(self.classes, mean.round(4).tolist())), "votes": votes}
        if "none" in votes:
            return {"kind": "none", "confident": True, "p_compound": None, **out}
        # ์นด๋“œ๊ฐ€ ์žˆ๋‹ค: ๋ณต์—ฝ/๋‹จ์—ฝ ๋‘˜ ์‚ฌ์ด์—์„œ๋งŒ ๋‹ค์‹œ ๋‚˜๋ˆˆ๋‹ค
        pc = float((p[:, 0] / (p[:, 0] + p[:, 1])).mean())
        agree = len(set(votes)) == 1
        confident = agree and max(pc, 1 - pc) >= self.threshold
        kind = ("compound" if pc > 0.5 else "simple") if confident else None
        return {"kind": kind, "confident": confident, "p_compound": pc, **out}