AndrewKof commited on
Commit
361c20d
Β·
1 Parent(s): 3cac439

Auto image size, working attention map

Browse files
Files changed (31) hide show
  1. app/.DS_Store +0 -0
  2. app/Inference.py +29 -0
  3. app/__pycache__/main.cpython-310.pyc +0 -0
  4. app/__pycache__/model.cpython-310.pyc +0 -0
  5. app/dinov2/__pycache__/__init__.cpython-310.pyc +0 -0
  6. app/dinov2/hub/__pycache__/__init__.cpython-310.pyc +0 -0
  7. app/dinov2/hub/__pycache__/backbones.cpython-310.pyc +0 -0
  8. app/dinov2/hub/__pycache__/classifiers.cpython-310.pyc +0 -0
  9. app/dinov2/hub/__pycache__/depthers.cpython-310.pyc +0 -0
  10. app/dinov2/hub/__pycache__/utils.cpython-310.pyc +0 -0
  11. app/dinov2/hub/depth/__pycache__/__init__.cpython-310.pyc +0 -0
  12. app/dinov2/hub/depth/__pycache__/decode_heads.cpython-310.pyc +0 -0
  13. app/dinov2/hub/depth/__pycache__/encoder_decoder.cpython-310.pyc +0 -0
  14. app/dinov2/hub/depth/__pycache__/ops.cpython-310.pyc +0 -0
  15. app/dinov2/layers/__pycache__/__init__.cpython-310.pyc +0 -0
  16. app/dinov2/layers/__pycache__/attention.cpython-310.pyc +0 -0
  17. app/dinov2/layers/__pycache__/block.cpython-310.pyc +0 -0
  18. app/dinov2/layers/__pycache__/dino_head.cpython-310.pyc +0 -0
  19. app/dinov2/layers/__pycache__/drop_path.cpython-310.pyc +0 -0
  20. app/dinov2/layers/__pycache__/layer_scale.cpython-310.pyc +0 -0
  21. app/dinov2/layers/__pycache__/mlp.cpython-310.pyc +0 -0
  22. app/dinov2/layers/__pycache__/patch_embed.cpython-310.pyc +0 -0
  23. app/dinov2/layers/__pycache__/swiglu_ffn.cpython-310.pyc +0 -0
  24. app/dinov2/logging/__pycache__/__init__.cpython-310.pyc +0 -0
  25. app/dinov2/models/__pycache__/__init__.cpython-310.pyc +0 -0
  26. app/dinov2/models/__pycache__/vision_transformer.cpython-310.pyc +0 -0
  27. app/main.py +33 -84
  28. app/model.py +2 -1
  29. app/static/Dockerfile +0 -22
  30. app/static/index.html +3 -3
  31. requirements.txt +1 -0
app/.DS_Store CHANGED
Binary files a/app/.DS_Store and b/app/.DS_Store differ
 
app/Inference.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import torch
3
+ from transformers import AutoProcessor, Dinov2ForImageClassification
4
+ from PIL import Image
5
+ from torch.nn.functional import softmax
6
+
7
+ # --- Load mapping ---
8
+ with open("id2name.json", "r") as f:
9
+ id2name = json.load(f)
10
+
11
+ # --- Load model ---
12
+ model_name = "Arew99/dinov2-costum"
13
+ processor = AutoProcessor.from_pretrained(model_name)
14
+ model = Dinov2ForImageClassification.from_pretrained(model_name)
15
+ model.eval()
16
+
17
+ # --- Load image (example) ---
18
+ image = Image.open("sample_fish.jpg").convert("RGB")
19
+ inputs = processor(images=image, return_tensors="pt")
20
+
21
+ # --- Inference ---
22
+ with torch.no_grad():
23
+ logits = model(**inputs).logits.squeeze(0)
24
+ probs, idxs = softmax(logits, dim=0).topk(5)
25
+
26
+ print("\nTop-5 predictions:")
27
+ for p, i in zip(probs.tolist(), idxs.tolist()):
28
+ label = id2name[str(i)]
29
+ print(f"{label:30s} {p*100:.2f}%")
app/__pycache__/main.cpython-310.pyc CHANGED
Binary files a/app/__pycache__/main.cpython-310.pyc and b/app/__pycache__/main.cpython-310.pyc differ
 
app/__pycache__/model.cpython-310.pyc CHANGED
Binary files a/app/__pycache__/model.cpython-310.pyc and b/app/__pycache__/model.cpython-310.pyc differ
 
app/dinov2/__pycache__/__init__.cpython-310.pyc CHANGED
Binary files a/app/dinov2/__pycache__/__init__.cpython-310.pyc and b/app/dinov2/__pycache__/__init__.cpython-310.pyc differ
 
