Initial commit

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
forgejoadmin 2026-08-18 03:16:42 -04:00
commit 71db2d1ab9
126 changed files with 3198 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -0,0 +1,83 @@
"""
De-risking spike: can a generic vision embedding model (CLIP) tell Pokemon
figures/toys apart via nearest-neighbor lookup, with no training?
Pipeline: embed every image under images/reference/ (one file per Pokemon,
filename = class name) to build a small reference index, then embed every
image under images/query/ and report the nearest reference neighbor +
cosine similarity. A "pass" is query/<name>.jpg matching reference/<name>.jpg
as the top-1 hit.
Usage: .venv\\Scripts\\python.exe match_spike.py
"""
import sys
from pathlib import Path
import open_clip
import torch
from PIL import Image
HERE = Path(__file__).parent
REF_DIR = HERE / "images" / (sys.argv[1] if len(sys.argv) > 1 else "reference")
QUERY_DIR = HERE / "images" / "query"
MODEL_NAME = "ViT-B-32-quickgelu"
PRETRAINED = "openai"
def load_model():
model, _, preprocess = open_clip.create_model_and_transforms(
MODEL_NAME, pretrained=PRETRAINED
)
model.eval()
return model, preprocess
def embed_image(model, preprocess, path: Path) -> torch.Tensor:
image = preprocess(Image.open(path).convert("RGB")).unsqueeze(0)
with torch.no_grad():
features = model.encode_image(image)
features = features / features.norm(dim=-1, keepdim=True)
return features.squeeze(0)
def main():
print(f"Loading {MODEL_NAME} ({PRETRAINED})...")
model, preprocess = load_model()
ref_paths = (
sorted(REF_DIR.glob("*.jpg"))
+ sorted(REF_DIR.glob("*.webp"))
+ sorted(REF_DIR.glob("*.png"))
)
query_paths = (
sorted(QUERY_DIR.glob("*.jpg"))
+ sorted(QUERY_DIR.glob("*.webp"))
+ sorted(QUERY_DIR.glob("*.png"))
)
print(f"Embedding {len(ref_paths)} reference images...")
ref_names = [p.stem for p in ref_paths]
ref_embeds = torch.stack([embed_image(model, preprocess, p) for p in ref_paths])
print(f"Embedding {len(query_paths)} query images...\n")
correct = 0
for qpath in query_paths:
qembed = embed_image(model, preprocess, qpath)
sims = ref_embeds @ qembed # cosine similarity, both sides unit-norm
ranked = sorted(zip(ref_names, sims.tolist()), key=lambda x: -x[1])
top_name, top_sim = ranked[0]
expected = qpath.stem.split("_")[0]
is_correct = top_name == expected
correct += is_correct
marker = "OK " if is_correct else "MISS"
print(f"[{marker}] query={qpath.stem:<18} -> best={top_name:<10} sim={top_sim:.4f}")
runner_up = ", ".join(f"{n}={s:.3f}" for n, s in ranked[1:4])
print(f" runner-up: {runner_up}")
print(f"\n{correct}/{len(query_paths)} correct top-1 matches")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,82 @@
"""
Same spike as match_spike.py, but using DINOv2 instead of CLIP.
DINOv2 is trained with a self-supervised image-only objective specifically
aimed at instance/fine-grained visual similarity, which is a better fit for
"is this query photo the same object as this reference photo" than CLIP
(CLIP is trained for text-image alignment and tends to cluster images by
generic scene/style rather than object identity).
Usage: .venv\\Scripts\\python.exe match_spike_dinov2.py
"""
import sys
from pathlib import Path
import torch
from PIL import Image
from transformers import AutoImageProcessor, AutoModel
HERE = Path(__file__).parent
REF_DIR = HERE / "images" / (sys.argv[1] if len(sys.argv) > 1 else "reference")
QUERY_DIR = HERE / "images" / "query"
MODEL_NAME = "facebook/dinov2-base"
def load_model():
processor = AutoImageProcessor.from_pretrained(MODEL_NAME)
model = AutoModel.from_pretrained(MODEL_NAME)
model.eval()
return model, processor
def embed_image(model, processor, path: Path) -> torch.Tensor:
image = Image.open(path).convert("RGB")
inputs = processor(images=image, return_tensors="pt")
with torch.no_grad():
outputs = model(**inputs)
features = outputs.last_hidden_state[:, 0, :] # CLS token
features = features / features.norm(dim=-1, keepdim=True)
return features.squeeze(0)
def main():
print(f"Loading {MODEL_NAME}...")
model, processor = load_model()
ref_paths = (
sorted(REF_DIR.glob("*.jpg"))
+ sorted(REF_DIR.glob("*.webp"))
+ sorted(REF_DIR.glob("*.png"))
)
query_paths = (
sorted(QUERY_DIR.glob("*.jpg"))
+ sorted(QUERY_DIR.glob("*.webp"))
+ sorted(QUERY_DIR.glob("*.png"))
)
print(f"Embedding {len(ref_paths)} reference images...")
ref_names = [p.stem for p in ref_paths]
ref_embeds = torch.stack([embed_image(model, processor, p) for p in ref_paths])
print(f"Embedding {len(query_paths)} query images...\n")
correct = 0
for qpath in query_paths:
qembed = embed_image(model, processor, qpath)
sims = ref_embeds @ qembed
ranked = sorted(zip(ref_names, sims.tolist()), key=lambda x: -x[1])
top_name, top_sim = ranked[0]
expected = qpath.stem.split("_")[0]
is_correct = top_name == expected
correct += is_correct
marker = "OK " if is_correct else "MISS"
print(f"[{marker}] query={qpath.stem:<18} -> best={top_name:<10} sim={top_sim:.4f}")
runner_up = ", ".join(f"{n}={s:.3f}" for n, s in ranked[1:4])
print(f" runner-up: {runner_up}")
print(f"\n{correct}/{len(query_paths)} correct top-1 matches")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,87 @@
"""
De-risking spike, take 3: instead of embedding + nearest-neighbor lookup,
just ask a self-hosted vision-language model directly what Pokemon is in
the photo. No reference images, no vector index -- the model's own
pretrained world knowledge does the recognition.
Usage: .venv_vlm\\Scripts\\python.exe match_spike_vlm.py
"""
from pathlib import Path
import torch
from PIL import Image
from transformers import AutoProcessor, Qwen2_5_VLForConditionalGeneration
HERE = Path(__file__).parent
QUERY_DIR = HERE / "images" / "query"
MODEL_NAME = "Qwen/Qwen2.5-VL-3B-Instruct"
PROMPT = (
"You are the recognition system inside a Pokedex app. Identify the "
"Pokemon species shown in this image, even if it's a toy, plush, "
"trading card, sprite, fan art, or in-game screenshot of it. "
"Reply with ONLY the species name, nothing else. If no Pokemon is "
"clearly depicted, reply with exactly: unknown"
)
def load_model():
processor = AutoProcessor.from_pretrained(MODEL_NAME)
model = Qwen2_5_VLForConditionalGeneration.from_pretrained(
MODEL_NAME, torch_dtype=torch.bfloat16, device_map="cuda"
)
model.eval()
return model, processor
def identify(model, processor, path: Path) -> str:
image = Image.open(path).convert("RGB")
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": PROMPT},
],
}
]
text = processor.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True
)
inputs = processor(text=[text], images=[image], return_tensors="pt").to("cuda")
with torch.no_grad():
generated = model.generate(**inputs, max_new_tokens=16)
trimmed = generated[:, inputs["input_ids"].shape[1] :]
output = processor.batch_decode(
trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=True
)[0]
return output.strip()
def main():
print(f"Loading {MODEL_NAME} on GPU...")
model, processor = load_model()
query_paths = (
sorted(QUERY_DIR.glob("*.jpg"))
+ sorted(QUERY_DIR.glob("*.webp"))
+ sorted(QUERY_DIR.glob("*.png"))
)
print(f"Identifying {len(query_paths)} query images...\n")
correct = 0
for qpath in query_paths:
expected = qpath.stem.split("_")[0]
answer = identify(model, processor, qpath)
is_correct = answer.strip().lower() == expected.lower()
correct += is_correct
marker = "OK " if is_correct else "MISS"
print(f"[{marker}] query={qpath.stem:<18} expected={expected:<10} model_said={answer!r}")
print(f"\n{correct}/{len(query_paths)} correct")
if __name__ == "__main__":
main()

View file

@ -0,0 +1,4 @@
torch --index-url https://download.pytorch.org/whl/cpu
open_clip_torch
pillow
numpy