app/dinov2/hub/__pycache__/__init__.cpython-310.pyc DELETED
Binary file (153 Bytes)
 
app/dinov2/hub/__pycache__/backbones.cpython-310.pyc DELETED
Binary file (3.98 kB)
 
app/dinov2/hub/__pycache__/classifiers.cpython-310.pyc DELETED
Binary file (6.31 kB)
 
app/dinov2/hub/__pycache__/depthers.cpython-310.pyc DELETED
Binary file (6.41 kB)
 
app/dinov2/hub/__pycache__/utils.cpython-310.pyc DELETED
Binary file (1.77 kB)
 
app/dinov2/hub/depth/__pycache__/__init__.cpython-310.pyc DELETED
Binary file (279 Bytes)
 
app/dinov2/hub/depth/__pycache__/decode_heads.cpython-310.pyc DELETED
Binary file (23.3 kB)
 
app/dinov2/hub/depth/__pycache__/encoder_decoder.cpython-310.pyc DELETED
Binary file (12.7 kB)
 
app/dinov2/hub/depth/__pycache__/ops.cpython-310.pyc DELETED
Binary file (1.06 kB)
 
app/dinov2/layers/__pycache__/__init__.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/__init__.cpython-310.pyc and b/app/dinov2/layers/__pycache__/__init__.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/attention.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/attention.cpython-310.pyc and b/app/dinov2/layers/__pycache__/attention.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/block.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/block.cpython-310.pyc and b/app/dinov2/layers/__pycache__/block.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/dino_head.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/dino_head.cpython-310.pyc and b/app/dinov2/layers/__pycache__/dino_head.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/drop_path.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/drop_path.cpython-310.pyc and b/app/dinov2/layers/__pycache__/drop_path.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/layer_scale.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/layer_scale.cpython-310.pyc and b/app/dinov2/layers/__pycache__/layer_scale.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/mlp.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/mlp.cpython-310.pyc and b/app/dinov2/layers/__pycache__/mlp.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/patch_embed.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/patch_embed.cpython-310.pyc and b/app/dinov2/layers/__pycache__/patch_embed.cpython-310.pyc differ
 
app/dinov2/layers/__pycache__/swiglu_ffn.cpython-310.pyc CHANGED
Binary files a/app/dinov2/layers/__pycache__/swiglu_ffn.cpython-310.pyc and b/app/dinov2/layers/__pycache__/swiglu_ffn.cpython-310.pyc differ
 
app/dinov2/logging/__pycache__/__init__.cpython-310.pyc DELETED
Binary file (2.66 kB)
 
app/dinov2/models/__pycache__/__init__.cpython-310.pyc CHANGED
Binary files a/app/dinov2/models/__pycache__/__init__.cpython-310.pyc and b/app/dinov2/models/__pycache__/__init__.cpython-310.pyc differ
 
app/dinov2/models/__pycache__/vision_transformer.cpython-310.pyc CHANGED
Binary files a/app/dinov2/models/__pycache__/vision_transformer.cpython-310.pyc and b/app/dinov2/models/__pycache__/vision_transformer.cpython-310.pyc differ
 
app/main.py CHANGED
@@ -1,31 +1,17 @@
1
  # app/main.py
2
  import os
3
- import json
4
- from pathlib import Path
5
-
6
- import torch
7
  from fastapi import FastAPI, File, UploadFile
8
  from fastapi.middleware.cors import CORSMiddleware
9
  from fastapi.responses import HTMLResponse
10
  from fastapi.staticfiles import StaticFiles
11
- from transformers import (
12
- Dinov2ForImageClassification,
13
- Dinov2ImageProcessor, # <-- needs the newer transformers
14
- )
15
- from torch.nn.functional import softmax
16
- from PIL import Image
17
 
18
- # -------------------------------------------------
19
- # paths
20
- # -------------------------------------------------
21
- BASE_DIR = Path(__file__).parent
22
- STATIC_DIR = BASE_DIR / "static"
23
- INDEX_HTML = STATIC_DIR / "index.html"
24
- MAP_PATH = BASE_DIR / "id2name.json"
25
 
 
 
 
26
  app = FastAPI(title="NEMO Tools")
27
 
28
- # CORS so the JS can call us
29
  app.add_middleware(
30
  CORSMiddleware,
31
  allow_origins=["*"],
@@ -34,80 +20,43 @@ app.add_middleware(
34
  allow_headers=["*"],
35
  )
36
 
37
- # serve /static/*
38
- app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
 
 
 
 
39
 
 
40
 
41
  @app.get("/", response_class=HTMLResponse)
42
  def serve_frontend():
43
- return INDEX_HTML.read_text(encoding="utf-8")
44
-
45
-
46
- # -------------------------------------------------
47
- # load model + processor + labels ONCE
48
- # -------------------------------------------------
49
- print("πŸš€ Loading model and label mapping...")
50
-
51
- MODEL_ID = "Arew99/dinov2-costum"
52
-
53
- # model: your fine-tuned one
54
- model = Dinov2ForImageClassification.from_pretrained(
55
- MODEL_ID,
56
- num_labels=101,
57
- ignore_mismatched_sizes=True,
58
- )
59
- model.eval()
60
-
61
- # processor: from the ORIGINAL dino repo (not your custom one)
62
- processor = Dinov2ImageProcessor.from_pretrained("facebook/dinov2-large")
63
-
64
- # labels
65
- with MAP_PATH.open("r") as f:
66
- id2name = json.load(f)
67
- print(f"βœ“ Loaded {len(id2name)} labels from id2name.json")
68
-
69
-
70
- # -------------------------------------------------
71
- # endpoints
72
- # -------------------------------------------------
73
- @app.post("/predict")
74
- async def predict(file: UploadFile = File(...)):
75
- # this is your β€œtop-5 for an image” endpoint
76
- img = Image.open(file.file).convert("RGB")
77
-
78
- # Dinov2ImageProcessor wants a list β†’ [img]
79
- inputs = processor(images=[img], return_tensors="pt")
80
-
81
- with torch.no_grad():
82
- logits = model(**inputs).logits[0] # shape [101]
83
- probs, idxs = softmax(logits, dim=0).topk(5)
84
-
85
- results = []
86
- for p, i in zip(probs.tolist(), idxs.tolist()):
87
- label = id2name.get(str(i), f"Class {i}")
88
- results.append({"label": label, "confidence": p})
89
-
90
- return {"predictions": results}
91
-
92
-
93
- @app.post("/classify")
94
- async def classify(file: UploadFile = File(...)):
95
- img = Image.open(file.file).convert("RGB")
96
- inputs = processor(images=[img], return_tensors="pt")
97
-
98
- with torch.no_grad():
99
- logits = model(**inputs).logits[0]
100
- pred = int(logits.argmax().item())
101
-
102
- return {"label": id2name.get(str(pred), f"Class {pred}")}
103
-
104
 
105
  @app.get("/api")
106
  def api_root():
107
- return {"message": "NEMO Tools backend is running."}
108
-
109
 
 
110
  if __name__ == "__main__":
111
  import uvicorn
112
-
113
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  # app/main.py
2
  import os
 
 
 
 
3
  from fastapi import FastAPI, File, UploadFile
4
  from fastapi.middleware.cors import CORSMiddleware
5
  from fastapi.responses import HTMLResponse
6
  from fastapi.staticfiles import StaticFiles
7
+ from app.model import load_model, predict_from_bytes
 
 
 
 
 
8
 
 
 
 
 
 
 
 
9
 
10
+ # ──────────────────────────────────────────────
11
+ # FastAPI setup
12
+ # ──────────────────────────────────────────────
13
  app = FastAPI(title="NEMO Tools")
14
 
 
15
  app.add_middleware(
16
  CORSMiddleware,
17
  allow_origins=["*"],
 
20
  allow_headers=["*"],
21
  )
22
 
23
+ # ──────────────────────────────────────────────
24
+ # Static Frontend
25
+ # ──────────────────────────────────────────────
26
+ BASE_DIR = os.path.dirname(__file__)
27
+ STATIC_DIR = os.path.join(BASE_DIR, "static")
28
+ INDEX_HTML = os.path.join(STATIC_DIR, "index.html")
29
 
30
+ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
31
 
32
  @app.get("/", response_class=HTMLResponse)
33
  def serve_frontend():
34
+ """Serve the web interface."""
35
+ with open(INDEX_HTML, "r", encoding="utf-8") as f:
36
+ return f.read()
37
+
38
+ # ──────────────────────────────────────────────
39
+ # Model Initialization
40
+ # ──────────────────────────────────────────────
41
+ print("πŸš€ Loading DINOv2 custom model...")
42
+ model_device_tuple = load_model()
43
+ print("βœ… Model loaded and ready for inference!")
44
+
45
+ # ──────────────────────────────────────────────
46
+ # API Endpoints
47
+ # ──────────────────────────────────────────────
48
+ @app.post("/attention")
49
+ async def generate_attention(file: UploadFile = File(...)):
50
+ """Generate and return mean attention map for uploaded image."""
51
+ image_bytes = await file.read()
52
+ result = predict_from_bytes(model_device_tuple, image_bytes)
53
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  @app.get("/api")
56
  def api_root():
57
+ return {"message": "NEMO Tools backend running."}
 
58
 
59
+ # ──────────────────────────────────────────────
60
  if __name__ == "__main__":
61
  import uvicorn
 
62
  uvicorn.run(app, host="0.0.0.0", port=7860)
app/model.py CHANGED
@@ -25,7 +25,7 @@ CKPT_PATH = hf_hub_download(
25
  )
26
 
27
  PATCH_SIZE = 14
28
- IMAGE_SIZE = (1024, 720)
29
 
30
 
31
  # -------------------------------------------------------
@@ -46,6 +46,7 @@ def load_model():
46
  # Load weights
47
  state_dict = load_file(CKPT_PATH)
48
  keys_list = list(state_dict.keys())
 
49
 
50
  # Handle "model." prefix if present
51
  if keys_list and "model." in keys_list[0]:
 
25
  )
26
 
27
  PATCH_SIZE = 14
28
+ IMAGE_SIZE = (1000,1000)
29
 
30
 
31
  # -------------------------------------------------------
 
46
  # Load weights
47
  state_dict = load_file(CKPT_PATH)
48
  keys_list = list(state_dict.keys())
49
+ print(f"Loaded {len(state_dict.keys())} weights from {CKPT_PATH}")
50
 
51
  # Handle "model." prefix if present
52
  if keys_list and "model." in keys_list[0]:
app/static/Dockerfile DELETED
@@ -1,22 +0,0 @@
1
- # Use a lightweight Python image
2
- FROM python:3.10-slim
3
-
4
- # Set working directory
5
- WORKDIR /code
6
-
7
- # Copy requirements and install dependencies
8
- COPY requirements.txt .
9
- RUN pip install --no-cache-dir -r requirements.txt
10
-
11
- # Copy your FastAPI app and dinov2 module
12
- COPY app ./app
13
- COPY dinov2 ./dinov2
14
-
15
- # Set environment variable for module discovery
16
- ENV PYTHONPATH=/code
17
-
18
- # Expose the default Hugging Face Spaces port
19
- EXPOSE 7860
20
-
21
- # Run the FastAPI app
22
- CMD ["python", "-m", "app.main"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app/static/index.html CHANGED
@@ -55,7 +55,7 @@
55
  <div class="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
56
  <!-- Logo and title -->
57
  <div class="flex items-center gap-3">
58
- <img src="assets/logo.png" alt="NEMO logo" class="h-10 w-10 rounded-full shadow-sm" />
59
  <div>
60
  <h1 class="text-lg font-bold text-indigo-600">NEMO tools</h1>
61
  <p class="text-xs text-gray-400">DINOv2 visualisation sandbox</p>
@@ -308,7 +308,7 @@
308
  fd.append("file", file);
309
 
310
  try {
311
- const res = await fetch("/predict", { method: "POST", body: fd });
312
  if (!res.ok) throw new Error(`Server error: ${res.status}`);
313
  const json = await res.json();
314
 
@@ -337,7 +337,7 @@
337
  fd.append("file", file);
338
 
339
  try {
340
- const res = await fetch("/predict", { method: "POST", body: fd }); // βœ… must match FastAPI route
341
  if (!res.ok) throw new Error(`Server error: ${res.status}`);
342
  const json = await res.json();
343
 
 
55
  <div class="max-w-6xl mx-auto px-4 py-4 flex items-center justify-between">
56
  <!-- Logo and title -->
57
  <div class="flex items-center gap-3">
58
+ <img src="/static/assets/logo.png" alt="NEMO logo" class="h-10 w-10 rounded-full shadow-sm" />
59
  <div>
60
  <h1 class="text-lg font-bold text-indigo-600">NEMO tools</h1>
61
  <p class="text-xs text-gray-400">DINOv2 visualisation sandbox</p>
 
308
  fd.append("file", file);
309
 
310
  try {
311
+ const res = await fetch("/attention", { method: "POST", body: fd });
312
  if (!res.ok) throw new Error(`Server error: ${res.status}`);
313
  const json = await res.json();
314
 
 
337
  fd.append("file", file);
338
 
339
  try {
340
+ const res = await fetch("/attention", { method: "POST", body: fd }); // βœ… must match FastAPI route
341
  if (!res.ok) throw new Error(`Server error: ${res.status}`);
342
  const json = await res.json();
343
 
requirements.txt CHANGED
@@ -6,6 +6,7 @@ torch
6
  torchvision
7
  pillow
8
  numpy
 
9
 
10
  # Hugging Face bits
11
  transformers>=4.42.0
 
6
  torchvision
7
  pillow
8
  numpy
9
+ matplotlib
10
 
11
  # Hugging Face bits
12
  transformers>=4.42.0