OmarAlasqa commited on
Commit
1df4279
·
0 Parent(s):

Initial commit with Git LFS for binaries

Browse files
Files changed (41) hide show
  1. .gitattributes +3 -0
  2. .gitignore +8 -0
  3. README.md +28 -0
  4. app.py +442 -0
  5. configs/_base_/datasets/coco_detection.py +49 -0
  6. configs/_base_/datasets/coco_instance.py +49 -0
  7. configs/_base_/datasets/coco_instance_semantic.py +54 -0
  8. configs/_base_/default_runtime.py +16 -0
  9. configs/_base_/models/mask_rcnn_r50_fpn.py +120 -0
  10. configs/_base_/schedules/schedule_1x.py +11 -0
  11. configs/_base_/schedules/schedule_20e.py +11 -0
  12. configs/_base_/schedules/schedule_2x.py +11 -0
  13. configs/dsb/mask_rcnn_geovig_m_fpn_1x_dsb.py +102 -0
  14. configs/dsb/mask_rcnn_mobilevig_m_fpn_1x_dsb.py +74 -0
  15. configs/dsb/mask_rcnn_resnet50_fpn_1x_dsb.py +74 -0
  16. configs/dsb/mask_rcnn_swin_t_fpn_1x_dsb.py +93 -0
  17. configs/kvasir/mask_rcnn_fastvit_sa12_fpn_1x_kvasir.py +82 -0
  18. configs/kvasir/mask_rcnn_geovig_m_fpn_1x_kvasir.py +85 -0
  19. configs/kvasir/mask_rcnn_mobilevig_m_fpn_1x_kvasir.py +66 -0
  20. configs/kvasir/mask_rcnn_resnet50_fpn_1x_kvasir.py +72 -0
  21. configs/kvasir/mask_rcnn_swin_t_fpn_1x_kvasir.py +92 -0
  22. configs/mask_rcnn_geovig_b_fpn_1x_coco.py +32 -0
  23. configs/mask_rcnn_geovig_m_fpn_1x_coco.py +28 -0
  24. configs/mask_rcnn_mobilevig_b_fpn_1x_coco.py +24 -0
  25. configs/mask_rcnn_mobilevig_m_fpn_1x_coco.py +25 -0
  26. configs/mask_rcnn_mobilevigv2_b_fpn_1x_coco.py +24 -0
  27. configs/mask_rcnn_mobilevigv2_m_fpn_1x_coco.py +25 -0
  28. configs/s +324 -0
  29. examples/000000032081.jpg +3 -0
  30. examples/000000045472.jpg +3 -0
  31. examples/0c2550a23b8a0f29a7575de8c61690d3c31bc897dd5ba66caec201d201a278c2.png +3 -0
  32. examples/cat.png +3 -0
  33. examples/cju5y84q3mdv50817eyp82xf3.jpg +3 -0
  34. examples/n03394916_14162.JPEG +3 -0
  35. examples/n03417042_2960.JPEG +3 -0
  36. geovig.py +560 -0
  37. geovig_backbone.py +667 -0
  38. geovig_det_backbone.py +676 -0
  39. imagenet_labels.json +1000 -0
  40. pre_run.sh +6 -0
  41. requirements.txt +15 -0
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.jpg filter=lfs diff=lfs merge=lfs -text
2
+ *.JPEG filter=lfs diff=lfs merge=lfs -text
3
+ *.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.class
4
+ .DS_Store
5
+ checkpoints/
6
+ =4.0.0
7
+ backbone_keys.txt
8
+ keys.txt
README.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: GeoViG — Geometry-Aware Graph Vision
3
+ emoji: 🔷
4
+ colorFrom: indigo
5
+ colorTo: blue
6
+ sdk: gradio
7
+ sdk_version: "4.44.0"
8
+ app_file: app.py
9
+ pre_load_timeout: 300
10
+ pinned: true
11
+ license: apache-2.0
12
+ short_description: ImageNet + detection + medical segmentation with GeoViG
13
+ ---
14
+
15
+ # GeoViG Gradio Demo
16
+
17
+ Interactive demo for **GeoViG** — a lightweight hybrid CNN-GNN architecture featuring **SpreadEdgePool** and **GraphMRConv**.
18
+
19
+ ### 🌟 Features
20
+ - **🖼️ Image Classification**: ImageNet-1K predictions with Ti/S/M/B variants.
21
+ - **📦 Object Detection**: Mask R-CNN outputs for COCO objects.
22
+ - **🏥 Medical Segmentation**: Polyp (Kvasir-SEG) and Nuclei (DSB 2018) segmentation.
23
+
24
+ ### 🛠️ Local Setup
25
+ To run this locally:
26
+ 1. `pip install -r requirements.txt`
27
+ 2. `sh pre_run.sh` (to install MMCV/MMDet)
28
+ 3. `python app.py`
app.py ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GeoViG Multi-Task Gradio Demo
3
+ Tabs:
4
+ 1. Image Classification (ImageNet-1K) — Ti / S / M / B
5
+ 2. Object Detection & Segmentation — GeoViG-M on COCO 2017
6
+ 3. Medical Segmentation — GeoViG-M on Kvasir-SEG / DSB 2018
7
+ """
8
+
9
+ import sys, os, json, urllib.request
10
+ import torch
11
+ import torch.nn.functional as F
12
+ from torchvision import transforms
13
+ from PIL import Image, ImageDraw
14
+ from huggingface_hub import hf_hub_download
15
+ import numpy as np
16
+ import gradio as gr
17
+
18
+ # Detect Gradio API version — theme/css moved to launch() in v6
19
+ _GR_MAJOR = int(gr.__version__.split(".")[0])
20
+ _BLOCKS_KWARGS = {} if _GR_MAJOR >= 6 else {
21
+ "theme": gr.themes.Soft(primary_hue="indigo"),
22
+ "css": "footer { display: none !important; }"
23
+ }
24
+ _LAUNCH_KWARGS = {
25
+ "theme": gr.themes.Soft(primary_hue="indigo"),
26
+ "css": "footer { display: none !important; }"
27
+ } if _GR_MAJOR >= 6 else {}
28
+
29
+ from geovig import geovig_ti, geovig_s, geovig_m, geovig_b
30
+
31
+ # ── Paths ──────────────────────────────────────────────────────────────────────
32
+ BASE = "."
33
+ REPO_ID = "OmarAlasqa/GeoViG"
34
+ CKPT_FILES = {
35
+ "GeoViG-Ti": "pth/geovig_ti_5e4_8G_300_75_22/checkpoint.pth",
36
+ "GeoViG-S": "pth/geovig_s_5e4_8G_300_77_48/checkpoint.pth",
37
+ "GeoViG-M": "pth/geovig_m_5e4_8G_300_80_70/checkpoint.pth",
38
+ "GeoViG-B": "pth/geovig_b_5e4_8G_300_82_38/checkpoint.pth",
39
+ "det_m": "coco_det_seg_pth/geovig_m_det_seg/epoch_12.pth",
40
+ "det_b": "coco_det_seg_pth/geovig_b_det_seg/epoch_12.pth",
41
+ "kvasir_m": "medical/kvasir_geovig_m/checkpoint.pth",
42
+ "dsb_m": "medical/dsb_geovig_m/checkpoint.pth"
43
+ }
44
+
45
+ def get_ckpt_path(key):
46
+ return hf_hub_download(repo_id=REPO_ID, filename=CKPT_FILES[key])
47
+
48
+ MMDET_DIR = "."
49
+
50
+ # ── ImageNet labels ────────────────────────────────────────────────────────────
51
+ LABELS_FILE = "imagenet_labels.json"
52
+ if not os.path.exists(LABELS_FILE):
53
+ urllib.request.urlretrieve(
54
+ "https://raw.githubusercontent.com/anishathalye/imagenet-simple-labels/master/imagenet-simple-labels.json",
55
+ LABELS_FILE)
56
+ with open(LABELS_FILE) as f:
57
+ IMAGENET_LABELS = json.load(f)
58
+
59
+ # ── MMDetection availability ───────────────────────────────────────────────────
60
+ try:
61
+ sys.path.insert(0, MMDET_DIR)
62
+ import mmcv
63
+ from mmdet.apis import init_detector, inference_detector
64
+ import mmdet.models # noqa – registers all standard detectors
65
+ import geovig_det_backbone # noqa – local copy: registers geovig_m_feat / geovig_b_feat
66
+ MMDET_OK = True
67
+ MMDET_ERROR = ""
68
+ except Exception as e:
69
+ MMDET_OK = False
70
+ MMDET_ERROR = str(e)
71
+
72
+ # ── Pre-processing ─────────────────────────────────────────────────────────────
73
+ IMAGENET_TF = transforms.Compose([
74
+ transforms.Resize(256, interpolation=transforms.InterpolationMode.BICUBIC),
75
+ transforms.CenterCrop(224),
76
+ transforms.ToTensor(),
77
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
78
+ ])
79
+
80
+ MEDICAL_TF = transforms.Compose([
81
+ transforms.Resize((224, 224)),
82
+ transforms.ToTensor(),
83
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
84
+ ])
85
+
86
+ # ── Model cache ────────────────────────────────────────────────────────────────
87
+ MODEL_CACHE = {}
88
+
89
+ BUILDERS = {
90
+ "GeoViG-Ti": geovig_ti,
91
+ "GeoViG-S": geovig_s,
92
+ "GeoViG-M": geovig_m,
93
+ "GeoViG-B": geovig_b,
94
+ }
95
+
96
+ def _load_cls_model(variant: str):
97
+ if variant in MODEL_CACHE:
98
+ return MODEL_CACHE[variant]
99
+ model = BUILDERS[variant]()
100
+ ckpt_path = get_ckpt_path(variant)
101
+ try:
102
+ sd = torch.load(ckpt_path, map_location="cpu", weights_only=False)
103
+ except TypeError:
104
+ sd = torch.load(ckpt_path, map_location="cpu") # PyTorch < 1.13
105
+ sd = sd.get("model", sd.get("state_dict", sd))
106
+ model.load_state_dict(sd, strict=False)
107
+ model.eval()
108
+ MODEL_CACHE[variant] = model
109
+ return model
110
+
111
+ # ── TAB 1 — Classification ─────────────────────────────────────────────────────
112
+ def classify(image: Image.Image, variant: str, top_k: int):
113
+ if image is None:
114
+ return {}
115
+ model = _load_cls_model(variant)
116
+ x = IMAGENET_TF(image.convert("RGB")).unsqueeze(0)
117
+ with torch.no_grad():
118
+ probs = F.softmax(model(x), dim=-1)[0]
119
+ top_probs, top_idxs = probs.topk(int(top_k))
120
+ return {IMAGENET_LABELS[i.item()]: float(p) for p, i in zip(top_probs, top_idxs)}
121
+
122
+ # ── TAB 2 — Detection (COCO) ──────────────────────────────────────────────────
123
+ COCO_NAMES = [
124
+ "person","bicycle","car","motorcycle","airplane","bus","train","truck","boat",
125
+ "traffic light","fire hydrant","stop sign","parking meter","bench","bird","cat",
126
+ "dog","horse","sheep","cow","elephant","bear","zebra","giraffe","backpack",
127
+ "umbrella","handbag","tie","suitcase","frisbee","skis","snowboard","sports ball",
128
+ "kite","baseball bat","baseball glove","skateboard","surfboard","tennis racket",
129
+ "bottle","wine glass","cup","fork","knife","spoon","bowl","banana","apple",
130
+ "sandwich","orange","broccoli","carrot","hot dog","pizza","donut","cake","chair",
131
+ "couch","potted plant","bed","dining table","toilet","tv","laptop","mouse",
132
+ "remote","keyboard","cell phone","microwave","oven","toaster","sink","refrigerator",
133
+ "book","clock","vase","scissors","teddy bear","hair drier","toothbrush"
134
+ ]
135
+
136
+ def _draw_detections(image: Image.Image, result, score_thr=0.3):
137
+ """Draw bboxes and masks from MMDet result onto PIL image."""
138
+ img_draw = image.convert("RGBA")
139
+ draw = ImageDraw.Draw(img_draw)
140
+
141
+ # For the separate segmentation image
142
+ seg_img_draw = image.convert("RGBA")
143
+ seg_draw = ImageDraw.Draw(seg_img_draw)
144
+ seg_np_img = np.array(image.convert("RGB"))
145
+
146
+ if isinstance(result, tuple):
147
+ bbox_result, segm_result = result
148
+ else:
149
+ bbox_result, segm_result = result, None
150
+
151
+ for cls_id, bboxes in enumerate(bbox_result):
152
+ color = tuple(np.random.randint(100, 255, 3).tolist())
153
+ for i, bbox in enumerate(bboxes):
154
+ if bbox[4] < score_thr:
155
+ continue
156
+ x1, y1, x2, y2, score = bbox
157
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=4)
158
+ label = f"{COCO_NAMES[cls_id]} {score:.2f}"
159
+ # Draw text with a small black outline for readability
160
+ for offset in [(-1,-1), (1,-1), (-1,1), (1,1)]:
161
+ draw.text((x1 + 2 + offset[0], y1 + 2 + offset[1]), label, fill=(0, 0, 0))
162
+ draw.text((x1 + 2, y1 + 2), label, fill=(255, 255, 0))
163
+
164
+ # Draw mask if available
165
+ if segm_result is not None and len(segm_result) > cls_id and segm_result[cls_id] is not None:
166
+ try:
167
+ mask = segm_result[cls_id][i]
168
+ if isinstance(mask, dict):
169
+ import pycocotools.mask as mask_util
170
+ mask = mask_util.decode(mask)
171
+
172
+ # Ensure mask is boolean and same shape
173
+ mask_bool = mask.astype(bool)
174
+ # Alpha blending ONLY on the masked pixels
175
+ # Background (1 - alpha) * image + alpha * color
176
+ alpha = 0.5
177
+ img_pixels = seg_np_img[mask_bool].astype(float)
178
+ color_pixels = np.array(color, dtype=float)
179
+ blended = (img_pixels * (1 - alpha) + color_pixels * alpha).astype(np.uint8)
180
+ seg_np_img[mask_bool] = blended
181
+ except Exception:
182
+ pass
183
+
184
+ det_result = img_draw.convert("RGB")
185
+ seg_result = Image.fromarray(seg_np_img)
186
+ return det_result, seg_result
187
+
188
+ DET_MODEL_CACHE = {}
189
+
190
+ def detect(image: Image.Image, model_size: str, score_thr: float):
191
+ if image is None:
192
+ return None, None, "⚠️ Please upload an image."
193
+ if not MMDET_OK:
194
+ return None, None, f"⚠️ MMDetection not available.\nInstall: `pip install mmcv-full==1.7.2 mmdet==2.28.0`\nError: {MMDET_ERROR}"
195
+
196
+ key = f"det_{model_size.lower()}"
197
+ if key not in DET_MODEL_CACHE:
198
+ cfg_map = {
199
+ "M": f"{MMDET_DIR}/configs/mask_rcnn_geovig_m_fpn_1x_coco.py",
200
+ "B": f"{MMDET_DIR}/configs/mask_rcnn_geovig_b_fpn_1x_coco.py",
201
+ }
202
+ ckpt_path = get_ckpt_path(f"det_{model_size.lower()}")
203
+ try:
204
+ det_model = init_detector(cfg_map[model_size], ckpt_path, device="cpu")
205
+ DET_MODEL_CACHE[key] = det_model
206
+ except Exception as e:
207
+ return None, None, f"⚠️ Failed to load detection model:\n{e}"
208
+
209
+ det_model = DET_MODEL_CACHE[key]
210
+ import cv2
211
+ cv_img = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)
212
+ try:
213
+ result = inference_detector(det_model, cv_img)
214
+ bbox_result = result[0] if isinstance(result, tuple) else result
215
+ max_score = max([b[:, 4].max() if b.size > 0 else 0 for b in bbox_result])
216
+ print(f"DEBUG [{model_size} COCO]: Max Score = {max_score:.4f}")
217
+
218
+ det_img, seg_img = _draw_detections(image, result, score_thr=score_thr)
219
+
220
+ # Count detections
221
+ count = sum(len([b for b in cls_bboxes if b[4] >= score_thr]) for cls_bboxes in result[0])
222
+ status = f"✅ GeoViG-{model_size} detection complete. Found {count} objects."
223
+ if count == 0:
224
+ status += " (Try lowering score threshold)"
225
+
226
+ return det_img, seg_img, status
227
+ except Exception as e:
228
+ return None, None, f"⚠️ Inference error:\n{e}"
229
+
230
+ # ── TAB 3 — Medical Segmentation ──────────────────────────────────────────────
231
+ MED_MODEL_CACHE = {}
232
+
233
+ def _overlay_mask(image: Image.Image, mask: np.ndarray, color=(0, 200, 100), alpha=0.7):
234
+ """Overlay a binary mask on the PIL image."""
235
+ img_arr = np.array(image.convert("RGB")).astype(float)
236
+ overlay = img_arr.copy()
237
+ overlay[mask > 0.5] = np.array(color, dtype=float)
238
+ blended = (img_arr * (1 - alpha) + overlay * alpha).astype(np.uint8)
239
+ return Image.fromarray(blended)
240
+
241
+ def segment_medical(image: Image.Image, dataset: str, score_thr: float):
242
+ if image is None:
243
+ return None, "⚠️ Please upload an image."
244
+ if not MMDET_OK:
245
+ return None, f"⚠️ MMDetection not available.\nInstall: `pip install mmcv-full==1.7.2 mmdet==2.28.0`\nError: {MMDET_ERROR}"
246
+
247
+ key = f"med_{dataset.lower().replace(' ', '_')}"
248
+ if key not in MED_MODEL_CACHE:
249
+ cfg_map = {
250
+ "Kvasir-SEG": f"{MMDET_DIR}/configs/kvasir/mask_rcnn_geovig_m_fpn_1x_kvasir.py",
251
+ "DSB 2018": f"{MMDET_DIR}/configs/dsb/mask_rcnn_geovig_m_fpn_1x_dsb.py",
252
+ }
253
+ v_key = "kvasir_m" if dataset == "Kvasir-SEG" else "dsb_m"
254
+ ckpt_path = get_ckpt_path(v_key)
255
+ try:
256
+ med_model = init_detector(cfg_map[dataset], ckpt_path, device="cpu")
257
+ MED_MODEL_CACHE[key] = med_model
258
+ except Exception as e:
259
+ return None, f"⚠️ Failed to load model for {dataset}:\n{e}"
260
+
261
+ med_model = MED_MODEL_CACHE[key]
262
+ import cv2
263
+ cv_img = cv2.cvtColor(np.array(image.convert("RGB")), cv2.COLOR_RGB2BGR)
264
+ try:
265
+ result = inference_detector(med_model, cv_img)
266
+ bbox_result = result[0] if isinstance(result, tuple) else result
267
+ segm_result = result[1] if isinstance(result, tuple) else None
268
+
269
+ max_score = max([b[:, 4].max() if b.size > 0 else 0 for b in bbox_result])
270
+ print(f"DEBUG [{dataset}]: Max Score = {max_score:.4f}")
271
+
272
+ # Merge all masks above threshold
273
+ combined_mask = np.zeros(cv_img.shape[:2], dtype=np.float32)
274
+ count = 0
275
+ if segm_result:
276
+ for cls_id, cls_masks in enumerate(segm_result):
277
+ bboxes = bbox_result[cls_id]
278
+ for i, mask in enumerate(cls_masks):
279
+ if bboxes[i, 4] < score_thr:
280
+ continue
281
+ if isinstance(mask, dict):
282
+ import pycocotools.mask as mask_util
283
+ mask = mask_util.decode(mask)
284
+ combined_mask = np.maximum(combined_mask, mask.astype(np.float32))
285
+ count += 1
286
+
287
+ if count == 0:
288
+ return image, f"⚠️ No objects detected above score {score_thr}."
289
+
290
+ color = (0, 200, 100) if dataset == "Kvasir-SEG" else (100, 150, 255)
291
+ out_img = _overlay_mask(image, combined_mask, color=color)
292
+
293
+ # Now draw boxes on top of the masked image
294
+ draw = ImageDraw.Draw(out_img)
295
+ if bbox_result:
296
+ for cls_bboxes in bbox_result:
297
+ for bbox in cls_bboxes:
298
+ if bbox[4] >= score_thr:
299
+ x1, y1, x2, y2, score = bbox
300
+ draw.rectangle([x1, y1, x2, y2], outline=color, width=3)
301
+
302
+ return out_img, f"✅ GeoViG-M medical segmentation complete. Found {count} instances."
303
+ except Exception as e:
304
+ return None, f"⚠️ Medical inference error:\n{e}"
305
+
306
+ # ── Gradio Layout ──────────────────────────────────────────────────────────────
307
+ with gr.Blocks(title="GeoViG Demo", **_BLOCKS_KWARGS) as demo:
308
+
309
+ gr.HTML("""
310
+ <div style="text-align:center; padding: 1rem 0 0.5rem">
311
+ <h1 style="font-size:2rem; font-weight:700; margin:0">🔷 GeoViG</h1>
312
+ <p style="color:#6b7280; margin:0.3rem 0 0">
313
+ Geometry-Aware Graph Reasoning for Mobile Vision &nbsp;|&nbsp;
314
+ <a href="https://huggingface.co/OmarAlasqa/GeoViG" target="_blank">Model Card</a>
315
+ </p>
316
+ </div>
317
+ """)
318
+
319
+ with gr.Tabs():
320
+
321
+ # ── Tab 1: Classification ──────────────────────────────────────────────
322
+ with gr.TabItem("🖼️ Image Classification"):
323
+ gr.Markdown("**ImageNet-1K Classification** — Upload any image and get top-K predictions.")
324
+ with gr.Row():
325
+ with gr.Column(scale=1):
326
+ cls_image = gr.Image(type="pil", label="Input Image", height=280)
327
+ cls_variant = gr.Radio(
328
+ choices=["GeoViG-Ti", "GeoViG-S", "GeoViG-M", "GeoViG-B"],
329
+ value="GeoViG-Ti",
330
+ label="Model",
331
+ info="Ti 75.2% · S 77.5% · M 80.7% · B 82.4% (ImageNet Top-1)"
332
+ )
333
+ cls_topk = gr.Slider(1, 10, value=5, step=1, label="Top-K")
334
+ cls_btn = gr.Button("🚀 Classify", variant="primary")
335
+ with gr.Column(scale=1):
336
+ cls_output = gr.Label(label="Predictions", num_top_classes=10)
337
+
338
+ cls_btn.click(fn=classify, inputs=[cls_image, cls_variant, cls_topk], outputs=cls_output)
339
+ cls_image.change(fn=classify, inputs=[cls_image, cls_variant, cls_topk], outputs=cls_output)
340
+
341
+ gr.Examples(
342
+ examples=[
343
+ ["examples/n03394916_14162.JPEG", "GeoViG-Ti", 5],
344
+ ["examples/n03417042_2960.JPEG", "GeoViG-M", 5],
345
+ ],
346
+ inputs=[cls_image, cls_variant, cls_topk],
347
+ outputs=cls_output,
348
+ fn=classify,
349
+ cache_examples=False,
350
+ )
351
+
352
+ gr.Markdown("""
353
+ | Model | Params | MACs | Top-1 | GPU (iPhone 13) |
354
+ |:---|:---:|:---:|:---:|:---:|
355
+ | GeoViG-Ti | 3.5M | 0.9G | 75.2% | 10.1ms |
356
+ | GeoViG-S | 5.0M | 1.2G | 77.5% | 12.2ms |
357
+ | GeoViG-M | 10.3M | 2.2G | 80.7% | 14.4ms |
358
+ | GeoViG-B | 19.7M | 4.5G | 82.4% | 29.0ms |
359
+ """)
360
+
361
+ # ── Tab 2: Object Detection ────────────────────────────────────────────
362
+ with gr.TabItem("📦 Object Detection (COCO)"):
363
+ mmdet_status = "✅ MMDetection available" if MMDET_OK else "⚠️ MMDetection not installed — install `mmcv-full==1.7.2` and `mmdet==2.28.0` to enable this tab"
364
+ gr.Markdown(f"**MS COCO Object Detection & Instance Segmentation** (Mask R-CNN backbone)\n\n_{mmdet_status}_")
365
+ with gr.Row():
366
+ with gr.Column(scale=1):
367
+ det_image = gr.Image(type="pil", label="Input Image", height=280)
368
+ det_size = gr.Radio(choices=["M", "B"], value="M",
369
+ label="Backbone",
370
+ info="M: 40.7 Box AP · B: 42.5 Box AP")
371
+ det_thr = gr.Slider(0.1, 0.9, value=0.7, step=0.05, label="Score threshold")
372
+ det_btn = gr.Button("🔍 Detect & Segment", variant="primary", interactive=MMDET_OK)
373
+ det_output_txt = gr.Textbox(label="Status", lines=2)
374
+ with gr.Column(scale=2):
375
+ with gr.Row():
376
+ det_output_img = gr.Image(label="Detection (Boxes)", type="pil")
377
+ seg_output_img = gr.Image(label="Instance Segmentation (Masks)", type="pil")
378
+
379
+ det_btn.click(fn=detect, inputs=[det_image, det_size, det_thr],
380
+ outputs=[det_output_img, seg_output_img, det_output_txt])
381
+
382
+ gr.Examples(
383
+ examples=[
384
+ ["examples/000000032081.jpg", "M", 0.7],
385
+ ["examples/000000045472.jpg", "M", 0.7],
386
+ ["examples/cat.png", "B", 0.7],
387
+ ],
388
+ inputs=[det_image, det_size, det_thr],
389
+ outputs=[det_output_img, seg_output_img, det_output_txt],
390
+ fn=detect,
391
+ cache_examples=False,
392
+ )
393
+
394
+ gr.Markdown("""
395
+ | Backbone | Params | Box AP | Mask AP |
396
+ |:---|:---:|:---:|:---:|
397
+ | GeoViG-M | 10.3M | 40.7 | 37.7 |
398
+ | GeoViG-B | 19.7M | 42.5 | 38.9 |
399
+ """)
400
+
401
+ # ── Tab 3: Medical Segmentation ────────────────────────────────────────
402
+ with gr.TabItem("🏥 Medical Segmentation"):
403
+ gr.Markdown(f"**Medical Image Segmentation** — GeoViG-M encoder · Mask R-CNN decoder\n\n_{mmdet_status}_")
404
+ with gr.Row():
405
+ with gr.Column(scale=1):
406
+ med_image = gr.Image(type="pil", label="Medical Image", height=280)
407
+ med_dataset = gr.Radio(
408
+ choices=["Kvasir-SEG", "DSB 2018"],
409
+ value="Kvasir-SEG",
410
+ label="Dataset / Task",
411
+ info="Kvasir: Polyp segmentation (Dice 0.945, HD 12.94) · DSB: Nuclei (Dice 0.908)"
412
+ )
413
+ med_thr = gr.Slider(0.1, 0.9, value=0.7, step=0.05, label="Score threshold")
414
+ med_btn = gr.Button("🔬 Segment", variant="primary", interactive=MMDET_OK)
415
+ with gr.Column(scale=1):
416
+ med_output_img = gr.Image(label="Segmentation Result", type="pil")
417
+ med_output_txt = gr.Textbox(label="Status", lines=3)
418
+
419
+ med_btn.click(fn=segment_medical, inputs=[med_image, med_dataset, med_thr],
420
+ outputs=[med_output_img, med_output_txt])
421
+
422
+ gr.Examples(
423
+ examples=[
424
+ ["examples/cju5y84q3mdv50817eyp82xf3.jpg", "Kvasir-SEG", 0.7],
425
+ ["examples/0c2550a23b8a0f29a7575de8c61690d3c31bc897dd5ba66caec201d201a278c2.png", "DSB 2018", 0.7],
426
+ ],
427
+ inputs=[med_image, med_dataset, med_thr],
428
+ outputs=[med_output_img, med_output_txt],
429
+ fn=segment_medical,
430
+ cache_examples=False,
431
+ )
432
+
433
+ gr.Markdown("""
434
+ | Task | Dataset | Dice ↑ | IoU ↑ | Hausdorff ↓ |
435
+ |:---|:---|:---:|:---:|:---:|
436
+ | Polyp Seg | Kvasir-SEG | **0.945** | **0.909** | **12.94** |
437
+ | Nuclei Seg | DSB 2018 | **0.908** | **0.839** | **5.19** |
438
+
439
+ > GeoViG reduces Hausdorff Distance **5.4× vs ResNet-50** (70.37 → 12.94) on Kvasir-SEG.
440
+ """)
441
+
442
+ demo.launch(**_LAUNCH_KWARGS)
configs/_base_/datasets/coco_detection.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dataset settings
2
+ dataset_type = 'CocoDataset'
3
+ data_root = '../../mmdetection/data/coco/'
4
+ img_norm_cfg = dict(
5
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
6
+ train_pipeline = [
7
+ dict(type='LoadImageFromFile'),
8
+ dict(type='LoadAnnotations', with_bbox=True),
9
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
10
+ dict(type='RandomFlip', flip_ratio=0.5),
11
+ dict(type='Normalize', **img_norm_cfg),
12
+ dict(type='Pad', size_divisor=32),
13
+ dict(type='DefaultFormatBundle'),
14
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels']),
15
+ ]
16
+ test_pipeline = [
17
+ dict(type='LoadImageFromFile'),
18
+ dict(
19
+ type='MultiScaleFlipAug',
20
+ img_scale=(1333, 800),
21
+ flip=False,
22
+ transforms=[
23
+ dict(type='Resize', keep_ratio=True),
24
+ dict(type='RandomFlip'),
25
+ dict(type='Normalize', **img_norm_cfg),
26
+ dict(type='Pad', size_divisor=32),
27
+ dict(type='ImageToTensor', keys=['img']),
28
+ dict(type='Collect', keys=['img']),
29
+ ])
30
+ ]
31
+ data = dict(
32
+ samples_per_gpu=2,
33
+ workers_per_gpu=2,
34
+ train=dict(
35
+ type=dataset_type,
36
+ ann_file=data_root + 'annotations/instances_train2017.json',
37
+ img_prefix=data_root + 'train2017/',
38
+ pipeline=train_pipeline),
39
+ val=dict(
40
+ type=dataset_type,
41
+ ann_file=data_root + 'annotations/instances_val2017.json',
42
+ img_prefix=data_root + 'val2017/',
43
+ pipeline=test_pipeline),
44
+ test=dict(
45
+ type=dataset_type,
46
+ ann_file=data_root + 'annotations/instances_val2017.json',
47
+ img_prefix=data_root + 'val2017/',
48
+ pipeline=test_pipeline))
49
+ evaluation = dict(interval=1, metric='bbox')
configs/_base_/datasets/coco_instance.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dataset settings
2
+ dataset_type = 'CocoDataset'
3
+ data_root = '../../../hvig/detection/data/coco/'
4
+ img_norm_cfg = dict(
5
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
6
+ train_pipeline = [
7
+ dict(type='LoadImageFromFile'),
8
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
9
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
10
+ dict(type='RandomFlip', flip_ratio=0.5),
11
+ dict(type='Normalize', **img_norm_cfg),
12
+ dict(type='Pad', size_divisor=32),
13
+ dict(type='DefaultFormatBundle'),
14
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
15
+ ]
16
+ test_pipeline = [
17
+ dict(type='LoadImageFromFile'),
18
+ dict(
19
+ type='MultiScaleFlipAug',
20
+ img_scale=(1333, 800),
21
+ flip=False,
22
+ transforms=[
23
+ dict(type='Resize', keep_ratio=True),
24
+ dict(type='RandomFlip'),
25
+ dict(type='Normalize', **img_norm_cfg),
26
+ dict(type='Pad', size_divisor=32),
27
+ dict(type='ImageToTensor', keys=['img']),
28
+ dict(type='Collect', keys=['img']),
29
+ ])
30
+ ]
31
+ data = dict(
32
+ samples_per_gpu=1, # was 2, made 1 to avoid out of memory
33
+ workers_per_gpu=2,
34
+ train=dict(
35
+ type=dataset_type,
36
+ ann_file=data_root + 'annotations/instances_train2017.json',
37
+ img_prefix=data_root + 'train2017/',
38
+ pipeline=train_pipeline),
39
+ val=dict(
40
+ type=dataset_type,
41
+ ann_file=data_root + 'annotations/instances_val2017.json',
42
+ img_prefix=data_root + 'val2017/',
43
+ pipeline=test_pipeline),
44
+ test=dict(
45
+ type=dataset_type,
46
+ ann_file=data_root + 'annotations/instances_val2017.json',
47
+ img_prefix=data_root + 'val2017/',
48
+ pipeline=test_pipeline))
49
+ evaluation = dict(metric=['bbox', 'segm'])
configs/_base_/datasets/coco_instance_semantic.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # dataset settings
2
+ dataset_type = 'CocoDataset'
3
+ data_root = 'data/coco/'
4
+ img_norm_cfg = dict(
5
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
6
+ train_pipeline = [
7
+ dict(type='LoadImageFromFile'),
8
+ dict(
9
+ type='LoadAnnotations', with_bbox=True, with_mask=True, with_seg=True),
10
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
11
+ dict(type='RandomFlip', flip_ratio=0.5),
12
+ dict(type='Normalize', **img_norm_cfg),
13
+ dict(type='Pad', size_divisor=32),
14
+ dict(type='SegRescale', scale_factor=1 / 8),
15
+ dict(type='DefaultFormatBundle'),
16
+ dict(
17
+ type='Collect',
18
+ keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks', 'gt_semantic_seg']),
19
+ ]
20
+ test_pipeline = [
21
+ dict(type='LoadImageFromFile'),
22
+ dict(
23
+ type='MultiScaleFlipAug',
24
+ img_scale=(1333, 800),
25
+ flip=False,
26
+ transforms=[
27
+ dict(type='Resize', keep_ratio=True),
28
+ dict(type='RandomFlip', flip_ratio=0.5),
29
+ dict(type='Normalize', **img_norm_cfg),
30
+ dict(type='Pad', size_divisor=32),
31
+ dict(type='ImageToTensor', keys=['img']),
32
+ dict(type='Collect', keys=['img']),
33
+ ])
34
+ ]
35
+ data = dict(
36
+ samples_per_gpu=2,
37
+ workers_per_gpu=2,
38
+ train=dict(
39
+ type=dataset_type,
40
+ ann_file=data_root + 'annotations/instances_train2017.json',
41
+ img_prefix=data_root + 'train2017/',
42
+ seg_prefix=data_root + 'stuffthingmaps/train2017/',
43
+ pipeline=train_pipeline),
44
+ val=dict(
45
+ type=dataset_type,
46
+ ann_file=data_root + 'annotations/instances_val2017.json',
47
+ img_prefix=data_root + 'val2017/',
48
+ pipeline=test_pipeline),
49
+ test=dict(
50
+ type=dataset_type,
51
+ ann_file=data_root + 'annotations/instances_val2017.json',
52
+ img_prefix=data_root + 'val2017/',
53
+ pipeline=test_pipeline))
54
+ evaluation = dict(metric=['bbox', 'segm'])
configs/_base_/default_runtime.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ checkpoint_config = dict(interval=1)
2
+ # yapf:disable
3
+ log_config = dict(
4
+ interval=50,
5
+ hooks=[
6
+ dict(type='TextLoggerHook'),
7
+ # dict(type='TensorboardLoggerHook')
8
+ ])
9
+ # yapf:enable
10
+ custom_hooks = [dict(type='NumClassCheckHook')]
11
+
12
+ dist_params = dict(backend='nccl')
13
+ log_level = 'INFO'
14
+ load_from = None
15
+ resume_from = None
16
+ workflow = [('train', 1)]
configs/_base_/models/mask_rcnn_r50_fpn.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # model settings
2
+ model = dict(
3
+ type='MaskRCNN',
4
+ pretrained='torchvision://resnet50',
5
+ backbone=dict(
6
+ type='ResNet',
7
+ depth=50,
8
+ num_stages=4,
9
+ out_indices=(0, 1, 2, 3),
10
+ frozen_stages=1,
11
+ norm_cfg=dict(type='BN', requires_grad=True),
12
+ norm_eval=True,
13
+ style='pytorch'),
14
+ neck=dict(
15
+ type='FPN',
16
+ in_channels=[256, 512, 1024, 2048],
17
+ out_channels=256,
18
+ num_outs=5),
19
+ rpn_head=dict(
20
+ type='RPNHead',
21
+ in_channels=256,
22
+ feat_channels=256,
23
+ anchor_generator=dict(
24
+ type='AnchorGenerator',
25
+ scales=[8],
26
+ ratios=[0.5, 1.0, 2.0],
27
+ strides=[4, 8, 16, 32, 64]),
28
+ bbox_coder=dict(
29
+ type='DeltaXYWHBBoxCoder',
30
+ target_means=[.0, .0, .0, .0],
31
+ target_stds=[1.0, 1.0, 1.0, 1.0]),
32
+ loss_cls=dict(
33
+ type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
34
+ loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
35
+ roi_head=dict(
36
+ type='StandardRoIHead',
37
+ bbox_roi_extractor=dict(
38
+ type='SingleRoIExtractor',
39
+ roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
40
+ out_channels=256,
41
+ featmap_strides=[4, 8, 16, 32]),
42
+ bbox_head=dict(
43
+ type='Shared2FCBBoxHead',
44
+ in_channels=256,
45
+ fc_out_channels=1024,
46
+ roi_feat_size=7,
47
+ num_classes=80,
48
+ bbox_coder=dict(
49
+ type='DeltaXYWHBBoxCoder',
50
+ target_means=[0., 0., 0., 0.],
51
+ target_stds=[0.1, 0.1, 0.2, 0.2]),
52
+ reg_class_agnostic=False,
53
+ loss_cls=dict(
54
+ type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
55
+ loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
56
+ mask_roi_extractor=dict(
57
+ type='SingleRoIExtractor',
58
+ roi_layer=dict(type='RoIAlign', output_size=14, sampling_ratio=0),
59
+ out_channels=256,
60
+ featmap_strides=[4, 8, 16, 32]),
61
+ mask_head=dict(
62
+ type='FCNMaskHead',
63
+ num_convs=4,
64
+ in_channels=256,
65
+ conv_out_channels=256,
66
+ num_classes=80,
67
+ loss_mask=dict(
68
+ type='CrossEntropyLoss', use_mask=True, loss_weight=1.0))),
69
+ # model training and testing settings
70
+ train_cfg=dict(
71
+ rpn=dict(
72
+ assigner=dict(
73
+ type='MaxIoUAssigner',
74
+ pos_iou_thr=0.7,
75
+ neg_iou_thr=0.3,
76
+ min_pos_iou=0.3,
77
+ match_low_quality=True,
78
+ ignore_iof_thr=-1),
79
+ sampler=dict(
80
+ type='RandomSampler',
81
+ num=256,
82
+ pos_fraction=0.5,
83
+ neg_pos_ub=-1,
84
+ add_gt_as_proposals=False),
85
+ allowed_border=-1,
86
+ pos_weight=-1,
87
+ debug=False),
88
+ rpn_proposal=dict(
89
+ nms_pre=2000,
90
+ max_per_img=1000,
91
+ nms=dict(type='nms', iou_threshold=0.7),
92
+ min_bbox_size=0),
93
+ rcnn=dict(
94
+ assigner=dict(
95
+ type='MaxIoUAssigner',
96
+ pos_iou_thr=0.5,
97
+ neg_iou_thr=0.5,
98
+ min_pos_iou=0.5,
99
+ match_low_quality=True,
100
+ ignore_iof_thr=-1),
101
+ sampler=dict(
102
+ type='RandomSampler',
103
+ num=512,
104
+ pos_fraction=0.25,
105
+ neg_pos_ub=-1,
106
+ add_gt_as_proposals=True),
107
+ mask_size=28,
108
+ pos_weight=-1,
109
+ debug=False)),
110
+ test_cfg=dict(
111
+ rpn=dict(
112
+ nms_pre=1000,
113
+ max_per_img=1000,
114
+ nms=dict(type='nms', iou_threshold=0.7),
115
+ min_bbox_size=0),
116
+ rcnn=dict(
117
+ score_thr=0.05,
118
+ nms=dict(type='nms', iou_threshold=0.5),
119
+ max_per_img=100,
120
+ mask_thr_binary=0.5)))
configs/_base_/schedules/schedule_1x.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # optimizer
2
+ optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
3
+ optimizer_config = dict(grad_clip=None)
4
+ # learning policy
5
+ lr_config = dict(
6
+ policy='step',
7
+ warmup='linear',
8
+ warmup_iters=500,
9
+ warmup_ratio=1e-6, # 0.001
10
+ step=[8, 11])
11
+ runner = dict(type='EpochBasedRunner', max_epochs=12)
configs/_base_/schedules/schedule_20e.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # optimizer
2
+ optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
3
+ optimizer_config = dict(grad_clip=None)
4
+ # learning policy
5
+ lr_config = dict(
6
+ policy='step',
7
+ warmup='linear',
8
+ warmup_iters=500,
9
+ warmup_ratio=0.001,
10
+ step=[16, 19])
11
+ runner = dict(type='EpochBasedRunner', max_epochs=20)
configs/_base_/schedules/schedule_2x.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # optimizer
2
+ optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
3
+ optimizer_config = dict(grad_clip=None)
4
+ # learning policy
5
+ lr_config = dict(
6
+ policy='step',
7
+ warmup='linear',
8
+ warmup_iters=500,
9
+ warmup_ratio=0.001,
10
+ step=[16, 22])
11
+ runner = dict(type='EpochBasedRunner', max_epochs=24)
configs/dsb/mask_rcnn_geovig_m_fpn_1x_dsb.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/schedules/schedule_1x.py',
4
+ '../_base_/default_runtime.py'
5
+ ]
6
+
7
+ model = dict(
8
+ backbone=dict(
9
+ _delete_=True,
10
+ type='geovig_m_feat',
11
+ style='pytorch',
12
+ pretrained=False,
13
+ use_detect_adapter=True,
14
+ init_cfg=None
15
+ ),
16
+ neck=dict(
17
+ type='FPN',
18
+ in_channels=[64, 128, 256, 384],
19
+ out_channels=256,
20
+ num_outs=5,
21
+ norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)
22
+ ),
23
+ roi_head=dict(
24
+ bbox_head=dict(num_classes=1),
25
+ mask_head=dict(num_classes=1)
26
+ ),
27
+ test_cfg=dict(
28
+ rpn=dict(nms_pre=1000, max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7), min_bbox_size=0),
29
+ rcnn=dict(
30
+ score_thr=0.05,
31
+ nms=dict(type='nms', iou_threshold=0.3),
32
+ max_per_img=200,
33
+ mask_thr_binary=0.5
34
+ )
35
+ )
36
+ )
37
+
38
+ # Dataset settings
39
+ dataset_type = 'DSBDataset'
40
+ data_root = '../../data/dsb2018/stage1_train'
41
+ img_norm_cfg = dict(
42
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
43
+ train_pipeline = [
44
+ dict(type='LoadImageFromFile'),
45
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
46
+ dict(type='LoadMasksFromAnn'),
47
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
48
+ dict(type='RandomFlip', flip_ratio=0.5),
49
+ dict(type='Normalize', **img_norm_cfg),
50
+ dict(type='Pad', size_divisor=32),
51
+ dict(type='DefaultFormatBundle'),
52
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
53
+ ]
54
+ test_pipeline = [
55
+ dict(type='LoadImageFromFile'),
56
+ dict(
57
+ type='MultiScaleFlipAug',
58
+ img_scale=(1333, 800),
59
+ flip=False,
60
+ transforms=[
61
+ dict(type='Resize', keep_ratio=True),
62
+ dict(type='RandomFlip'),
63
+ dict(type='Normalize', **img_norm_cfg),
64
+ dict(type='Pad', size_divisor=32),
65
+ dict(type='ImageToTensor', keys=['img']),
66
+ dict(type='Collect', keys=['img']),
67
+ ])
68
+ ]
69
+ data = dict(
70
+ samples_per_gpu=1,
71
+ workers_per_gpu=4,
72
+ train=dict(
73
+ type=dataset_type,
74
+ img_prefix=data_root,
75
+ ann_file='',
76
+ pipeline=train_pipeline),
77
+ val=dict(
78
+ type=dataset_type,
79
+ img_prefix=data_root,
80
+ ann_file='',
81
+ pipeline=test_pipeline),
82
+ test=dict(
83
+ type=dataset_type,
84
+ img_prefix=data_root,
85
+ ann_file='',
86
+ pipeline=test_pipeline))
87
+
88
+ # Fine-tuning config
89
+ load_from = 'geovig_m_seg_4G/epoch_12.pth'
90
+
91
+ evaluation = dict(metric='mAP')
92
+
93
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05)
94
+ optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=1.0, norm_type=2))
95
+
96
+ runner = dict(type='EpochBasedRunner', max_epochs=20)
97
+ lr_config = dict(
98
+ policy='step',
99
+ warmup='linear',
100
+ warmup_iters=500,
101
+ warmup_ratio=0.001,
102
+ step=[16, 19])
configs/dsb/mask_rcnn_mobilevig_m_fpn_1x_dsb.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../mask_rcnn_mobilevig_m_fpn_1x_coco.py'
3
+ ]
4
+
5
+ model = dict(
6
+ roi_head=dict(
7
+ bbox_head=dict(num_classes=1),
8
+ mask_head=dict(num_classes=1)
9
+ ),
10
+ test_cfg=dict(
11
+ rpn=dict(nms_pre=1000, max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7), min_bbox_size=0),
12
+ rcnn=dict(
13
+ score_thr=0.05,
14
+ nms=dict(type='nms', iou_threshold=0.3),
15
+ max_per_img=200,
16
+ mask_thr_binary=0.5
17
+ )
18
+ )
19
+ )
20
+
21
+ # Dataset settings
22
+ dataset_type = 'DSBDataset'
23
+ data_root = '../../data/dsb2018/stage1_train'
24
+ img_norm_cfg = dict(
25
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
26
+
27
+ train_pipeline = [
28
+ dict(type='LoadImageFromFile'),
29
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
30
+ dict(type='LoadMasksFromAnn'),
31
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
32
+ dict(type='RandomFlip', flip_ratio=0.5),
33
+ dict(type='Normalize', **img_norm_cfg),
34
+ dict(type='Pad', size_divisor=32),
35
+ dict(type='DefaultFormatBundle'),
36
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
37
+ ]
38
+ test_pipeline = [
39
+ dict(type='LoadImageFromFile'),
40
+ dict(
41
+ type='MultiScaleFlipAug',
42
+ img_scale=(1333, 800),
43
+ flip=False,
44
+ transforms=[
45
+ dict(type='Resize', keep_ratio=True),
46
+ dict(type='RandomFlip'),
47
+ dict(type='Normalize', **img_norm_cfg),
48
+ dict(type='Pad', size_divisor=32),
49
+ dict(type='ImageToTensor', keys=['img']),
50
+ dict(type='Collect', keys=['img']),
51
+ ])
52
+ ]
53
+ data = dict(
54
+ samples_per_gpu=1,
55
+ workers_per_gpu=4,
56
+ train=dict(
57
+ type=dataset_type,
58
+ img_prefix=data_root,
59
+ ann_file='',
60
+ pipeline=train_pipeline),
61
+ val=dict(
62
+ type=dataset_type,
63
+ img_prefix=data_root,
64
+ ann_file='',
65
+ pipeline=test_pipeline),
66
+ test=dict(
67
+ type=dataset_type,
68
+ img_prefix=data_root,
69
+ ann_file='',
70
+ pipeline=test_pipeline))
71
+
72
+ # MobileViG Pretrained Weights
73
+ load_from = 'geovig_m_seg_4G/det_mobilevig_m_62_8.pth'
74
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05)
configs/dsb/mask_rcnn_resnet50_fpn_1x_dsb.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/schedules/schedule_1x.py',
4
+ '../_base_/default_runtime.py'
5
+ ]
6
+
7
+ model = dict(
8
+ roi_head=dict(
9
+ bbox_head=dict(num_classes=1),
10
+ mask_head=dict(num_classes=1)
11
+ ),
12
+ test_cfg=dict(
13
+ rpn=dict(nms_pre=1000, max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7), min_bbox_size=0),
14
+ rcnn=dict(
15
+ score_thr=0.05,
16
+ nms=dict(type='nms', iou_threshold=0.3),
17
+ max_per_img=200,
18
+ mask_thr_binary=0.5
19
+ )
20
+ )
21
+ )
22
+
23
+ # Dataset settings
24
+ dataset_type = 'DSBDataset'
25
+ data_root = '../../data/dsb2018/stage1_train'
26
+ img_norm_cfg = dict(
27
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
28
+ train_pipeline = [
29
+ dict(type='LoadImageFromFile'),
30
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
31
+ dict(type='LoadMasksFromAnn'),
32
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
33
+ dict(type='RandomFlip', flip_ratio=0.5),
34
+ dict(type='Normalize', **img_norm_cfg),
35
+ dict(type='Pad', size_divisor=32),
36
+ dict(type='DefaultFormatBundle'),
37
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
38
+ ]
39
+ test_pipeline = [
40
+ dict(type='LoadImageFromFile'),
41
+ dict(
42
+ type='MultiScaleFlipAug',
43
+ img_scale=(1333, 800),
44
+ flip=False,
45
+ transforms=[
46
+ dict(type='Resize', keep_ratio=True),
47
+ dict(type='RandomFlip'),
48
+ dict(type='Normalize', **img_norm_cfg),
49
+ dict(type='Pad', size_divisor=32),
50
+ dict(type='ImageToTensor', keys=['img']),
51
+ dict(type='Collect', keys=['img']),
52
+ ])
53
+ ]
54
+ data = dict(
55
+ samples_per_gpu=1,
56
+ workers_per_gpu=4,
57
+ train=dict(
58
+ type=dataset_type,
59
+ img_prefix=data_root,
60
+ ann_file='',
61
+ pipeline=train_pipeline),
62
+ val=dict(
63
+ type=dataset_type,
64
+ img_prefix=data_root,
65
+ ann_file='',
66
+ pipeline=test_pipeline),
67
+ test=dict(
68
+ type=dataset_type,
69
+ img_prefix=data_root,
70
+ ann_file='',
71
+ pipeline=test_pipeline))
72
+
73
+ load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth'
74
+ optimizer = dict(type='SGD', lr=0.002, momentum=0.9, weight_decay=0.0001)
configs/dsb/mask_rcnn_swin_t_fpn_1x_dsb.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/schedules/schedule_1x.py',
4
+ '../_base_/default_runtime.py'
5
+ ]
6
+
7
+ model = dict(
8
+ backbone=dict(
9
+ _delete_=True,
10
+ type='SwinTransformer',
11
+ embed_dims=96,
12
+ depths=[2, 2, 6, 2],
13
+ num_heads=[3, 6, 12, 24],
14
+ window_size=7,
15
+ mlp_ratio=4,
16
+ qkv_bias=True,
17
+ qk_scale=None,
18
+ drop_rate=0.,
19
+ attn_drop_rate=0.,
20
+ drop_path_rate=0.2,
21
+ patch_norm=True,
22
+ out_indices=(0, 1, 2, 3),
23
+ with_cp=False,
24
+ convert_weights=True,
25
+ ),
26
+ neck=dict(in_channels=[96, 192, 384, 768]),
27
+ roi_head=dict(
28
+ bbox_head=dict(num_classes=1),
29
+ mask_head=dict(num_classes=1)
30
+ ),
31
+ test_cfg=dict(
32
+ rpn=dict(nms_pre=1000, max_per_img=1000, nms=dict(type='nms', iou_threshold=0.7), min_bbox_size=0),
33
+ rcnn=dict(
34
+ score_thr=0.05,
35
+ nms=dict(type='nms', iou_threshold=0.3),
36
+ max_per_img=200,
37
+ mask_thr_binary=0.5
38
+ )
39
+ )
40
+ )
41
+
42
+ # Dataset settings
43
+ dataset_type = 'DSBDataset'
44
+ data_root = '../../data/dsb2018/stage1_train'
45
+ img_norm_cfg = dict(
46
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
47
+ train_pipeline = [
48
+ dict(type='LoadImageFromFile'),
49
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
50
+ dict(type='LoadMasksFromAnn'),
51
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
52
+ dict(type='RandomFlip', flip_ratio=0.5),
53
+ dict(type='Normalize', **img_norm_cfg),
54
+ dict(type='Pad', size_divisor=32),
55
+ dict(type='DefaultFormatBundle'),
56
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
57
+ ]
58
+ test_pipeline = [
59
+ dict(type='LoadImageFromFile'),
60
+ dict(
61
+ type='MultiScaleFlipAug',
62
+ img_scale=(1333, 800),
63
+ flip=False,
64
+ transforms=[
65
+ dict(type='Resize', keep_ratio=True),
66
+ dict(type='RandomFlip'),
67
+ dict(type='Normalize', **img_norm_cfg),
68
+ dict(type='Pad', size_divisor=32),
69
+ dict(type='ImageToTensor', keys=['img']),
70
+ dict(type='Collect', keys=['img']),
71
+ ])
72
+ ]
73
+ data = dict(
74
+ samples_per_gpu=1,
75
+ workers_per_gpu=4,
76
+ train=dict(
77
+ type=dataset_type,
78
+ img_prefix=data_root,
79
+ ann_file='',
80
+ pipeline=train_pipeline),
81
+ val=dict(
82
+ type=dataset_type,
83
+ img_prefix=data_root,
84
+ ann_file='',
85
+ pipeline=test_pipeline),
86
+ test=dict(
87
+ type=dataset_type,
88
+ img_prefix=data_root,
89
+ ann_file='',
90
+ pipeline=test_pipeline))
91
+
92
+ load_from = 'https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth'
93
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05)
configs/kvasir/mask_rcnn_fastvit_sa12_fpn_1x_kvasir.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/datasets/coco_instance.py',
4
+ '../_base_/schedules/schedule_1x.py',
5
+ '../_base_/default_runtime.py'
6
+ ]
7
+
8
+ # Requires importing fastvit_backbone in main/test scripts
9
+ custom_imports = dict(imports=['fastvit_backbone'], allow_failed_imports=False)
10
+
11
+ model = dict(
12
+ backbone=dict(
13
+ _delete_=True,
14
+ type='fastvit_sa12',
15
+ init_cfg=dict(type='Pretrained', checkpoint='geovig_m_5e4_8G_300_80_70/fastvit_sa12.pth.tar')
16
+ ),
17
+ neck=dict(
18
+ type='FPN',
19
+ in_channels=[64, 128, 256, 512],
20
+ out_channels=256,
21
+ num_outs=5,
22
+ norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)
23
+ ),
24
+ roi_head=dict(
25
+ bbox_head=dict(num_classes=1),
26
+ mask_head=dict(num_classes=1)
27
+ )
28
+ )
29
+
30
+ dataset_type = 'KvasirDataset'
31
+ data_root = '../../data/Kvasir-SEG'
32
+ img_norm_cfg = dict(
33
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
34
+
35
+ train_pipeline = [
36
+ dict(type='LoadImageFromFile'),
37
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
38
+ dict(type='LoadMasksFromAnn'),
39
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
40
+ dict(type='RandomFlip', flip_ratio=0.5),
41
+ dict(type='Normalize', **img_norm_cfg),
42
+ dict(type='Pad', size_divisor=32),
43
+ dict(type='DefaultFormatBundle'),
44
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
45
+ ]
46
+ test_pipeline = [
47
+ dict(type='LoadImageFromFile'),
48
+ dict(
49
+ type='MultiScaleFlipAug',
50
+ img_scale=(1333, 800),
51
+ flip=False,
52
+ transforms=[
53
+ dict(type='Resize', keep_ratio=True),
54
+ dict(type='RandomFlip'),
55
+ dict(type='Normalize', **img_norm_cfg),
56
+ dict(type='Pad', size_divisor=32),
57
+ dict(type='ImageToTensor', keys=['img']),
58
+ dict(type='Collect', keys=['img']),
59
+ ])
60
+ ]
61
+
62
+ data = dict(
63
+ samples_per_gpu=1,
64
+ workers_per_gpu=4,
65
+ train=dict(
66
+ type=dataset_type,
67
+ img_prefix=data_root,
68
+ ann_file=data_root + '/kavsir_bboxes.json',
69
+ pipeline=train_pipeline),
70
+ val=dict(
71
+ type=dataset_type,
72
+ img_prefix=data_root,
73
+ ann_file=data_root + '/kavsir_bboxes.json',
74
+ pipeline=test_pipeline),
75
+ test=dict(
76
+ type=dataset_type,
77
+ img_prefix=data_root,
78
+ ann_file=data_root + '/kavsir_bboxes.json',
79
+ pipeline=test_pipeline))
80
+
81
+ evaluation = dict(metric='mAP', save_best='mAP')
82
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05)
configs/kvasir/mask_rcnn_geovig_m_fpn_1x_kvasir.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/schedules/schedule_1x.py',
4
+ '../_base_/default_runtime.py'
5
+ ]
6
+
7
+ model = dict(
8
+ backbone=dict(
9
+ _delete_=True,
10
+ type='geovig_m_feat',
11
+ style='pytorch',
12
+ pretrained=False,
13
+ use_detect_adapter=True,
14
+ init_cfg=None
15
+ ),
16
+ neck=dict(
17
+ type='FPN',
18
+ in_channels=[64, 128, 256, 384],
19
+ out_channels=256,
20
+ num_outs=5,
21
+ norm_cfg=dict(type='GN', num_groups=32, requires_grad=True)
22
+ ),
23
+ roi_head=dict(
24
+ bbox_head=dict(num_classes=1),
25
+ mask_head=dict(num_classes=1)
26
+ )
27
+ )
28
+
29
+ # Dataset settings
30
+ dataset_type = 'KvasirDataset'
31
+ data_root = '../../data/Kvasir-SEG'
32
+ img_norm_cfg = dict(
33
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
34
+ train_pipeline = [
35
+ dict(type='LoadImageFromFile'),
36
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
37
+ dict(type='LoadMasksFromAnn'),
38
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
39
+ dict(type='RandomFlip', flip_ratio=0.5),
40
+ dict(type='Normalize', **img_norm_cfg),
41
+ dict(type='Pad', size_divisor=32),
42
+ dict(type='DefaultFormatBundle'),
43
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
44
+ ]
45
+ test_pipeline = [
46
+ dict(type='LoadImageFromFile'),
47
+ dict(
48
+ type='MultiScaleFlipAug',
49
+ img_scale=(1333, 800),
50
+ flip=False,
51
+ transforms=[
52
+ dict(type='Resize', keep_ratio=True),
53
+ dict(type='RandomFlip'),
54
+ dict(type='Normalize', **img_norm_cfg),
55
+ dict(type='Pad', size_divisor=32),
56
+ dict(type='ImageToTensor', keys=['img']),
57
+ dict(type='Collect', keys=['img']),
58
+ ])
59
+ ]
60
+ data = dict(
61
+ samples_per_gpu=1,
62
+ workers_per_gpu=4,
63
+ train=dict(
64
+ type=dataset_type,
65
+ img_prefix=data_root,
66
+ ann_file=data_root + '/kavsir_bboxes.json',
67
+ pipeline=train_pipeline),
68
+ val=dict(
69
+ type=dataset_type,
70
+ img_prefix=data_root,
71
+ ann_file=data_root + '/kavsir_bboxes.json',
72
+ pipeline=test_pipeline),
73
+ test=dict(
74
+ type=dataset_type,
75
+ img_prefix=data_root,
76
+ ann_file=data_root + '/kavsir_bboxes.json',
77
+ pipeline=test_pipeline))
78
+
79
+ # Fine-tuning config
80
+ load_from = 'geovig_m_seg_4G/epoch_12.pth'
81
+
82
+ evaluation = dict(metric='mAP')
83
+
84
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05)
85
+ optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=1.0, norm_type=2))
configs/kvasir/mask_rcnn_mobilevig_m_fpn_1x_kvasir.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../mask_rcnn_mobilevig_m_fpn_1x_coco.py'
3
+ ]
4
+
5
+ model = dict(
6
+ roi_head=dict(
7
+ bbox_head=dict(num_classes=1),
8
+ mask_head=dict(num_classes=1)
9
+ )
10
+ )
11
+
12
+ dataset_type = 'KvasirDataset'
13
+ data_root = '../../data/Kvasir-SEG'
14
+ img_norm_cfg = dict(
15
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
16
+
17
+ train_pipeline = [
18
+ dict(type='LoadImageFromFile'),
19
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
20
+ dict(type='LoadMasksFromAnn'),
21
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
22
+ dict(type='RandomFlip', flip_ratio=0.5),
23
+ dict(type='Normalize', **img_norm_cfg),
24
+ dict(type='Pad', size_divisor=32),
25
+ dict(type='DefaultFormatBundle'),
26
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
27
+ ]
28
+ test_pipeline = [
29
+ dict(type='LoadImageFromFile'),
30
+ dict(
31
+ type='MultiScaleFlipAug',
32
+ img_scale=(1333, 800),
33
+ flip=False,
34
+ transforms=[
35
+ dict(type='Resize', keep_ratio=True),
36
+ dict(type='RandomFlip'),
37
+ dict(type='Normalize', **img_norm_cfg),
38
+ dict(type='Pad', size_divisor=32),
39
+ dict(type='ImageToTensor', keys=['img']),
40
+ dict(type='Collect', keys=['img']),
41
+ ])
42
+ ]
43
+
44
+ data = dict(
45
+ samples_per_gpu=1,
46
+ workers_per_gpu=4,
47
+ train=dict(
48
+ type=dataset_type,
49
+ img_prefix=data_root,
50
+ ann_file=data_root + '/kavsir_bboxes.json',
51
+ pipeline=train_pipeline),
52
+ val=dict(
53
+ type=dataset_type,
54
+ img_prefix=data_root,
55
+ ann_file=data_root + '/kavsir_bboxes.json',
56
+ pipeline=test_pipeline),
57
+ test=dict(
58
+ type=dataset_type,
59
+ img_prefix=data_root,
60
+ ann_file=data_root + '/kavsir_bboxes.json',
61
+ pipeline=test_pipeline))
62
+
63
+ evaluation = dict(metric='mAP', save_best='mAP')
64
+
65
+ # MobileViG Pretrained Weights (COCO)
66
+ load_from = 'geovig_m_seg_4G/det_mobilevig_m_62_8.pth'
configs/kvasir/mask_rcnn_resnet50_fpn_1x_kvasir.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/datasets/coco_instance.py',
4
+ '../_base_/schedules/schedule_1x.py',
5
+ '../_base_/default_runtime.py'
6
+ ]
7
+
8
+ model = dict(
9
+ roi_head=dict(
10
+ bbox_head=dict(num_classes=1),
11
+ mask_head=dict(num_classes=1)
12
+ )
13
+ )
14
+
15
+ dataset_type = 'KvasirDataset'
16
+ data_root = '../../data/Kvasir-SEG'
17
+ img_norm_cfg = dict(
18
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
19
+
20
+ train_pipeline = [
21
+ dict(type='LoadImageFromFile'),
22
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
23
+ dict(type='LoadMasksFromAnn'),
24
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
25
+ dict(type='RandomFlip', flip_ratio=0.5),
26
+ dict(type='Normalize', **img_norm_cfg),
27
+ dict(type='Pad', size_divisor=32),
28
+ dict(type='DefaultFormatBundle'),
29
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
30
+ ]
31
+ test_pipeline = [
32
+ dict(type='LoadImageFromFile'),
33
+ dict(
34
+ type='MultiScaleFlipAug',
35
+ img_scale=(1333, 800),
36
+ flip=False,
37
+ transforms=[
38
+ dict(type='Resize', keep_ratio=True),
39
+ dict(type='RandomFlip'),
40
+ dict(type='Normalize', **img_norm_cfg),
41
+ dict(type='Pad', size_divisor=32),
42
+ dict(type='ImageToTensor', keys=['img']),
43
+ dict(type='Collect', keys=['img']),
44
+ ])
45
+ ]
46
+
47
+ data = dict(
48
+ samples_per_gpu=1,
49
+ workers_per_gpu=4,
50
+ train=dict(
51
+ type=dataset_type,
52
+ img_prefix=data_root,
53
+ ann_file=data_root + '/kavsir_bboxes.json',
54
+ pipeline=train_pipeline),
55
+ val=dict(
56
+ type=dataset_type,
57
+ img_prefix=data_root,
58
+ ann_file=data_root + '/kavsir_bboxes.json',
59
+ pipeline=test_pipeline),
60
+ test=dict(
61
+ type=dataset_type,
62
+ img_prefix=data_root,
63
+ ann_file=data_root + '/kavsir_bboxes.json',
64
+ pipeline=test_pipeline))
65
+
66
+ # COCO Pretrained Weights
67
+ load_from = 'https://download.openmmlab.com/mmdetection/v2.0/mask_rcnn/mask_rcnn_r50_fpn_1x_coco/mask_rcnn_r50_fpn_1x_coco_20200205-d4b0c5d6.pth'
68
+
69
+ evaluation = dict(metric='mAP', save_best='mAP')
70
+
71
+ optimizer = dict(type='SGD', lr=0.002, momentum=0.9, weight_decay=0.0001)
72
+
configs/kvasir/mask_rcnn_swin_t_fpn_1x_kvasir.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '../_base_/models/mask_rcnn_r50_fpn.py',
3
+ '../_base_/datasets/coco_instance.py',
4
+ '../_base_/schedules/schedule_1x.py',
5
+ '../_base_/default_runtime.py'
6
+ ]
7
+
8
+ model = dict(
9
+ backbone=dict(
10
+ _delete_=True,
11
+ type='SwinTransformer',
12
+ embed_dims=96,
13
+ depths=[2, 2, 6, 2],
14
+ num_heads=[3, 6, 12, 24],
15
+ window_size=7,
16
+ mlp_ratio=4,
17
+ qkv_bias=True,
18
+ qk_scale=None,
19
+ drop_rate=0.,
20
+ attn_drop_rate=0.,
21
+ drop_path_rate=0.2,
22
+ patch_norm=True,
23
+ out_indices=(0, 1, 2, 3),
24
+ with_cp=False,
25
+ convert_weights=True,
26
+ ),
27
+ neck=dict(in_channels=[96, 192, 384, 768]),
28
+ roi_head=dict(
29
+ bbox_head=dict(num_classes=1),
30
+ mask_head=dict(num_classes=1)
31
+ )
32
+ )
33
+
34
+ dataset_type = 'KvasirDataset'
35
+ data_root = '../../data/Kvasir-SEG'
36
+ img_norm_cfg = dict(
37
+ mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
38
+
39
+ train_pipeline = [
40
+ dict(type='LoadImageFromFile'),
41
+ dict(type='LoadAnnotations', with_bbox=True, with_mask=False),
42
+ dict(type='LoadMasksFromAnn'),
43
+ dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
44
+ dict(type='RandomFlip', flip_ratio=0.5),
45
+ dict(type='Normalize', **img_norm_cfg),
46
+ dict(type='Pad', size_divisor=32),
47
+ dict(type='DefaultFormatBundle'),
48
+ dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
49
+ ]
50
+ test_pipeline = [
51
+ dict(type='LoadImageFromFile'),
52
+ dict(
53
+ type='MultiScaleFlipAug',
54
+ img_scale=(1333, 800),
55
+ flip=False,
56
+ transforms=[
57
+ dict(type='Resize', keep_ratio=True),
58
+ dict(type='RandomFlip'),
59
+ dict(type='Normalize', **img_norm_cfg),
60
+ dict(type='Pad', size_divisor=32),
61
+ dict(type='ImageToTensor', keys=['img']),
62
+ dict(type='Collect', keys=['img']),
63
+ ])
64
+ ]
65
+
66
+ data = dict(
67
+ samples_per_gpu=1,
68
+ workers_per_gpu=4,
69
+ train=dict(
70
+ type=dataset_type,
71
+ img_prefix=data_root,
72
+ ann_file=data_root + '/kavsir_bboxes.json',
73
+ pipeline=train_pipeline),
74
+ val=dict(
75
+ type=dataset_type,
76
+ img_prefix=data_root,
77
+ ann_file=data_root + '/kavsir_bboxes.json',
78
+ pipeline=test_pipeline),
79
+ test=dict(
80
+ type=dataset_type,
81
+ img_prefix=data_root,
82
+ ann_file=data_root + '/kavsir_bboxes.json',
83
+ pipeline=test_pipeline))
84
+
85
+ evaluation = dict(metric='mAP', save_best='mAP')
86
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05,
87
+ paramwise_cfg=dict(custom_keys={'absolute_pos_embed': dict(decay_mult=0.),
88
+ 'relative_position_bias_table': dict(decay_mult=0.),
89
+ 'norm': dict(decay_mult=0.)}))
90
+
91
+ # We use init_cfg for backbone loading, but can also set load_from for the whole detector
92
+ load_from = 'https://download.openmmlab.com/mmdetection/v2.0/swin/mask_rcnn_swin-t-p4-w7_fpn_1x_coco/mask_rcnn_swin-t-p4-w7_fpn_1x_coco_20210902_120937-9d6b7cfa.pth'
configs/mask_rcnn_geovig_b_fpn_1x_coco.py ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ _base_ = [
3
+ '_base_/models/mask_rcnn_r50_fpn.py',
4
+ '_base_/datasets/coco_instance.py',
5
+ '_base_/schedules/schedule_1x.py',
6
+ '_base_/default_runtime.py'
7
+ ]
8
+
9
+ model = dict(
10
+ backbone=dict(
11
+ _delete_=True,
12
+ type='geovig_b_feat',
13
+ style='pytorch',
14
+ pretrained='../../geovig_b_5e4_8G_300_82_38/checkpoint.pth',
15
+ use_detect_adapter=True, # Enable new adapter
16
+ init_cfg=None
17
+ ),
18
+ neck=dict(
19
+ type='FPN',
20
+ in_channels=[80, 160, 320, 512],
21
+ out_channels=256,
22
+ num_outs=5,
23
+ norm_cfg=dict(type='GN', num_groups=32, requires_grad=True) # GroupNorm Stability
24
+ ))
25
+
26
+ # optimizer
27
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05) # Lower LR + AdamW
28
+ optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=1.0, norm_type=2)) # Gradient Clipping
29
+
30
+ # Evaluation: Disable BN buffer broadcast (redundant for frozen BN and causes sync hangs)
31
+ # Evaluation: DISABLED during training to prevent DDP sync timeouts. Validate offline.
32
+ evaluation = dict(interval=100, metric=['bbox', 'segm'])
configs/mask_rcnn_geovig_m_fpn_1x_coco.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ _base_ = [
3
+ '_base_/models/mask_rcnn_r50_fpn.py',
4
+ '_base_/datasets/coco_instance.py',
5
+ '_base_/schedules/schedule_1x.py',
6
+ '_base_/default_runtime.py'
7
+ ]
8
+
9
+ model = dict(
10
+ backbone=dict(
11
+ _delete_=True,
12
+ type='geovig_m_feat',
13
+ style='pytorch',
14
+ pretrained='../../geovig_m_5e4_8G_300_80_70/checkpoint.pth',
15
+ use_detect_adapter=True, # Enable new adapter
16
+ init_cfg=None
17
+ ),
18
+ neck=dict(
19
+ type='FPN',
20
+ in_channels=[64, 128, 256, 384],
21
+ out_channels=256,
22
+ num_outs=5,
23
+ norm_cfg=dict(type='GN', num_groups=32, requires_grad=True) # GroupNorm Stability
24
+ ))
25
+
26
+ # optimizer
27
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, weight_decay=0.05) # Lower LR + AdamW
28
+ optimizer_config = dict(_delete_=True, grad_clip=dict(max_norm=1.0, norm_type=2)) # Gradient Clipping
configs/mask_rcnn_mobilevig_b_fpn_1x_coco.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '_base_/models/mask_rcnn_r50_fpn.py',
3
+ '_base_/datasets/coco_instance.py',
4
+ '_base_/schedules/schedule_1x.py',
5
+ '_base_/default_runtime.py'
6
+ ]
7
+ model = dict(
8
+ backbone=dict(
9
+ type='mobilevig_b_feat',
10
+ style='pytorch',
11
+ init_cfg=dict(
12
+ type='Pretrained',
13
+ checkpoint='../Pretrained_Models_MobileViG/MobileViG_B_82_6.pth.tar',
14
+ ),
15
+ ),
16
+ neck=dict(
17
+ type='FPN',
18
+ in_channels=[42, 84, 240, 464],
19
+ out_channels=256,
20
+ num_outs=5))
21
+ # optimizer
22
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.0001)
23
+ optimizer_config = dict(grad_clip=None)
24
+
configs/mask_rcnn_mobilevig_m_fpn_1x_coco.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '_base_/models/mask_rcnn_r50_fpn.py',
3
+ '_base_/datasets/coco_instance.py',
4
+ '_base_/schedules/schedule_1x.py',
5
+ '_base_/default_runtime.py'
6
+ ]
7
+ # optimizer
8
+ model = dict(
9
+ backbone=dict(
10
+ type='mobilevig_m_feat',
11
+ style='pytorch',
12
+ init_cfg=dict(
13
+ type='Pretrained',
14
+ checkpoint='../../geovig_m_5e4_8G_300_80_70/MobileViG_M_80_7.pth.tar',
15
+ ),
16
+ ),
17
+ neck=dict(
18
+ type='FPN',
19
+ in_channels=[42, 84, 224, 400],
20
+ out_channels=256,
21
+ num_outs=5))
22
+ # optimizer
23
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.05)
24
+ optimizer_config = dict(grad_clip=None)
25
+
configs/mask_rcnn_mobilevigv2_b_fpn_1x_coco.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '_base_/models/mask_rcnn_r50_fpn.py',
3
+ '_base_/datasets/coco_instance.py',
4
+ '_base_/schedules/schedule_1x.py',
5
+ '_base_/default_runtime.py'
6
+ ]
7
+ model = dict(
8
+ backbone=dict(
9
+ type='mobilevigv2_b_feat',
10
+ style='pytorch',
11
+ init_cfg=dict(
12
+ type='Pretrained',
13
+ checkpoint='../Results/MobileViG_V2_B_Detection.pth',
14
+ ),
15
+ ),
16
+ neck=dict(
17
+ type='FPN',
18
+ in_channels=[64, 128, 256, 512],
19
+ out_channels=256,
20
+ num_outs=5))
21
+ # optimizer
22
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.0001)
23
+ optimizer_config = dict(grad_clip=None)
24
+
configs/mask_rcnn_mobilevigv2_m_fpn_1x_coco.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ _base_ = [
2
+ '_base_/models/mask_rcnn_r50_fpn.py',
3
+ '_base_/datasets/coco_instance.py',
4
+ '_base_/schedules/schedule_1x.py',
5
+ '_base_/default_runtime.py'
6
+ ]
7
+ # optimizer
8
+ model = dict(
9
+ backbone=dict(
10
+ type='mobilevigv2_m_feat',
11
+ style='pytorch',
12
+ init_cfg=dict(
13
+ type='Pretrained',
14
+ checkpoint='../Results/MobileViG_V2_M_Detection.pth',
15
+ ),
16
+ ),
17
+ neck=dict(
18
+ type='FPN',
19
+ in_channels=[32, 64, 192, 384],
20
+ out_channels=256,
21
+ num_outs=5))
22
+ # optimizer
23
+ optimizer = dict(_delete_=True, type='AdamW', lr=0.0002, weight_decay=0.05)
24
+ optimizer_config = dict(grad_clip=None)
25
+
configs/s ADDED
@@ -0,0 +1,324 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """ ImageNet Training Script
2
+ #train.py from vig_pytorch
3
+
4
+ This is intended to be a lean and easily modifiable ImageNet training script that reproduces ImageNet
5
+ training results with some of the latest networks and training techniques. It favours canonical PyTorch
6
+ and standard Python style over trying to be able to 'do it all.' That said, it offers quite a few speed
7
+ and training result improvements over the usual PyTorch example scripts. Repurpose as you see fit.
8
+
9
+ This script was started from an early version of the PyTorch ImageNet example
10
+ (https://github.com/pytorch/examples/tree/master/imagenet)
11
+
12
+ Hacked together by / Copyright 2020 Ross Wightman (https://github.com/rwightman)
13
+
14
+ Modified from the training script used for the Vision GNN model found in:
15
+ @misc{
16
+ https://doi.org/10.48550/arxiv.2206.00272,
17
+ doi = {10.48550/ARXIV.2206.00272},
18
+ url = {https://arxiv.org/abs/2206.00272},
19
+ author = {Han, Kai and Wang, Yunhe and Guo, Jianyuan and Tang, Yehui and Wu, Enhua},
20
+ keywords = {Computer Vision and Pattern Recognition (cs.CV), FOS: Computer and information sciences, FOS: Computer and information sciences},
21
+ title = {Vision GNN: An Image is Worth Graph of Nodes},
22
+ publisher = {arXiv},
23
+ year = {2022},
24
+ copyright = {arXiv.org perpetual, non-exclusive license}
25
+ }
26
+ """
27
+ # Copyright (c) OpenMMLab. All rights reserved.
28
+ import warnings
29
+ warnings.filterwarnings('ignore')
30
+ import argparse
31
+ import time
32
+ import yaml
33
+ import os
34
+ import logging
35
+ from collections import OrderedDict
36
+ from contextlib import suppress
37
+ from datetime import datetime
38
+
39
+ import copy
40
+ import os.path as osp
41
+
42
+ import torch
43
+ import torch.nn as nn
44
+ import torchvision.utils
45
+ from torch.nn.parallel import DistributedDataParallel
46
+ import timm
47
+
48
+ from timm.data import ImageDataset, resolve_data_config, Mixup, FastCollateMixup, AugMixDataset #create_loader
49
+ # from timm.models import create_model, resume_checkpoint, convert_splitbn_model
50
+ from timm.utils import *
51
+ from timm.loss import LabelSmoothingCrossEntropy, SoftTargetCrossEntropy, JsdCrossEntropy
52
+ from timm.optim import create_optimizer
53
+ from timm.scheduler import create_scheduler
54
+ from timm.utils import NativeScaler
55
+
56
+ import mmcv
57
+ from mmcv import Config, DictAction
58
+ from mmcv.runner import get_dist_info, init_dist
59
+ from mmcv.utils import get_git_hash
60
+
61
+ from mmdet import __version__
62
+ from mmdet.apis import init_random_seed, set_random_seed # , train_detector
63
+ from mmdet.datasets import build_dataset
64
+ from mmdet.models import build_detector
65
+ from mmdet.utils import (collect_env, get_device, get_root_logger,
66
+ setup_multi_processes, update_data_root)
67
+
68
+ from mmdet_custom.apis.train import train_detector
69
+ import mmcv_custom.runner.epoch_based_runner
70
+ import mmcv_custom.runner.optimizer
71
+
72
+ import mobilevigv2_backbone
73
+ import geovig_backbone
74
+
75
+ # # 1. Disable TF32 (H100 uses this by default, but it crashes older cuBLAS)
76
+ # torch.backends.cuda.matmul.allow_tf32 = False
77
+ # torch.backends.cudnn.allow_tf32 = False
78
+
79
+ # # 2. Force cuBLAS to use a specific, stable workspace configuration
80
+ # # This must match the environment variable you set earlier
81
+ # import os
82
+ # os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8'
83
+
84
+ # # 3. Disable the specific cuBLASLt heuristics that cause Hopper crashes
85
+ # os.environ['DISABLE_ADDMM_CUDA_LT'] = '1'
86
+
87
+ # # 4. Optional: Force deterministic math (prevents cuBLAS from choosing risky 'fast' paths)
88
+ # torch.use_deterministic_algorithms(True, warn_only=True)
89
+
90
+ parser = argparse.ArgumentParser(description='Train a detector')
91
+ parser.add_argument('config', help='train config file path')
92
+ parser.add_argument('--work-dir', help='the dir to save logs and models')
93
+ parser.add_argument('--mobilevigv2_model', default='mobilevigv2_m', type=str, metavar='MODEL',
94
+ help='Name of model to train (default: "mobilevigv2_m"')
95
+ parser.add_argument(
96
+ '--resume-from', help='the checkpoint file to resume from')
97
+ parser.add_argument(
98
+ '--auto-resume',
99
+ action='store_true',
100
+ help='resume from the latest checkpoint automatically')
101
+ parser.add_argument(
102
+ '--no-validate',
103
+ action='store_true',
104
+ help='whether not to evaluate the checkpoint during training')
105
+ group_gpus = parser.add_mutually_exclusive_group()
106
+ group_gpus.add_argument(
107
+ '--gpus',
108
+ type=int,
109
+ help='(Deprecated, please use --gpu-id) number of gpus to use '
110
+ '(only applicable to non-distributed training)')
111
+ group_gpus.add_argument(
112
+ '--gpu-ids',
113
+ type=int,
114
+ nargs='+',
115
+ help='(Deprecated, please use --gpu-id) ids of gpus to use '
116
+ '(only applicable to non-distributed training)')
117
+ group_gpus.add_argument(
118
+ '--gpu-id',
119
+ type=int,
120
+ default=0,
121
+ help='id of gpu to use '
122
+ '(only applicable to non-distributed training)')
123
+ parser.add_argument('--seed', type=int, default=None, help='random seed')
124
+ parser.add_argument(
125
+ '--diff-seed',
126
+ action='store_true',
127
+ help='Whether or not set different seeds for different ranks')
128
+ parser.add_argument(
129
+ '--deterministic',
130
+ action='store_true',
131
+ help='whether to set deterministic options for CUDNN backend.')
132
+ parser.add_argument(
133
+ '--options',
134
+ nargs='+',
135
+ action=DictAction,
136
+ help='override some settings in the used config, the key-value pair '
137
+ 'in xxx=yyy format will be merged into config file (deprecate), '
138
+ 'change to --cfg-options instead.')
139
+ parser.add_argument(
140
+ '--cfg-options',
141
+ nargs='+',
142
+ action=DictAction,
143
+ help='override some settings in the used config, the key-value pair '
144
+ 'in xxx=yyy format will be merged into config file. If the value to '
145
+ 'be overwritten is a list, it should be like key="[a,b]" or key=a,b '
146
+ 'It also allows nested list/tuple values, e.g. key="[(a,b),(c,d)]" '
147
+ 'Note that the quotation marks are necessary and that no white space '
148
+ 'is allowed.')
149
+ parser.add_argument(
150
+ '--launcher',
151
+ choices=['none', 'pytorch', 'slurm', 'mpi'],
152
+ default='none',
153
+ help='job launcher')
154
+ parser.add_argument('--local_rank', type=int, default=0)
155
+ parser.add_argument(
156
+ '--auto-scale-lr',
157
+ action='store_true',
158
+ help='enable automatically scaling LR.')
159
+
160
+ args = parser.parse_args()
161
+ print(args)
162
+ print("\n mobilevigv2_model = " + str(args.mobilevigv2_model))
163
+
164
+ def parse_args():
165
+ args = parser.parse_args()
166
+ if 'LOCAL_RANK' not in os.environ:
167
+ os.environ['LOCAL_RANK'] = str(args.local_rank)
168
+
169
+ if args.options and args.cfg_options:
170
+ raise ValueError(
171
+ '--options and --cfg-options cannot be both '
172
+ 'specified, --options is deprecated in favor of --cfg-options')
173
+ if args.options:
174
+ warnings.warn('--options is deprecated in favor of --cfg-options')
175
+ args.cfg_options = args.options
176
+
177
+ return args
178
+
179
+
180
+ def main():
181
+ args = parse_args()
182
+
183
+ cfg = Config.fromfile(args.config)
184
+
185
+ # update data root according to MMDET_DATASETS
186
+ update_data_root(cfg)
187
+
188
+ if args.cfg_options is not None:
189
+ cfg.merge_from_dict(args.cfg_options)
190
+
191
+ if args.auto_scale_lr:
192
+ if 'auto_scale_lr' in cfg and \
193
+ 'enable' in cfg.auto_scale_lr and \
194
+ 'base_batch_size' in cfg.auto_scale_lr:
195
+ cfg.auto_scale_lr.enable = True
196
+ else:
197
+ warnings.warn('Can not find "auto_scale_lr" or '
198
+ '"auto_scale_lr.enable" or '
199
+ '"auto_scale_lr.base_batch_size" in your'
200
+ ' configuration file. Please update all the '
201
+ 'configuration files to mmdet >= 2.24.1.')
202
+
203
+ # set multi-process settings
204
+ setup_multi_processes(cfg)
205
+
206
+ # set cudnn_benchmark
207
+ if cfg.get('cudnn_benchmark', False):
208
+ torch.backends.cudnn.benchmark = True
209
+
210
+ # work_dir is determined in this priority: CLI > segment in file > filename
211
+ if args.work_dir is not None:
212
+ # update configs according to CLI args if args.work_dir is not None
213
+ cfg.work_dir = args.work_dir
214
+ elif cfg.get('work_dir', None) is None:
215
+ # use config filename as default work_dir if cfg.work_dir is None
216
+ cfg.work_dir = osp.join('./work_dirs',
217
+ osp.splitext(osp.basename(args.config))[0])
218
+ if args.resume_from is not None:
219
+ cfg.resume_from = args.resume_from
220
+ cfg.auto_resume = args.auto_resume
221
+ if args.gpus is not None:
222
+ cfg.gpu_ids = range(1)
223
+ warnings.warn('`--gpus` is deprecated because we only support '
224
+ 'single GPU mode in non-distributed training. '
225
+ 'Use `gpus=1` now.')
226
+ if args.gpu_ids is not None:
227
+ cfg.gpu_ids = args.gpu_ids[0:1]
228
+ warnings.warn('`--gpu-ids` is deprecated, please use `--gpu-id`. '
229
+ 'Because we only support single GPU mode in '
230
+ 'non-distributed training. Use the first GPU '
231
+ 'in `gpu_ids` now.')
232
+ if args.gpus is None and args.gpu_ids is None:
233
+ cfg.gpu_ids = [args.gpu_id]
234
+
235
+ # init distributed env first, since logger depends on the dist info.
236
+ if args.launcher == 'none':
237
+ distributed = False
238
+ print("NOT DISTRIBUTED LAUNCH")
239
+ else:
240
+ print("DIST LAUNCH")
241
+ distributed = True
242
+ init_dist(args.launcher, **cfg.dist_params)
243
+ # re-set gpu_ids with distributed training mode
244
+ _, world_size = get_dist_info()
245
+ cfg.gpu_ids = range(world_size)
246
+ print ("GPU IDS")
247
+ print(cfg.gpu_ids)
248
+
249
+ # create work_dir
250
+ mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir))
251
+ # dump config
252
+ cfg.dump(osp.join(cfg.work_dir, osp.basename(args.config)))
253
+ # init the logger before other steps
254
+ timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime())
255
+ log_file = osp.join(cfg.work_dir, f'{timestamp}.log')
256
+ logger = get_root_logger(log_file=log_file, log_level=cfg.log_level)
257
+
258
+ # init the meta dict to record some important information such as
259
+ # environment info and seed, which will be logged
260
+ meta = dict()
261
+ # log env info
262
+ env_info_dict = collect_env()
263
+ env_info = '\n'.join([(f'{k}: {v}') for k, v in env_info_dict.items()])
264
+ dash_line = '-' * 60 + '\n'
265
+ logger.info('Environment info:\n' + dash_line + env_info + '\n' +
266
+ dash_line)
267
+ meta['env_info'] = env_info
268
+ meta['config'] = cfg.pretty_text
269
+ # log some basic info
270
+ logger.info(f'Distributed training: {distributed}')
271
+ logger.info(f'Config:\n{cfg.pretty_text}')
272
+
273
+ cfg.device = get_device()
274
+ # set random seeds
275
+ seed = init_random_seed(args.seed, device=cfg.device)
276
+ seed = seed + dist.get_rank() if args.diff_seed else seed
277
+ logger.info(f'Set random seed to {seed}, '
278
+ f'deterministic: {args.deterministic}')
279
+ set_random_seed(seed, deterministic=args.deterministic)
280
+ cfg.seed = seed
281
+ meta['seed'] = seed
282
+ meta['exp_name'] = osp.basename(args.config)
283
+
284
+ model = build_detector(
285
+ cfg.model,
286
+ train_cfg=cfg.get('train_cfg'),
287
+ test_cfg=cfg.get('test_cfg'))
288
+ # drop_connect_rate=0.1,
289
+ # drop_path_rate=0.1,
290
+ # drop_block_rate=0.1)
291
+ model.init_weights()
292
+
293
+ print("\n\n\nCONFIG OF MODEL: \n" + str(cfg.model) + "\n\n\n")
294
+
295
+ datasets = [build_dataset(cfg.data.train)]
296
+ if len(cfg.workflow) == 2:
297
+ val_dataset = copy.deepcopy(cfg.data.val)
298
+ val_dataset.pipeline = cfg.data.train.pipeline
299
+ datasets.append(build_dataset(val_dataset))
300
+ if cfg.checkpoint_config is not None:
301
+ # save mmdet version, config file content and class names in
302
+ # checkpoints as meta data
303
+ cfg.checkpoint_config.meta = dict(
304
+ mmdet_version=__version__ + get_git_hash()[:7],
305
+ CLASSES=datasets[0].CLASSES)
306
+ # add an attribute for visualization convenience
307
+ model.CLASSES = datasets[0].CLASSES
308
+
309
+ print("\n\n\nTRAIN DETECTOR CONFIG OF MODEL: \n" + str(cfg) + "\n\n\n")
310
+
311
+ print("\n\n\TRAIN DETECTOR MODEL: \n" + str(model) + "\n\n\n")
312
+
313
+ train_detector(
314
+ model,
315
+ datasets,
316
+ cfg,
317
+ distributed=distributed,
318
+ validate=(not args.no_validate),
319
+ timestamp=timestamp,
320
+ meta=meta)
321
+
322
+
323
+ if __name__ == '__main__':
324
+ main()
examples/000000032081.jpg ADDED

Git LFS Details

  • SHA256: 2acde5232e9dd81c012d6e0314f733985f603c95d7d482d5a52e4191daa00bae
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
examples/000000045472.jpg ADDED

Git LFS Details

  • SHA256: 0e7a75145b74abbf6e26aa3ab0bafa2f2625d4e8014c7bd4ae938c12e08a8131
  • Pointer size: 131 Bytes
  • Size of remote file: 243 kB
examples/0c2550a23b8a0f29a7575de8c61690d3c31bc897dd5ba66caec201d201a278c2.png ADDED

Git LFS Details

  • SHA256: 3de491549810c22846c18309bdbbffc09eaf0d7d095ac49606f4185afdfa9528
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
examples/cat.png ADDED

Git LFS Details

  • SHA256: ef5f47eb0f7a5f394126bd6f5016d728fe45a082da27dfa8b2b479c6adcbe9ce
  • Pointer size: 132 Bytes
  • Size of remote file: 3.61 MB
examples/cju5y84q3mdv50817eyp82xf3.jpg ADDED

Git LFS Details

  • SHA256: 9e7b85705297ae6afddc34036743514e54ffb1a391d24f0c499d5c137c195a39
  • Pointer size: 130 Bytes
  • Size of remote file: 31 kB
examples/n03394916_14162.JPEG ADDED

Git LFS Details

  • SHA256: b7ae56614ed0688a6f09fc6a7d8e59a263076bd5d489986805a430471ca921d1
  • Pointer size: 131 Bytes
  • Size of remote file: 512 kB
examples/n03417042_2960.JPEG ADDED

Git LFS Details

  • SHA256: cfa85f75b097038dfd12be3b63e5226fa07b969ebfac5dd57520cb36a4fb7f78
  • Pointer size: 131 Bytes
  • Size of remote file: 166 kB
geovig.py ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ import torch.nn.functional as F
4
+ from torch.nn import Sequential as Seq
5
+ import logging
6
+
7
+ from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
8
+ try:
9
+ from timm.layers import DropPath # timm >= 0.9
10
+ except ImportError:
11
+ from timm.models.layers import DropPath # timm 0.6.x
12
+ from timm.models import register_model
13
+
14
+ '''
15
+ @article{han2022vision,
16
+ title={Vision GNN: An Image is Worth Graph of Nodes},
17
+ author={Han, Kai and Wang, Yunhe and Guo, Jianyuan and Tang, Yehui and Wu, Enhua},
18
+ journal={arXiv preprint arXiv:2206.00272},
19
+ year={2022}
20
+ }
21
+ '''
22
+
23
+ def _cfg(url='', **kwargs):
24
+ return {
25
+ 'url': url,
26
+ 'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': None,
27
+ 'crop_pct': .9, 'interpolation': 'bicubic',
28
+ 'mean': IMAGENET_DEFAULT_MEAN, 'std': IMAGENET_DEFAULT_STD,
29
+ 'classifier': 'head',
30
+ **kwargs
31
+ }
32
+
33
+
34
+ default_cfgs = {
35
+ 'mobilevig': _cfg(crop_pct=0.9, mean=IMAGENET_DEFAULT_MEAN, std=IMAGENET_DEFAULT_STD)
36
+ }
37
+
38
+
39
+ class Stem(nn.Module):
40
+ def __init__(self, input_dim, output_dim, activation=nn.GELU):
41
+ super(Stem, self).__init__()
42
+ self.stem = nn.Sequential(
43
+ nn.Conv2d(input_dim, output_dim // 2, kernel_size=3, stride=2, padding=1),
44
+ nn.BatchNorm2d(output_dim // 2),
45
+ nn.GELU(),
46
+ nn.Conv2d(output_dim // 2, output_dim, kernel_size=3, stride=2, padding=1),
47
+ nn.BatchNorm2d(output_dim),
48
+ nn.GELU()
49
+ )
50
+
51
+ def forward(self, x):
52
+ return self.stem(x)
53
+
54
+
55
+ class MLP(nn.Module):
56
+ """
57
+ Implementation of MLP with 1*1 convolutions.
58
+ Input: tensor with shape [B, C, H, W]
59
+ """
60
+
61
+ def __init__(self, in_features, hidden_features=None,
62
+ out_features=None, drop=0., mid_conv=False):
63
+ super().__init__()
64
+ out_features = out_features or in_features
65
+ hidden_features = hidden_features or in_features
66
+ self.mid_conv = mid_conv
67
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
68
+ self.act = nn.GELU()
69
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
70
+ self.drop = nn.Dropout(drop)
71
+
72
+ if self.mid_conv:
73
+ self.mid = nn.Conv2d(hidden_features, hidden_features, kernel_size=3, stride=1, padding=1,
74
+ groups=hidden_features)
75
+ self.mid_norm = nn.BatchNorm2d(hidden_features)
76
+
77
+ self.norm1 = nn.BatchNorm2d(hidden_features)
78
+ self.norm2 = nn.BatchNorm2d(out_features)
79
+
80
+ def forward(self, x):
81
+ x = self.fc1(x)
82
+ x = self.norm1(x)
83
+ x = self.act(x)
84
+
85
+ if self.mid_conv:
86
+ x_mid = self.mid(x)
87
+ x_mid = self.mid_norm(x_mid)
88
+ x = self.act(x_mid)
89
+ x = self.drop(x)
90
+
91
+ x = self.fc2(x)
92
+ x = self.norm2(x)
93
+
94
+ x = self.drop(x)
95
+ return x
96
+
97
+
98
+ class InvertedResidual(nn.Module):
99
+ def __init__(self, dim, mlp_ratio=4., drop=0., drop_path=0., use_layer_scale=True, layer_scale_init_value=1e-5):
100
+ super().__init__()
101
+
102
+ mlp_hidden_dim = int(dim * mlp_ratio)
103
+ self.mlp = MLP(in_features=dim, hidden_features=mlp_hidden_dim, drop=drop, mid_conv=True)
104
+
105
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
106
+ else nn.Identity()
107
+ self.use_layer_scale = use_layer_scale
108
+ if use_layer_scale:
109
+ self.layer_scale_2 = nn.Parameter(
110
+ layer_scale_init_value * torch.ones(dim).unsqueeze(-1).unsqueeze(-1), requires_grad=True)
111
+
112
+ def forward(self, x):
113
+ if self.use_layer_scale:
114
+ x = x + self.drop_path(self.layer_scale_2 * self.mlp(x))
115
+ else:
116
+ x = x + self.drop_path(self.mlp(x))
117
+ return x
118
+
119
+
120
+ # -------------------- New graph utilities and GeoViG additions --------------------
121
+
122
+ def img_to_graph(x):
123
+ """
124
+ Converts (B, C, H, W) -> (B, N, C) and generates a grid adjacency (8-neighbor-ish).
125
+ For efficiency this returns a single edge_index shared for the batch.
126
+ """
127
+ B, C, H, W = x.shape
128
+ device = x.device
129
+ N = H * W
130
+
131
+ # Flatten features: (B, C, H, W) -> (B, N, C)
132
+ x_flat = x.flatten(2).transpose(1, 2) # (B, N, C)
133
+
134
+ # Generate grid edges (simplified 8-neighbor connectivity)
135
+ idx = torch.arange(N, device=device).view(H, W)
136
+ edges = []
137
+
138
+ # Right neighbors
139
+ if W > 1:
140
+ edges.append(torch.stack([idx[:, :-1].flatten(), idx[:, 1:].flatten()], dim=0))
141
+ # Left (will be mirrored later)
142
+ # Down neighbors
143
+ if H > 1:
144
+ edges.append(torch.stack([idx[:-1, :].flatten(), idx[1:, :].flatten()], dim=0))
145
+ # Diagonals
146
+ if H > 1 and W > 1:
147
+ edges.append(torch.stack([idx[:-1, :-1].flatten(), idx[1:, 1:].flatten()], dim=0))
148
+ edges.append(torch.stack([idx[1:, :-1].flatten(), idx[:-1, 1:].flatten()], dim=0))
149
+
150
+ if len(edges) == 0:
151
+ edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
152
+ else:
153
+ edge_index = torch.cat(edges, dim=1)
154
+ # Make undirected by mirroring
155
+ edge_index = torch.cat([edge_index, edge_index.flip(0)], dim=1)
156
+
157
+ return x_flat, edge_index
158
+
159
+
160
+ class GraphMRConv(nn.Module):
161
+ """
162
+ Graph version of Max-Relative Graph Convolution.
163
+ Operates on (B, N, C) and edge_index (2, E).
164
+ Uses PyTorch's index_reduce_ with 'amax' for true max aggregation.
165
+ """
166
+ def __init__(self, in_channels, out_channels, concat=False):
167
+ super().__init__()
168
+ self.in_channels = in_channels
169
+ self.out_channels = out_channels
170
+ self.concat = concat
171
+ linear_in = 2 * in_channels if concat else in_channels
172
+ self.nn = nn.Sequential(
173
+ nn.Linear(linear_in, out_channels),
174
+ nn.GELU()
175
+ )
176
+
177
+ def forward(self, x, edge_index):
178
+ # x: (B, N, C)
179
+ B, N, C = x.shape
180
+ device = x.device
181
+
182
+ # Reshape to (B*N, C) for batched processing
183
+ x_flat = x.view(-1, C)
184
+
185
+ if edge_index.numel() == 0:
186
+ # No edges: fallback to concatenation with zeros
187
+ # If no edges, no aggregation, so the update is zero.
188
+ # We apply the non-linearity to a zero tensor to maintain shape and type.
189
+ return self.nn(torch.zeros_like(x_flat)).view(B, N, -1)
190
+
191
+ row, col = edge_index # (E,), (E,)
192
+ num_edges = row.size(0)
193
+
194
+ # Create batched edge index: (2, B*E)
195
+ batch_offsets = torch.arange(B, device=device) * N
196
+ edge_offsets = batch_offsets.view(-1, 1).repeat(1, num_edges).view(-1)
197
+
198
+ row_batch = row.repeat(B) + edge_offsets
199
+ col_batch = col.repeat(B) + edge_offsets
200
+
201
+ # Gather features
202
+ # Gather features
203
+ x_flat_c = x_flat.contiguous() # Ensure memory layout for scatter
204
+ # Optimization: Don't compute x_j - x_i explicitly (huge tensor).
205
+ # max(x_j - x_i) = max(x_j) - x_i
206
+ x_j = x_flat_c[col_batch] # (B*E, C)
207
+
208
+ # Initialize aggregation buffer
209
+ aggr = torch.full_like(x_flat, -1e9) # (B*N, C)
210
+
211
+ # Vectorized Max Aggregation of Neighbors
212
+ index_expanded = row_batch.view(-1, 1).expand(-1, C)
213
+ aggr.scatter_reduce_(0, index_expanded, x_j, reduce='amax', include_self=True)
214
+
215
+ # Replace -1e9 with 0 (although strictly if we subtract x_i later, we need to handle no-neighbor case carefully)
216
+ # If include_self=True, then x_i is included in x_j? No.
217
+ # include_self=True in scatter_reduce_ means "reduction includes the initial value of self (aggr)".
218
+ # Our initial value is -1e9. So it works as a safe initializer.
219
+
220
+ aggr = torch.where(aggr == -1e9, torch.zeros_like(aggr), aggr)
221
+
222
+ if self.concat:
223
+ rel = aggr - x_flat
224
+ out = torch.cat([x_flat, rel], dim=-1) # (B*N, 2C)
225
+ else:
226
+ out = aggr - x_flat # (B*N, C)
227
+
228
+ return self.nn(out).view(B, N, -1)
229
+
230
+
231
+ class GraphMobileViGBlock(nn.Module):
232
+ """
233
+ Graph MobileViG block: GraphMRConv + FFN for (B,N,C) representations.
234
+ """
235
+ def __init__(self, dim, drop=0.0, concat=False):
236
+ super().__init__()
237
+ self.norm1 = nn.LayerNorm(dim)
238
+ self.graph_conv = GraphMRConv(dim, dim, concat=concat)
239
+ self.norm2 = nn.LayerNorm(dim)
240
+ self.ffn = nn.Sequential(
241
+ nn.Linear(dim, dim * 4),
242
+ nn.GELU(),
243
+ nn.Linear(dim * 4, dim),
244
+ nn.Dropout(drop)
245
+ )
246
+
247
+ def forward(self, x, edge_index):
248
+ # x: (B, N, C)
249
+ shortcut = x
250
+ x = self.norm1(x)
251
+ x = self.graph_conv(x, edge_index)
252
+ x = x + shortcut
253
+
254
+ shortcut = x
255
+ x = self.norm2(x)
256
+ x = self.ffn(x)
257
+ x = x + shortcut
258
+ return x
259
+
260
+
261
+ class SpreadEdgePool(nn.Module):
262
+ """
263
+ Geometry-Aware SpreadEdgePool: uses edge scores (diffusion distance proxy) as
264
+ attention weights to guide feature pooling. Ensures nodes with high structural
265
+ diversity contribute more to the pooled representation.
266
+ """
267
+ def __init__(self, in_channels, ratio=0.5):
268
+ super().__init__()
269
+ self.ratio = ratio
270
+ self.in_channels = in_channels
271
+
272
+ def forward(self, x, edge_index):
273
+ """
274
+ x: (B, N, C)
275
+ edge_index: (2, E)
276
+ Returns:
277
+ x_pooled: (B, N_new, C) with geometry-aware weighting
278
+ new_edge_index: (2, E_new)
279
+ """
280
+ B, N, C = x.shape
281
+ device = x.device
282
+ num_keep = max(1, int(N * self.ratio))
283
+
284
+ # Fallback if no edges
285
+ if edge_index.numel() == 0:
286
+ x_pooled = F.adaptive_avg_pool1d(x.transpose(1, 2), num_keep).transpose(1, 2)
287
+ idx = torch.arange(num_keep, device=device)
288
+ new_edge_index = torch.stack([idx[:-1], idx[1:]], dim=0) if num_keep > 1 else torch.zeros((2, 0), dtype=torch.long, device=device)
289
+ return x_pooled, new_edge_index
290
+
291
+ row, col = edge_index # (E,), (E,)
292
+
293
+ # Geometry-Aware Scoring: Diffusion distance proxy via feature space
294
+ # Optimization: Calculate distance WITHOUT materializing (B, E, C) difference tensor.
295
+ # |A - B|^2 = |A|^2 + |B|^2 - 2(A . B)
296
+
297
+ # Precompute norms: (B, N, 1)
298
+ x_sq = (x ** 2).sum(dim=-1, keepdim=True)
299
+
300
+ # Gather norms: (B, E, 1)
301
+ sq_i = x_sq[:, row, :]
302
+ sq_j = x_sq[:, col, :]
303
+
304
+ # Gather features just for dot (this still uses memory but dot reduces it immediately)
305
+ x_row = x[:, row, :] # (B, E, C)
306
+ x_col = x[:, col, :] # (B, E, C)
307
+ dot = (x_row * x_col).sum(dim=-1, keepdim=True) # (B, E, 1)
308
+
309
+ # Combine
310
+ dist_sq = sq_i + sq_j - 2 * dot
311
+ dist = torch.sqrt(F.relu(dist_sq) + 1e-6).squeeze(-1) # (B, E)
312
+
313
+ # Spread score: high score = high distance = high structural diversity
314
+ # Original: "similarity = torch.exp(-dist)", "edge_scores = 1.0 / (similarity + 1e-6)"
315
+ # This means edge_scores = exp(dist). So higher distance means higher score.
316
+ # Using distance directly achieves this.
317
+ edge_scores = dist # Linear distance directly.
318
+
319
+ # Map edge scores to node importance: sum scores of incident edges
320
+ # Average over batch for global topology understanding
321
+ avg_edge_scores = edge_scores.mean(dim=0) # (E,)
322
+
323
+ # Aggregate edge importance to nodes via index_add_
324
+ node_importance = torch.zeros(N, device=device)
325
+ node_importance.index_add_(0, row, avg_edge_scores) # Sum incoming edge scores per node
326
+
327
+ # Normalize importance scores to [0, 1] via sigmoid
328
+ node_weights = torch.sigmoid(node_importance).view(1, N, 1) # (1, N, 1) for broadcasting
329
+
330
+ # Geometry-Aware Pooling: Weight features by their geometric importance before aggregation
331
+ x_weighted = x * node_weights # (B, N, C), broadcast multiply
332
+
333
+ # Adaptive pooling now prioritizes geometrically important features
334
+ x_pooled = F.adaptive_avg_pool1d(x_weighted.transpose(1, 2), num_keep).transpose(1, 2) # (B, num_keep, C)
335
+
336
+ # Build heuristic new adjacency: simple chain / local ring
337
+ idx = torch.arange(num_keep, device=device)
338
+ if num_keep > 1:
339
+ left = idx[:-1]
340
+ right = idx[1:]
341
+ new_edge_index = torch.cat([torch.stack([left, right], dim=0), torch.stack([right, left], dim=0)], dim=1)
342
+ else:
343
+ new_edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
344
+
345
+ return x_pooled, new_edge_index
346
+
347
+
348
+ class Downsample(nn.Module):
349
+ """
350
+ Convolution-based downsample
351
+ """
352
+ def __init__(self, in_dim, out_dim):
353
+ super().__init__()
354
+ self.conv = nn.Sequential(
355
+ nn.Conv2d(in_dim, out_dim, 3, stride=2, padding=1),
356
+ nn.BatchNorm2d(out_dim),
357
+ )
358
+
359
+ def forward(self, x):
360
+ x = self.conv(x)
361
+ return x
362
+
363
+ class GeoViG(nn.Module):
364
+ """
365
+ Hybrid Grid->Graph GeoViG model. Can be used for classification or as a backbone for detection.
366
+ """
367
+ def __init__(self,
368
+ local_blocks=[2, 2],
369
+ local_channels=[42, 84],
370
+ graph_blocks=[2],
371
+ graph_channels=[168],
372
+ num_classes=1000,
373
+ pool_ratio=0.5,
374
+ distillation=False,
375
+ features_only=False,
376
+ out_indices=None,
377
+ pretrained=None,
378
+ use_concat=False,
379
+ **kwargs):
380
+ super().__init__()
381
+
382
+ self.features_only = features_only
383
+ self.out_indices = out_indices
384
+ self.distillation = distillation
385
+ self.pretrained = pretrained
386
+ self.use_concat = use_concat
387
+
388
+ # Stage 0: stem
389
+ self.stem = Stem(input_dim=3, output_dim=local_channels[0])
390
+
391
+ # Grid stages
392
+ self.grid_stages = nn.ModuleList()
393
+ in_c = local_channels[0]
394
+
395
+ self.feature_info = [dict(num_chs=in_c, reduction=2, module=f'stem')]
396
+
397
+ for i, out_c in enumerate(local_channels):
398
+ layers = []
399
+ if i > 0:
400
+ layers.append(Downsample(in_c, out_c))
401
+ in_c = out_c
402
+ for _ in range(local_blocks[i]):
403
+ layers.append(InvertedResidual(dim=out_c, mlp_ratio=4, drop_path=0.0))
404
+ self.grid_stages.append(nn.Sequential(*layers))
405
+ self.feature_info.append(dict(num_chs=out_c, reduction=2**(i+2), module=f'grid_stages.{i}'))
406
+
407
+ # Transition: grid -> graph
408
+ self.to_graph_pool = SpreadEdgePool(in_channels=in_c, ratio=pool_ratio)
409
+ self.graph_dim = in_c
410
+
411
+ # Graph stages
412
+ self.graph_proj = nn.Linear(self.graph_dim, graph_channels[0]) if graph_channels else nn.Identity()
413
+ self.graph_dim = graph_channels[0] if graph_channels else self.graph_dim
414
+
415
+ self.graph_stages = nn.ModuleList()
416
+ for _ in range(graph_blocks[0] if len(graph_blocks) > 0 else 0):
417
+ self.graph_stages.append(GraphMobileViGBlock(dim=self.graph_dim, concat=use_concat))
418
+
419
+ self.feature_info.append(dict(num_chs=self.graph_dim, reduction=2**(len(local_channels)+1), module='graph_stages'))
420
+
421
+ if not self.features_only:
422
+ # Head for classification
423
+ self.norm = nn.LayerNorm(self.graph_dim)
424
+ self.head = nn.Linear(self.graph_dim, num_classes)
425
+ if self.distillation:
426
+ self.head_dist = nn.Linear(self.graph_dim, num_classes)
427
+ else:
428
+ # For torchvision FPN
429
+ self._out_channels = []
430
+ if self.out_indices:
431
+ for i in self.out_indices:
432
+ # The indices for feature_info are shifted by 1 compared to grid_stages
433
+ self._out_channels.append(self.feature_info[i+1]['num_chs'])
434
+
435
+ if self.pretrained:
436
+ self._load_pretrained_weights()
437
+
438
+ @property
439
+ def out_channels(self):
440
+ if not self.features_only:
441
+ raise ValueError("out_channels is only available in features_only mode.")
442
+ return self._out_channels
443
+
444
+ def _load_pretrained_weights(self):
445
+ """Load pretrained weights from checkpoint."""
446
+ if self.pretrained and isinstance(self.pretrained, str):
447
+ logging.info(f"Loading pretrained weights from {self.pretrained}")
448
+ state_dict = torch.load(self.pretrained, map_location='cpu', weights_only=False)
449
+
450
+ if 'state_dict' in state_dict:
451
+ state_dict = state_dict['state_dict']
452
+ elif 'model' in state_dict:
453
+ state_dict = state_dict['model']
454
+
455
+ model_dict = self.state_dict()
456
+
457
+ # Filter out keys that are not in the current model, have different shapes, or belong to the classification head
458
+ pretrained_dict = {
459
+ k: v for k, v in state_dict.items()
460
+ if k in model_dict and v.shape == model_dict[k].shape and 'head' not in k
461
+ }
462
+
463
+ model_dict.update(pretrained_dict)
464
+ self.load_state_dict(model_dict, strict=False)
465
+ logging.info(f"Loaded {len(pretrained_dict)} keys from pretrained weights.")
466
+
467
+ def train(self, mode=True):
468
+ super().train(mode)
469
+ # For detection, keep BatchNorm in eval mode
470
+ if self.features_only:
471
+ for m in self.modules():
472
+ if isinstance(m, nn.BatchNorm2d):
473
+ m.eval()
474
+
475
+ def forward(self, x):
476
+ outs = {}
477
+ x = self.stem(x)
478
+
479
+ for i, stage in enumerate(self.grid_stages):
480
+ x = stage(x)
481
+ if self.features_only and self.out_indices and i in self.out_indices:
482
+ outs[str(i)] = x
483
+
484
+ # Graph processing
485
+ B, C, H, W = x.shape
486
+ x_nodes, edge_index = img_to_graph(x)
487
+ x_nodes, edge_index = self.to_graph_pool(x_nodes, edge_index)
488
+ x_nodes = self.graph_proj(x_nodes)
489
+
490
+ for block in self.graph_stages:
491
+ x_nodes = block(x_nodes, edge_index)
492
+
493
+ if self.features_only:
494
+ graph_stage_idx = len(self.grid_stages)
495
+ if self.out_indices and graph_stage_idx in self.out_indices:
496
+ num_nodes = x_nodes.shape[1]
497
+ new_h = int(num_nodes**0.5)
498
+ new_w = int(num_nodes**0.5)
499
+ if new_h * new_w != num_nodes:
500
+ new_h = new_w = int(num_nodes**0.5)
501
+ x_nodes = x_nodes[:, :new_h*new_w, :]
502
+
503
+ x_graph = x_nodes.transpose(1, 2).view(B, self.graph_dim, new_h, new_w)
504
+ outs[str(graph_stage_idx)] = x_graph
505
+
506
+ return outs
507
+
508
+ # Global readout (mean) for classification
509
+ x_global = x_nodes.mean(dim=1)
510
+ x_out = self.norm(x_global)
511
+
512
+ if self.distillation:
513
+ x_main = self.head(x_out)
514
+ x_dist = self.head_dist(x_out)
515
+ if self.training:
516
+ return x_main, x_dist
517
+ else:
518
+ return (x_main + x_dist) / 2
519
+ else:
520
+ return self.head(x_out)
521
+
522
+
523
+ # Register a small GeoViG variant for timm-style usage
524
+ @register_model
525
+ def geovig_ti(pretrained=False, **kwargs):
526
+ model = GeoViG(local_blocks=[3, 3, 3], local_channels=[42, 84, 168],
527
+ graph_blocks=[3], graph_channels=[256],
528
+ pool_ratio=0.5, use_concat=True,
529
+ pretrained=pretrained, **kwargs)
530
+ model.default_cfg = default_cfgs['mobilevig']
531
+ return model
532
+
533
+ @register_model
534
+ def geovig_s(pretrained=False, **kwargs):
535
+ model = GeoViG(local_blocks=[3, 3, 4], local_channels=[56, 112, 224],
536
+ graph_blocks=[3], graph_channels=[288],
537
+ pool_ratio=0.5, use_concat=False,
538
+ pretrained=pretrained, **kwargs)
539
+ model.default_cfg = default_cfgs['mobilevig']
540
+ return model
541
+
542
+ @register_model
543
+ def geovig_m(pretrained=False, **kwargs):
544
+ # Target: ~10M Params (MobileViG-M: 14M)
545
+ model = GeoViG(local_blocks=[3, 3, 9], local_channels=[64, 128, 256],
546
+ graph_blocks=[3], graph_channels=[384],
547
+ pool_ratio=0.5,
548
+ pretrained=pretrained, **kwargs)
549
+ model.default_cfg = default_cfgs['mobilevig']
550
+ return model
551
+
552
+ @register_model
553
+ def geovig_b(pretrained=False, **kwargs):
554
+ # Target: ~19M Params (MobileViG-B: 26.7M)
555
+ model = GeoViG(local_blocks=[5, 5, 9], local_channels=[80, 160, 320],
556
+ graph_blocks=[4], graph_channels=[512],
557
+ pool_ratio=0.5,
558
+ pretrained=pretrained, **kwargs)
559
+ model.default_cfg = default_cfgs['mobilevig']
560
+ return model
geovig_backbone.py ADDED
@@ -0,0 +1,667 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ from torch.nn import Sequential as Seq
6
+ import logging
7
+ import copy
8
+
9
+ from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
10
+ from timm.models.layers import DropPath
11
+ from timm.models.registry import register_model
12
+
13
+ try:
14
+ from mmdet.models.builder import BACKBONES as det_BACKBONES
15
+ from mmdet.utils import get_root_logger
16
+ from mmcv.runner import _load_checkpoint
17
+ has_mmdet = True
18
+ except ImportError:
19
+ print("If for detection, please install mmdetection first")
20
+ has_mmdet = False
21
+
22
+ def _cfg(url='', **kwargs):
23
+ return {
24
+ 'url': url,
25
+ 'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': None,
26
+ 'crop_pct': .9, 'interpolation': 'bicubic',
27
+ 'mean': IMAGENET_DEFAULT_MEAN, 'std': IMAGENET_DEFAULT_STD,
28
+ 'classifier': 'head',
29
+ **kwargs
30
+ }
31
+
32
+ default_cfgs = {
33
+ 'mobilevig': _cfg(crop_pct=0.9, mean=IMAGENET_DEFAULT_MEAN, std=IMAGENET_DEFAULT_STD)
34
+ }
35
+
36
+ import logging
37
+
38
+ logging.basicConfig(
39
+ level=logging.WARNING,
40
+ format="%(asctime)s | %(levelname)s | %(name)s | %(message)s"
41
+ )
42
+
43
+ logger = logging.getLogger(__name__)
44
+
45
+ logger = logging.getLogger(__name__)
46
+
47
+ class Stem(nn.Module):
48
+ def __init__(self, input_dim, output_dim, activation=nn.GELU):
49
+ super(Stem, self).__init__()
50
+ self.stem = nn.Sequential(
51
+ nn.Conv2d(input_dim, output_dim // 2, kernel_size=3, stride=2, padding=1),
52
+ nn.BatchNorm2d(output_dim // 2),
53
+ nn.GELU(),
54
+ nn.Conv2d(output_dim // 2, output_dim, kernel_size=3, stride=2, padding=1),
55
+ nn.BatchNorm2d(output_dim),
56
+ nn.GELU()
57
+ )
58
+
59
+ def forward(self, x):
60
+ return self.stem(x)
61
+
62
+ class GraphAdapter(nn.Module):
63
+ """
64
+ Graph-Aware Adapter: Lightweight 3x3 DW Conv + GroupNorm + 1x1 Conv + GroupNorm.
65
+ Aligns graph features with grid-based detection heads.
66
+ """
67
+ def __init__(self, channels):
68
+ super().__init__()
69
+ self.adapter = nn.Sequential(
70
+ # Depthwise 3x3
71
+ nn.Conv2d(channels, channels, kernel_size=3, padding=1, groups=channels, bias=False),
72
+ # GroupNorm (num_groups=1 is equivalent to LayerNorm but works with 4D tensors)
73
+ nn.GroupNorm(1, channels),
74
+ # Pointwise 1x1
75
+ nn.Conv2d(channels, channels, kernel_size=1, bias=False),
76
+ nn.GroupNorm(32, channels), # GroupNorm for stability
77
+ nn.GELU()
78
+ )
79
+
80
+ def forward(self, x):
81
+ return self.adapter(x)
82
+
83
+
84
+
85
+ class MLP(nn.Module):
86
+ """
87
+ Implementation of MLP with 1*1 convolutions.
88
+ Input: tensor with shape [B, C, H, W]
89
+ """
90
+
91
+ def __init__(self, in_features, hidden_features=None,
92
+ out_features=None, drop=0., mid_conv=False):
93
+ super().__init__()
94
+ out_features = out_features or in_features
95
+ hidden_features = hidden_features or in_features
96
+ self.mid_conv = mid_conv
97
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
98
+ self.act = nn.GELU()
99
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
100
+ self.drop = nn.Dropout(drop)
101
+
102
+ if self.mid_conv:
103
+ self.mid = nn.Conv2d(hidden_features, hidden_features, kernel_size=3, stride=1, padding=1,
104
+ groups=hidden_features)
105
+ self.mid_norm = nn.BatchNorm2d(hidden_features)
106
+
107
+ self.norm1 = nn.BatchNorm2d(hidden_features)
108
+ self.norm2 = nn.BatchNorm2d(out_features)
109
+
110
+ def forward(self, x):
111
+ x = self.fc1(x)
112
+ x = self.norm1(x)
113
+ x = self.act(x)
114
+
115
+ if self.mid_conv:
116
+ x_mid = self.mid(x)
117
+ x_mid = self.mid_norm(x_mid)
118
+ x = self.act(x_mid)
119
+ x = self.drop(x)
120
+
121
+ x = self.fc2(x)
122
+ x = self.norm2(x)
123
+
124
+ x = self.drop(x)
125
+ return x
126
+
127
+
128
+ class InvertedResidual(nn.Module):
129
+ def __init__(self, dim, mlp_ratio=4., drop=0., drop_path=0., use_layer_scale=True, layer_scale_init_value=1e-5):
130
+ super().__init__()
131
+
132
+ mlp_hidden_dim = int(dim * mlp_ratio)
133
+ self.mlp = MLP(in_features=dim, hidden_features=mlp_hidden_dim, drop=drop, mid_conv=True)
134
+
135
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
136
+ else nn.Identity()
137
+ self.use_layer_scale = use_layer_scale
138
+ if use_layer_scale:
139
+ self.layer_scale_2 = nn.Parameter(
140
+ layer_scale_init_value * torch.ones(dim).unsqueeze(-1).unsqueeze(-1), requires_grad=True)
141
+
142
+ def forward(self, x):
143
+ if self.use_layer_scale:
144
+ x = x + self.drop_path(self.layer_scale_2 * self.mlp(x))
145
+ else:
146
+ x = x + self.drop_path(self.mlp(x))
147
+ return x
148
+
149
+
150
+ def img_to_graph(x):
151
+ """
152
+ Converts (B, C, H, W) -> (B, N, C) and generates a grid adjacency (8-neighbor-ish).
153
+ For efficiency this returns a single edge_index shared for the batch.
154
+ """
155
+ B, C, H, W = x.shape
156
+ device = x.device
157
+ N = H * W
158
+
159
+ # Flatten features: (B, C, H, W) -> (B, N, C)
160
+ x_flat = x.flatten(2).transpose(1, 2) # (B, N, C)
161
+
162
+ # Generate grid edges (simplified 8-neighbor connectivity)
163
+ idx = torch.arange(N, device=device).view(H, W)
164
+ edges = []
165
+
166
+ # Right neighbors
167
+ if W > 1:
168
+ edges.append(torch.stack([idx[:, :-1].flatten(), idx[:, 1:].flatten()], dim=0))
169
+ # Left (will be mirrored later)
170
+ # Down neighbors
171
+ if H > 1:
172
+ edges.append(torch.stack([idx[:-1, :].flatten(), idx[1:, :].flatten()], dim=0))
173
+ # Diagonals
174
+ if H > 1 and W > 1:
175
+ edges.append(torch.stack([idx[:-1, :-1].flatten(), idx[1:, 1:].flatten()], dim=0))
176
+ edges.append(torch.stack([idx[1:, :-1].flatten(), idx[:-1, 1:].flatten()], dim=0))
177
+
178
+ if len(edges) == 0:
179
+ edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
180
+ else:
181
+ edge_index = torch.cat(edges, dim=1)
182
+ # Make undirected by mirroring
183
+ edge_index = torch.cat([edge_index, edge_index.flip(0)], dim=1)
184
+
185
+ return x_flat, edge_index
186
+
187
+
188
+ class GraphMRConv(nn.Module):
189
+ """
190
+ Graph version of Max-Relative Graph Convolution.
191
+ Operates on (B, N, C) and edge_index (2, E).
192
+ Uses PyTorch's index_reduce_ with 'amax' for true max aggregation.
193
+ """
194
+ def __init__(self, in_channels, out_channels):
195
+ super().__init__()
196
+ self.in_channels = in_channels
197
+ self.out_channels = out_channels
198
+ # OPTIMIZATION: Removed concatenation (2*C). Now just Linear(C -> C).
199
+ # This reduces parameters significantly and speeds up the projection.
200
+ self.nn = nn.Sequential(
201
+ nn.Linear(in_channels, out_channels),
202
+ nn.GELU()
203
+ )
204
+
205
+ def forward(self, x, edge_index):
206
+ # x: (B, N, C)
207
+ B, N, C = x.shape
208
+ device = x.device
209
+
210
+ # Reshape to (B*N, C) for batched processing
211
+ x_flat = x.view(-1, C)
212
+
213
+ if edge_index.numel() == 0:
214
+ # No edges: fallback to identity-like behavior with zeros
215
+ # Return processed x_flat reshaped, or zeros if safer
216
+ # Ideally graph conv with no edges is just local projection of 0-vector (since it's rel diff)
217
+ # max(x_j) would be empty -> -inf -> replaced by 0
218
+ # then -x_i
219
+ # so effectively -x_i passed through nn
220
+ return self.nn(-x).view(B, N, -1)
221
+
222
+ row, col = edge_index # (E,), (E,)
223
+ num_edges = row.size(0)
224
+
225
+ # Create batched edge index: (2, B*E)
226
+ batch_offsets = torch.arange(B, device=device) * N
227
+ edge_offsets = batch_offsets.view(-1, 1).repeat(1, num_edges).view(-1)
228
+
229
+ row_batch = row.repeat(B) + edge_offsets
230
+ col_batch = col.repeat(B) + edge_offsets
231
+
232
+ # Gather features
233
+ x_flat_c = x_flat.contiguous()
234
+ # Optimization: Don't compute x_j - x_i explicitly (huge tensor).
235
+ # max(x_j - x_i) = max(x_j) - x_i
236
+ x_j = x_flat_c[col_batch] # (B*E, C)
237
+
238
+ # Initialize aggregation buffer
239
+ aggr = torch.full_like(x_flat, -1e9) # (B*N, C)
240
+
241
+ # Vectorized Max Aggregation of Neighbors
242
+ index_expanded = row_batch.view(-1, 1).expand(-1, C)
243
+ aggr.scatter_reduce_(0, index_expanded, x_j, reduce='amax', include_self=True)
244
+
245
+ # Replace -1e9 with 0
246
+ aggr = torch.where(aggr == -1e9, torch.zeros_like(aggr), aggr)
247
+
248
+ # Now apply the "- x_i" part of "max(x_j) - x_i"
249
+ aggr = aggr - x_flat
250
+
251
+ # Output only the aggregated update
252
+ return self.nn(aggr).view(B, N, -1)
253
+
254
+
255
+ class GraphMobileViGBlock(nn.Module):
256
+ """
257
+ Graph MobileViG block: GraphMRConv + FFN for (B,N,C) representations.
258
+ """
259
+ def __init__(self, dim, drop=0.0):
260
+ super().__init__()
261
+ self.norm1 = nn.LayerNorm(dim)
262
+ self.graph_conv = GraphMRConv(dim, dim)
263
+ self.norm2 = nn.LayerNorm(dim)
264
+ self.ffn = nn.Sequential(
265
+ nn.Linear(dim, dim * 4),
266
+ nn.GELU(),
267
+ nn.Linear(dim * 4, dim),
268
+ nn.Dropout(drop)
269
+ )
270
+
271
+ def forward(self, x, edge_index):
272
+ # x: (B, N, C)
273
+ shortcut = x
274
+ x = self.norm1(x)
275
+ x = self.graph_conv(x, edge_index)
276
+ x = x + shortcut
277
+
278
+ shortcut = x
279
+ x = self.norm2(x)
280
+ x = self.ffn(x)
281
+ x = x + shortcut
282
+ return x
283
+
284
+
285
+ class SpreadEdgePool(nn.Module):
286
+ """
287
+ Geometry-Aware SpreadEdgePool
288
+ """
289
+ def __init__(self, in_channels, ratio=0.5):
290
+ super().__init__()
291
+ self.ratio = ratio
292
+ self.in_channels = in_channels
293
+
294
+ def forward(self, x, edge_index, shape=None):
295
+ B, N, C = x.shape
296
+ device = x.device
297
+
298
+ # 2D Scaling Path (For Detection) - STABLE MODE
299
+ # Skips distance-based weighting to avoid instability in detection fine-tuning
300
+ if shape is not None:
301
+ H, W = shape
302
+ if H * W == N:
303
+ x_2d = x.transpose(1, 2).view(B, C, H, W)
304
+
305
+ # Force Stride 2 for Detection (Target Stride 32 from Stride 16 input)
306
+ # ratio=0.25 implies 1/4 area -> stride 2
307
+ stride = 2 if self.ratio <= 0.25 else 1
308
+
309
+ x_pooled_2d = F.avg_pool2d(x_2d, kernel_size=stride, stride=stride)
310
+
311
+ # Regenerate Grid Graph
312
+ x_pooled, new_edge_index = img_to_graph(x_pooled_2d)
313
+ return x_pooled, new_edge_index
314
+
315
+ # Default 1D Graph Pooling (original classification logic)
316
+ num_keep = max(1, int(N * self.ratio))
317
+
318
+ # Only compute weights if strictly needed (1D path)
319
+ if edge_index.numel() > 0:
320
+ row, col = edge_index
321
+ x_sq = (x ** 2).sum(dim=-1, keepdim=True)
322
+ sq_i = x_sq[:, row, :]
323
+ sq_j = x_sq[:, col, :]
324
+ x_row = x[:, row, :]
325
+ x_col = x[:, col, :]
326
+ dot = (x_row * x_col).sum(dim=-1, keepdim=True)
327
+ dist_sq = sq_i + sq_j - 2 * dot
328
+ dist = torch.sqrt(F.relu(dist_sq) + 1e-6).squeeze(-1)
329
+ avg_edge_scores = dist.mean(dim=0)
330
+ node_importance = torch.zeros(N, device=device)
331
+ node_importance.index_add_(0, row, avg_edge_scores)
332
+ node_weights = torch.sigmoid(node_importance).view(1, N, 1)
333
+ else:
334
+ node_weights = torch.ones((1, N, 1), device=device)
335
+
336
+ x_weighted = x * node_weights
337
+ num_keep = max(1, int(N * self.ratio))
338
+
339
+ x_pooled = F.adaptive_avg_pool1d(x_weighted.transpose(1, 2), num_keep).transpose(1, 2)
340
+
341
+ if edge_index.numel() == 0:
342
+ idx = torch.arange(num_keep, device=device)
343
+ new_edge_index = torch.stack([idx[:-1], idx[1:]], dim=0) if num_keep > 1 else torch.zeros((2, 0), dtype=torch.long, device=device)
344
+ return x_pooled, new_edge_index
345
+
346
+ idx = torch.arange(num_keep, device=device)
347
+ if num_keep > 1:
348
+ left = idx[:-1]
349
+ right = idx[1:]
350
+ new_edge_index = torch.cat([torch.stack([left, right], dim=0), torch.stack([right, left], dim=0)], dim=1)
351
+ else:
352
+ new_edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
353
+
354
+ return x_pooled, new_edge_index
355
+
356
+
357
+ class Downsample(nn.Module):
358
+ """
359
+ Convolution-based downsample
360
+ """
361
+ def __init__(self, in_dim, out_dim):
362
+ super().__init__()
363
+ self.conv = nn.Sequential(
364
+ nn.Conv2d(in_dim, out_dim, 3, stride=2, padding=1),
365
+ nn.BatchNorm2d(out_dim),
366
+ )
367
+
368
+ def forward(self, x):
369
+ x = self.conv(x)
370
+ return x
371
+
372
+ class GeoViG(nn.Module):
373
+ """
374
+ Hybrid Grid->Graph GeoViG model. Can be used for classification or as a backbone for detection.
375
+ """
376
+ def __init__(self,
377
+ local_blocks=[2, 2],
378
+ local_channels=[42, 84],
379
+ graph_blocks=[2],
380
+ graph_channels=[168],
381
+ num_classes=1000,
382
+ pool_ratio=0.5,
383
+ distillation=False,
384
+ features_only=False,
385
+ out_indices=None,
386
+ pretrained=None,
387
+ use_detect_adapter=False,
388
+ **kwargs):
389
+ super().__init__()
390
+
391
+ self.features_only = features_only
392
+ self.pool_ratio = pool_ratio
393
+ self.out_indices = out_indices
394
+ self.distillation = distillation
395
+ self.pretrained = pretrained
396
+ self.use_detect_adapter = use_detect_adapter
397
+
398
+ # Stage 0: stem
399
+ self.stem = Stem(input_dim=3, output_dim=local_channels[0])
400
+
401
+ # Grid stages
402
+ self.grid_stages = nn.ModuleList()
403
+ in_c = local_channels[0]
404
+
405
+ self.feature_info = [dict(num_chs=in_c, reduction=2, module=f'stem')]
406
+
407
+ for i, out_c in enumerate(local_channels):
408
+ layers = []
409
+ if i > 0:
410
+ layers.append(Downsample(in_c, out_c))
411
+ in_c = out_c
412
+ for _ in range(local_blocks[i]):
413
+ layers.append(InvertedResidual(dim=out_c, mlp_ratio=4, drop_path=0.0))
414
+ self.grid_stages.append(nn.Sequential(*layers))
415
+ self.feature_info.append(dict(num_chs=out_c, reduction=2**(i+2), module=f'grid_stages.{i}'))
416
+
417
+ # Transition: grid -> graph
418
+ self.to_graph_pool = SpreadEdgePool(in_channels=in_c, ratio=pool_ratio)
419
+ self.graph_dim = in_c
420
+
421
+ # Graph stages
422
+ self.graph_proj = nn.Linear(self.graph_dim, graph_channels[0]) if graph_channels else nn.Identity()
423
+ self.graph_dim = graph_channels[0] if graph_channels else self.graph_dim
424
+
425
+ self.graph_stages = nn.ModuleList()
426
+ for _ in range(graph_blocks[0] if len(graph_blocks) > 0 else 0):
427
+ self.graph_stages.append(GraphMobileViGBlock(dim=self.graph_dim))
428
+
429
+ self.feature_info.append(dict(num_chs=self.graph_dim, reduction=2**(len(local_channels)+1), module='graph_stages'))
430
+
431
+ if not self.features_only:
432
+ # Head for classification
433
+ self.norm = nn.LayerNorm(self.graph_dim)
434
+ self.head = nn.Linear(self.graph_dim, num_classes)
435
+ if self.distillation:
436
+ self.head_dist = nn.Linear(self.graph_dim, num_classes)
437
+ else:
438
+ # For torchvision FPN
439
+ self._out_channels = []
440
+ if self.out_indices:
441
+ for i in self.out_indices:
442
+ # The indices for feature_info are shifted by 1 compared to grid_stages
443
+ # features_info: [stem(idx0), grid0(idx1), grid1(idx2), grid2(idx3), graph(idx4)]
444
+ # grid_stages: [grid0, grid1, grid2] (indices 0, 1, 2)
445
+ pass
446
+
447
+ # Detection Adapter Initialization
448
+ if self.use_detect_adapter and self.features_only:
449
+ # Determine channels automatically
450
+ # Stem: local_channels[0] (but usually we tap grid stages)
451
+ # Grid Stages output: local_channels
452
+ # Graph Stage output: graph_channels[0]
453
+
454
+ # Map indices to channels
455
+ # We assume out_indices=[0, 1, 2, 3] corresponds to [grid0, grid1, grid2, graph]
456
+ # Verify this mapping convention from forward loop
457
+ self.detect_adapters = nn.ModuleList()
458
+
459
+ # This is a bit dynamic because out_indices can vary, but assuming standard GeoViG-M/B
460
+ # and standard out_indices=[0, 1, 2, 3]
461
+
462
+ current_channels = []
463
+ # Indices refer to stages
464
+ # 0 -> grid_stages[0] (local_channels[0])
465
+ # 1 -> grid_stages[1] (local_channels[1])
466
+ # 2 -> grid_stages[2] (local_channels[2])
467
+ # 3 -> graph_stage (graph_channels[0]) (if present)
468
+
469
+ all_channels = local_channels[:]
470
+ if graph_channels:
471
+ all_channels.append(graph_channels[0])
472
+
473
+ for idx in (self.out_indices or []):
474
+ if idx < len(all_channels):
475
+ self.detect_adapters.append(GraphAdapter(all_channels[idx]))
476
+ else:
477
+ # Fallback or error
478
+ pass
479
+
480
+ self.init_weights()
481
+ if pretrained:
482
+ self._load_pretrained_weights()
483
+
484
+ # self = torch.nn.SyncBatchNorm.convert_sync_batchnorm(self)
485
+
486
+ def init_weights(self):
487
+ if self.pretrained and isinstance(self.pretrained, str):
488
+ logger = get_root_logger()
489
+ print(f"Pretrained weights being being loaded from {self.pretrained}")
490
+ logger.warn(f'Pretrained weights being loaded from {self.pretrained}')
491
+ self._load_pretrained_weights()
492
+ else:
493
+ # Init random weights if needed or rely on default init
494
+ pass
495
+
496
+ def _load_pretrained_weights(self):
497
+ """Load pretrained weights from checkpoint."""
498
+ if self.pretrained and isinstance(self.pretrained, str):
499
+ logging.info(f"Loading pretrained weights from {self.pretrained}")
500
+ try:
501
+ # Use mmcv loader if available
502
+ ckpt = _load_checkpoint(self.pretrained, logger=logging.getLogger(), map_location='cpu')
503
+ except:
504
+ ckpt = torch.load(self.pretrained, map_location='cpu')
505
+
506
+ print("ckpt keys: ", ckpt.keys())
507
+ if 'state_dict_ema' in ckpt:
508
+ state_dict = ckpt['state_dict_ema']
509
+ elif 'state_dict' in ckpt:
510
+ state_dict = ckpt['state_dict']
511
+ elif 'model' in ckpt:
512
+ state_dict = ckpt['model']
513
+ else:
514
+ state_dict = ckpt
515
+
516
+ model_dict = self.state_dict()
517
+
518
+ # Simple permissive loading
519
+ pretrained_dict = {}
520
+ for k, v in state_dict.items():
521
+ if k in model_dict:
522
+ if v.shape == model_dict[k].shape:
523
+ # Exclude classification head logic
524
+ if 'head' not in k:
525
+ pretrained_dict[k] = v
526
+ else:
527
+ print(f"Shape mismatch for {k}: {v.shape} vs {model_dict[k].shape}")
528
+
529
+ missing, unexpected = self.load_state_dict(pretrained_dict, strict=False)
530
+ print("missing_keys: ", missing)
531
+ print("unexpected_keys: ", unexpected)
532
+
533
+ @torch.no_grad()
534
+ def train(self, mode=True):
535
+ super().train(mode)
536
+ # For detection, keep BatchNorm in eval mode
537
+ for m in self.modules():
538
+ if isinstance(m, nn.BatchNorm2d):
539
+ m.eval()
540
+
541
+ def forward(self, x):
542
+ outs = []
543
+ x = self.stem(x)
544
+
545
+ for i, stage in enumerate(self.grid_stages):
546
+ x = stage(x)
547
+ if self.features_only and self.out_indices and i in self.out_indices:
548
+ outs.append(x)
549
+
550
+ # Graph processing
551
+ B, C, H, W = x.shape
552
+ x_nodes, edge_index = img_to_graph(x)
553
+ # We need to know the stride used by SpreadEdgePool to reconstruct the shape later
554
+ # Logic matches SpreadEdgePool: stride 2 if ratio <= 0.25 else 1
555
+ pool_stride = 2 if self.pool_ratio <= 0.25 else 1
556
+
557
+ x_nodes, edge_index = self.to_graph_pool(x_nodes, edge_index, shape=(H, W))
558
+ x_nodes = self.graph_proj(x_nodes)
559
+
560
+ for block in self.graph_stages:
561
+ x_nodes = block(x_nodes, edge_index)
562
+
563
+ if self.features_only:
564
+ graph_stage_idx = len(self.grid_stages)
565
+ if self.out_indices and graph_stage_idx in self.out_indices:
566
+ # Calculate correct new dimensions
567
+ new_h = H // pool_stride
568
+ new_w = W // pool_stride
569
+
570
+ # Verify shape matches
571
+ if new_h * new_w != x_nodes.shape[1]:
572
+ logger.warning(
573
+ "Shape mismatch in Graph Stage reshape. Expected %dx%d=%d, got %d",
574
+ new_h, new_w, new_h * new_w, x_nodes.shape[1]
575
+ )
576
+ # Fallback to square if totally broken (should not happen with stable pooling)
577
+ num_nodes = x_nodes.shape[1]
578
+ new_h = int(num_nodes**0.5)
579
+ new_w = int(num_nodes**0.5)
580
+ if new_h * new_w != num_nodes:
581
+ x_nodes = x_nodes[:, :new_h*new_w, :]
582
+
583
+ x_graph = x_nodes.transpose(1, 2).view(B, self.graph_dim, new_h, new_w)
584
+ outs.append(x_graph)
585
+
586
+ # Apply Adapters and Clamping
587
+ final_outs = []
588
+ for i, feat in enumerate(outs):
589
+ # Stability Fix: Clamp
590
+ feat = torch.clamp(feat, min=-10.0, max=10.0)
591
+
592
+ # Adaptation: Apply GraphAdapter if enabled
593
+ if self.use_detect_adapter and hasattr(self, 'detect_adapters') and i < len(self.detect_adapters):
594
+ feat = self.detect_adapters[i](feat)
595
+
596
+ final_outs.append(feat)
597
+
598
+ return tuple(final_outs)
599
+
600
+ # Global readout (mean) for classification
601
+ x_global = x_nodes.mean(dim=1)
602
+ x_out = self.norm(x_global)
603
+
604
+ if self.distillation:
605
+ x_main = self.head(x_out)
606
+ x_dist = self.head_dist(x_out)
607
+ if self.training:
608
+ return x_main, x_dist
609
+ else:
610
+ return (x_main + x_dist) / 2
611
+ else:
612
+ return self.head(x_out)
613
+
614
+
615
+ if has_mmdet:
616
+ @det_BACKBONES.register_module()
617
+ def geovig_m_feat(pretrained=False, **kwargs):
618
+ if pretrained == 'torchvision://resnet50':
619
+ pretrained = True
620
+
621
+ # Absolute path relative to this file — works from any CWD
622
+ _here = os.path.dirname(os.path.abspath(__file__))
623
+ real_pretrained = os.path.normpath(
624
+ os.path.join(_here, '../final_model_ipa_pth/pth/geovig_m_5e4_8G_300_80_70/checkpoint.pth')
625
+ )
626
+ # Skip backbone pre-loading if path missing; init_detector loads the full ckpt anyway
627
+ if pretrained is True:
628
+ load_path = real_pretrained if os.path.exists(real_pretrained) else None
629
+ elif isinstance(pretrained, str) and os.path.exists(pretrained):
630
+ load_path = pretrained
631
+ else:
632
+ load_path = None
633
+
634
+ model = GeoViG(local_blocks=[3, 3, 9], local_channels=[64, 128, 256],
635
+ graph_blocks=[3], graph_channels=[384],
636
+ pool_ratio=0.25,
637
+ features_only=True,
638
+ out_indices=[0, 1, 2, 3],
639
+ pretrained=load_path)
640
+ model.default_cfg = default_cfgs['mobilevig']
641
+ return model
642
+
643
+ @det_BACKBONES.register_module()
644
+ def geovig_b_feat(pretrained=False, **kwargs):
645
+ if pretrained == 'torchvision://resnet50':
646
+ pretrained = True
647
+
648
+ _here = os.path.dirname(os.path.abspath(__file__))
649
+ real_pretrained = os.path.normpath(
650
+ os.path.join(_here, '../final_model_ipa_pth/pth/geovig_b_5e4_8G_300_82_38/checkpoint.pth')
651
+ )
652
+ if pretrained is True:
653
+ load_path = real_pretrained if os.path.exists(real_pretrained) else None
654
+ elif isinstance(pretrained, str) and os.path.exists(pretrained):
655
+ load_path = pretrained
656
+ else:
657
+ load_path = None
658
+
659
+ model = GeoViG(local_blocks=[5, 5, 9], local_channels=[80, 160, 320],
660
+ graph_blocks=[4], graph_channels=[512],
661
+ pool_ratio=0.25,
662
+ features_only=True,
663
+ out_indices=[0, 1, 2, 3],
664
+ pretrained=load_path)
665
+ model.default_cfg = default_cfgs['mobilevig']
666
+ return model
667
+
geovig_det_backbone.py ADDED
@@ -0,0 +1,676 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ from torch.nn import Sequential as Seq
6
+ import logging
7
+ import copy
8
+
9
+ from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
10
+ from timm.models.layers import DropPath
11
+ from timm.models.registry import register_model
12
+
13
+ try:
14
+ from mmdet.models.builder import BACKBONES as det_BACKBONES
15
+ from mmdet.utils import get_root_logger
16
+ from mmcv.runner import _load_checkpoint
17
+ has_mmdet = True
18
+
19
+ # Register dummy medical datasets for configuration compatibility
20
+ from mmdet.datasets.builder import DATASETS
21
+ from mmdet.datasets.custom import CustomDataset
22
+
23
+ @DATASETS.register_module(force=True)
24
+ class KvasirDataset(CustomDataset):
25
+ CLASSES = ('polyp',)
26
+
27
+ @DATASETS.register_module(force=True)
28
+ class DSBDataset(CustomDataset):
29
+ CLASSES = ('nucleus',)
30
+ except ImportError:
31
+ print("If for detection, please install mmdetection first")
32
+ has_mmdet = False
33
+
34
+ def _cfg(url='', **kwargs):
35
+ return {
36
+ 'url': url,
37
+ 'num_classes': 1000, 'input_size': (3, 224, 224), 'pool_size': None,
38
+ 'crop_pct': .9, 'interpolation': 'bicubic',
39
+ 'mean': IMAGENET_DEFAULT_MEAN, 'std': IMAGENET_DEFAULT_STD,
40
+ 'classifier': 'head',
41
+ **kwargs
42
+ }
43
+
44
+ default_cfgs = {
45
+ 'mobilevig': _cfg(crop_pct=0.9, mean=IMAGENET_DEFAULT_MEAN, std=IMAGENET_DEFAULT_STD)
46
+ }
47
+
48
+ import logging
49
+
50
+ logging.basicConfig(
51
+ level=logging.WARNING,
52
+ format="%(asctime)s | %(levelname)s | %(name)s | %(message)s"
53
+ )
54
+
55
+ logger = logging.getLogger(__name__)
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+ class Stem(nn.Module):
60
+ def __init__(self, input_dim, output_dim, activation=nn.GELU):
61
+ super(Stem, self).__init__()
62
+ self.stem = nn.Sequential(
63
+ nn.Conv2d(input_dim, output_dim // 2, kernel_size=3, stride=2, padding=1),
64
+ nn.BatchNorm2d(output_dim // 2),
65
+ nn.GELU(),
66
+ nn.Conv2d(output_dim // 2, output_dim, kernel_size=3, stride=2, padding=1),
67
+ nn.BatchNorm2d(output_dim),
68
+ nn.GELU()
69
+ )
70
+
71
+ def forward(self, x):
72
+ return self.stem(x)
73
+
74
+ class GraphAdapter(nn.Module):
75
+ """
76
+ Graph-Aware Adapter: Lightweight 3x3 DW Conv + GroupNorm + 1x1 Conv + GroupNorm.
77
+ Aligns graph features with grid-based detection heads.
78
+ """
79
+ def __init__(self, channels):
80
+ super().__init__()
81
+ self.adapter = nn.Sequential(
82
+ # Depthwise 3x3
83
+ nn.Conv2d(channels, channels, kernel_size=3, padding=1, groups=channels, bias=False),
84
+ # GroupNorm (num_groups=1 is equivalent to LayerNorm but works with 4D tensors)
85
+ nn.GroupNorm(1, channels),
86
+ # Pointwise 1x1
87
+ nn.Conv2d(channels, channels, kernel_size=1, bias=False),
88
+ nn.GroupNorm(32, channels), # GroupNorm for stability
89
+ nn.GELU()
90
+ )
91
+
92
+ def forward(self, x):
93
+ return self.adapter(x)
94
+
95
+
96
+
97
+ class MLP(nn.Module):
98
+ """
99
+ Implementation of MLP with 1*1 convolutions.
100
+ Input: tensor with shape [B, C, H, W]
101
+ """
102
+
103
+ def __init__(self, in_features, hidden_features=None,
104
+ out_features=None, drop=0., mid_conv=False):
105
+ super().__init__()
106
+ out_features = out_features or in_features
107
+ hidden_features = hidden_features or in_features
108
+ self.mid_conv = mid_conv
109
+ self.fc1 = nn.Conv2d(in_features, hidden_features, 1)
110
+ self.act = nn.GELU()
111
+ self.fc2 = nn.Conv2d(hidden_features, out_features, 1)
112
+ self.drop = nn.Dropout(drop)
113
+
114
+ if self.mid_conv:
115
+ self.mid = nn.Conv2d(hidden_features, hidden_features, kernel_size=3, stride=1, padding=1,
116
+ groups=hidden_features)
117
+ self.mid_norm = nn.BatchNorm2d(hidden_features)
118
+
119
+ self.norm1 = nn.BatchNorm2d(hidden_features)
120
+ self.norm2 = nn.BatchNorm2d(out_features)
121
+
122
+ def forward(self, x):
123
+ x = self.fc1(x)
124
+ x = self.norm1(x)
125
+ x = self.act(x)
126
+
127
+ if self.mid_conv:
128
+ x_mid = self.mid(x)
129
+ x_mid = self.mid_norm(x_mid)
130
+ x = self.act(x_mid)
131
+ x = self.drop(x)
132
+
133
+ x = self.fc2(x)
134
+ x = self.norm2(x)
135
+
136
+ x = self.drop(x)
137
+ return x
138
+
139
+
140
+ class InvertedResidual(nn.Module):
141
+ def __init__(self, dim, mlp_ratio=4., drop=0., drop_path=0., use_layer_scale=True, layer_scale_init_value=1e-5):
142
+ super().__init__()
143
+
144
+ mlp_hidden_dim = int(dim * mlp_ratio)
145
+ self.mlp = MLP(in_features=dim, hidden_features=mlp_hidden_dim, drop=drop, mid_conv=True)
146
+
147
+ self.drop_path = DropPath(drop_path) if drop_path > 0. \
148
+ else nn.Identity()
149
+ self.use_layer_scale = use_layer_scale
150
+ if use_layer_scale:
151
+ self.layer_scale_2 = nn.Parameter(
152
+ layer_scale_init_value * torch.ones(dim).unsqueeze(-1).unsqueeze(-1), requires_grad=True)
153
+
154
+ def forward(self, x):
155
+ if self.use_layer_scale:
156
+ x = x + self.drop_path(self.layer_scale_2 * self.mlp(x))
157
+ else:
158
+ x = x + self.drop_path(self.mlp(x))
159
+ return x
160
+
161
+
162
+ def img_to_graph(x):
163
+ """
164
+ Converts (B, C, H, W) -> (B, N, C) and generates a grid adjacency (8-neighbor-ish).
165
+ For efficiency this returns a single edge_index shared for the batch.
166
+ """
167
+ B, C, H, W = x.shape
168
+ device = x.device
169
+ N = H * W
170
+
171
+ # Flatten features: (B, C, H, W) -> (B, N, C)
172
+ x_flat = x.flatten(2).transpose(1, 2) # (B, N, C)
173
+
174
+ # Generate grid edges (simplified 8-neighbor connectivity)
175
+ idx = torch.arange(N, device=device).view(H, W)
176
+ edges = []
177
+
178
+ # Right neighbors
179
+ if W > 1:
180
+ edges.append(torch.stack([idx[:, :-1].flatten(), idx[:, 1:].flatten()], dim=0))
181
+ # Left (will be mirrored later)
182
+ # Down neighbors
183
+ if H > 1:
184
+ edges.append(torch.stack([idx[:-1, :].flatten(), idx[1:, :].flatten()], dim=0))
185
+ # Diagonals
186
+ if H > 1 and W > 1:
187
+ edges.append(torch.stack([idx[:-1, :-1].flatten(), idx[1:, 1:].flatten()], dim=0))
188
+ edges.append(torch.stack([idx[1:, :-1].flatten(), idx[:-1, 1:].flatten()], dim=0))
189
+
190
+ if len(edges) == 0:
191
+ edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
192
+ else:
193
+ edge_index = torch.cat(edges, dim=1)
194
+ # Make undirected by mirroring
195
+ edge_index = torch.cat([edge_index, edge_index.flip(0)], dim=1)
196
+
197
+ return x_flat, edge_index
198
+
199
+
200
+ class GraphMRConv(nn.Module):
201
+ """
202
+ Graph version of Max-Relative Graph Convolution.
203
+ Operates on (B, N, C) and edge_index (2, E).
204
+ Uses PyTorch's index_reduce_ with 'amax' for true max aggregation.
205
+ """
206
+ def __init__(self, in_channels, out_channels):
207
+ super().__init__()
208
+ self.in_channels = in_channels
209
+ self.out_channels = out_channels
210
+ # OPTIMIZATION: Removed concatenation (2*C). Now just Linear(C -> C).
211
+ # This reduces parameters significantly and speeds up the projection.
212
+ self.nn = nn.Sequential(
213
+ nn.Linear(in_channels, out_channels),
214
+ nn.GELU()
215
+ )
216
+
217
+ def forward(self, x, edge_index):
218
+ # x: (B, N, C)
219
+ B, N, C = x.shape
220
+ device = x.device
221
+
222
+ # Reshape to (B*N, C) for batched processing
223
+ x_flat = x.view(-1, C)
224
+
225
+ if edge_index.numel() == 0:
226
+ # No edges: fallback to identity-like behavior with zeros
227
+ # Return processed x_flat reshaped, or zeros if safer
228
+ # Ideally graph conv with no edges is just local projection of 0-vector (since it's rel diff)
229
+ # max(x_j) would be empty -> -inf -> replaced by 0
230
+ # then -x_i
231
+ # so effectively -x_i passed through nn
232
+ return self.nn(-x).view(B, N, -1)
233
+
234
+ row, col = edge_index # (E,), (E,)
235
+ num_edges = row.size(0)
236
+
237
+ # Create batched edge index: (2, B*E)
238
+ batch_offsets = torch.arange(B, device=device) * N
239
+ edge_offsets = batch_offsets.view(-1, 1).repeat(1, num_edges).view(-1)
240
+
241
+ row_batch = row.repeat(B) + edge_offsets
242
+ col_batch = col.repeat(B) + edge_offsets
243
+
244
+ # Gather features
245
+ x_flat_c = x_flat.contiguous()
246
+ # Optimization: Don't compute x_j - x_i explicitly (huge tensor).
247
+ # max(x_j - x_i) = max(x_j) - x_i
248
+ x_j = x_flat_c[col_batch] # (B*E, C)
249
+
250
+ # Initialize aggregation buffer
251
+ aggr = torch.full_like(x_flat, -1e9) # (B*N, C)
252
+
253
+ # Vectorized Max Aggregation of Neighbors
254
+ index_expanded = row_batch.view(-1, 1).expand(-1, C)
255
+ aggr.scatter_reduce_(0, index_expanded, x_j, reduce='amax', include_self=True)
256
+
257
+ # Replace -1e9 with 0
258
+ aggr = torch.where(aggr == -1e9, torch.zeros_like(aggr), aggr)
259
+
260
+ # Now apply the "- x_i" part of "max(x_j) - x_i"
261
+ aggr = aggr - x_flat
262
+
263
+ # Output only the aggregated update
264
+ return self.nn(aggr).view(B, N, -1)
265
+
266
+
267
+ class GraphMobileViGBlock(nn.Module):
268
+ """
269
+ Graph MobileViG block: GraphMRConv + FFN for (B,N,C) representations.
270
+ """
271
+ def __init__(self, dim, drop=0.0):
272
+ super().__init__()
273
+ self.norm1 = nn.LayerNorm(dim)
274
+ self.graph_conv = GraphMRConv(dim, dim)
275
+ self.norm2 = nn.LayerNorm(dim)
276
+ self.ffn = nn.Sequential(
277
+ nn.Linear(dim, dim * 4),
278
+ nn.GELU(),
279
+ nn.Linear(dim * 4, dim),
280
+ nn.Dropout(drop)
281
+ )
282
+
283
+ def forward(self, x, edge_index):
284
+ # x: (B, N, C)
285
+ shortcut = x
286
+ x = self.norm1(x)
287
+ x = self.graph_conv(x, edge_index)
288
+ x = x + shortcut
289
+
290
+ shortcut = x
291
+ x = self.norm2(x)
292
+ x = self.ffn(x)
293
+ x = x + shortcut
294
+ return x
295
+
296
+
297
+ class SpreadEdgePool(nn.Module):
298
+ """
299
+ Geometry-Aware SpreadEdgePool
300
+ """
301
+ def __init__(self, in_channels, ratio=0.5):
302
+ super().__init__()
303
+ self.ratio = ratio
304
+ self.in_channels = in_channels
305
+
306
+ def forward(self, x, edge_index, shape=None):
307
+ B, N, C = x.shape
308
+ device = x.device
309
+
310
+ # 2D Scaling Path (For Detection) - STABLE MODE
311
+ # Skips distance-based weighting to avoid instability in detection fine-tuning
312
+ if shape is not None:
313
+ H, W = shape
314
+ if H * W == N:
315
+ x_2d = x.transpose(1, 2).view(B, C, H, W)
316
+
317
+ # Force Stride 2 for Detection (Target Stride 32 from Stride 16 input)
318
+ # ratio=0.25 implies 1/4 area -> stride 2
319
+ stride = 2 if self.ratio <= 0.25 else 1
320
+
321
+ x_pooled_2d = F.avg_pool2d(x_2d, kernel_size=stride, stride=stride)
322
+
323
+ # Regenerate Grid Graph
324
+ x_pooled, new_edge_index = img_to_graph(x_pooled_2d)
325
+ return x_pooled, new_edge_index
326
+
327
+ # Default 1D Graph Pooling (original classification logic)
328
+ num_keep = max(1, int(N * self.ratio))
329
+
330
+ # Only compute weights if strictly needed (1D path)
331
+ if edge_index.numel() > 0:
332
+ row, col = edge_index
333
+ x_sq = (x ** 2).sum(dim=-1, keepdim=True)
334
+ sq_i = x_sq[:, row, :]
335
+ sq_j = x_sq[:, col, :]
336
+ x_row = x[:, row, :]
337
+ x_col = x[:, col, :]
338
+ dot = (x_row * x_col).sum(dim=-1, keepdim=True)
339
+ dist_sq = sq_i + sq_j - 2 * dot
340
+ dist = torch.sqrt(F.relu(dist_sq) + 1e-6).squeeze(-1)
341
+ avg_edge_scores = dist.mean(dim=0)
342
+ node_importance = torch.zeros(N, device=device)
343
+ node_importance.index_add_(0, row, avg_edge_scores)
344
+ node_weights = torch.sigmoid(node_importance).view(1, N, 1)
345
+ else:
346
+ node_weights = torch.ones((1, N, 1), device=device)
347
+
348
+ x_weighted = x * node_weights
349
+ num_keep = max(1, int(N * self.ratio))
350
+
351
+ x_pooled = F.adaptive_avg_pool1d(x_weighted.transpose(1, 2), num_keep).transpose(1, 2)
352
+
353
+ if edge_index.numel() == 0:
354
+ idx = torch.arange(num_keep, device=device)
355
+ new_edge_index = torch.stack([idx[:-1], idx[1:]], dim=0) if num_keep > 1 else torch.zeros((2, 0), dtype=torch.long, device=device)
356
+ return x_pooled, new_edge_index
357
+
358
+ idx = torch.arange(num_keep, device=device)
359
+ if num_keep > 1:
360
+ left = idx[:-1]
361
+ right = idx[1:]
362
+ new_edge_index = torch.cat([torch.stack([left, right], dim=0), torch.stack([right, left], dim=0)], dim=1)
363
+ else:
364
+ new_edge_index = torch.zeros((2, 0), dtype=torch.long, device=device)
365
+
366
+ return x_pooled, new_edge_index
367
+
368
+
369
+ class Downsample(nn.Module):
370
+ """
371
+ Convolution-based downsample
372
+ """
373
+ def __init__(self, in_dim, out_dim):
374
+ super().__init__()
375
+ self.conv = nn.Sequential(
376
+ nn.Conv2d(in_dim, out_dim, 3, stride=2, padding=1),
377
+ nn.BatchNorm2d(out_dim),
378
+ )
379
+
380
+ def forward(self, x):
381
+ x = self.conv(x)
382
+ return x
383
+
384
+ class GeoViG(nn.Module):
385
+ """
386
+ Hybrid Grid->Graph GeoViG model. Can be used for classification or as a backbone for detection.
387
+ """
388
+ def __init__(self,
389
+ local_blocks=[2, 2],
390
+ local_channels=[42, 84],
391
+ graph_blocks=[2],
392
+ graph_channels=[168],
393
+ num_classes=1000,
394
+ pool_ratio=0.5,
395
+ distillation=False,
396
+ features_only=False,
397
+ out_indices=None,
398
+ pretrained=None,
399
+ use_detect_adapter=False,
400
+ **kwargs):
401
+ super().__init__()
402
+
403
+ self.features_only = features_only
404
+ self.pool_ratio = pool_ratio
405
+ self.out_indices = out_indices
406
+ self.distillation = distillation
407
+ self.pretrained = pretrained
408
+ self.use_detect_adapter = use_detect_adapter
409
+
410
+ # Stage 0: stem
411
+ self.stem = Stem(input_dim=3, output_dim=local_channels[0])
412
+
413
+ # Grid stages
414
+ self.grid_stages = nn.ModuleList()
415
+ in_c = local_channels[0]
416
+
417
+ self.feature_info = [dict(num_chs=in_c, reduction=2, module=f'stem')]
418
+
419
+ for i, out_c in enumerate(local_channels):
420
+ layers = []
421
+ if i > 0:
422
+ layers.append(Downsample(in_c, out_c))
423
+ in_c = out_c
424
+ for _ in range(local_blocks[i]):
425
+ layers.append(InvertedResidual(dim=out_c, mlp_ratio=4, drop_path=0.0))
426
+ self.grid_stages.append(nn.Sequential(*layers))
427
+ self.feature_info.append(dict(num_chs=out_c, reduction=2**(i+2), module=f'grid_stages.{i}'))
428
+
429
+ # Transition: grid -> graph
430
+ self.to_graph_pool = SpreadEdgePool(in_channels=in_c, ratio=pool_ratio)
431
+ self.graph_dim = in_c
432
+
433
+ # Graph stages
434
+ self.graph_proj = nn.Linear(self.graph_dim, graph_channels[0]) if graph_channels else nn.Identity()
435
+ self.graph_dim = graph_channels[0] if graph_channels else self.graph_dim
436
+
437
+ self.graph_stages = nn.ModuleList()
438
+ for _ in range(graph_blocks[0] if len(graph_blocks) > 0 else 0):
439
+ self.graph_stages.append(GraphMobileViGBlock(dim=self.graph_dim))
440
+
441
+ self.feature_info.append(dict(num_chs=self.graph_dim, reduction=2**(len(local_channels)+1), module='graph_stages'))
442
+
443
+ if not self.features_only:
444
+ # Head for classification
445
+ self.norm = nn.LayerNorm(self.graph_dim)
446
+ self.head = nn.Linear(self.graph_dim, num_classes)
447
+ if self.distillation:
448
+ self.head_dist = nn.Linear(self.graph_dim, num_classes)
449
+ else:
450
+ # For torchvision FPN
451
+ self._out_channels = []
452
+ if self.out_indices:
453
+ for i in self.out_indices:
454
+ # The indices for feature_info are shifted by 1 compared to grid_stages
455
+ # features_info: [stem(idx0), grid0(idx1), grid1(idx2), grid2(idx3), graph(idx4)]
456
+ # grid_stages: [grid0, grid1, grid2] (indices 0, 1, 2)
457
+ pass
458
+
459
+ # Detection Adapter Initialization
460
+ if self.use_detect_adapter and self.features_only:
461
+ # Determine channels automatically
462
+ # Stem: local_channels[0] (but usually we tap grid stages)
463
+ # Grid Stages output: local_channels
464
+ # Graph Stage output: graph_channels[0]
465
+
466
+ # Map indices to channels
467
+ # We assume out_indices=[0, 1, 2, 3] corresponds to [grid0, grid1, grid2, graph]
468
+ # Verify this mapping convention from forward loop
469
+ self.detect_adapters = nn.ModuleList()
470
+
471
+ # This is a bit dynamic because out_indices can vary, but assuming standard GeoViG-M/B
472
+ # and standard out_indices=[0, 1, 2, 3]
473
+
474
+ current_channels = []
475
+ # Indices refer to stages
476
+ # 0 -> grid_stages[0] (local_channels[0])
477
+ # 1 -> grid_stages[1] (local_channels[1])
478
+ # 2 -> grid_stages[2] (local_channels[2])
479
+ # 3 -> graph_stage (graph_channels[0]) (if present)
480
+
481
+ all_channels = local_channels[:]
482
+ if graph_channels:
483
+ all_channels.append(graph_channels[0])
484
+
485
+ for idx in (self.out_indices or []):
486
+ if idx < len(all_channels):
487
+ self.detect_adapters.append(GraphAdapter(all_channels[idx]))
488
+ else:
489
+ # Fallback or error
490
+ pass
491
+
492
+ self.init_weights()
493
+ if pretrained:
494
+ self._load_pretrained_weights()
495
+
496
+ # self = torch.nn.SyncBatchNorm.convert_sync_batchnorm(self)
497
+
498
+ def init_weights(self):
499
+ if self.pretrained and isinstance(self.pretrained, str):
500
+ logger = get_root_logger()
501
+ print(f"Pretrained weights being being loaded from {self.pretrained}")
502
+ logger.warn(f'Pretrained weights being loaded from {self.pretrained}')
503
+ self._load_pretrained_weights()
504
+ else:
505
+ # Init random weights if needed or rely on default init
506
+ pass
507
+
508
+ def _load_pretrained_weights(self):
509
+ """Load pretrained weights from checkpoint."""
510
+ if self.pretrained and isinstance(self.pretrained, str):
511
+ logging.info(f"Loading pretrained weights from {self.pretrained}")
512
+ try:
513
+ # Use mmcv loader if available
514
+ ckpt = _load_checkpoint(self.pretrained, logger=logging.getLogger(), map_location='cpu')
515
+ except:
516
+ ckpt = torch.load(self.pretrained, map_location='cpu')
517
+
518
+ print("ckpt keys: ", ckpt.keys())
519
+ if 'state_dict_ema' in ckpt:
520
+ state_dict = ckpt['state_dict_ema']
521
+ elif 'state_dict' in ckpt:
522
+ state_dict = ckpt['state_dict']
523
+ elif 'model' in ckpt:
524
+ state_dict = ckpt['model']
525
+ else:
526
+ state_dict = ckpt
527
+
528
+ model_dict = self.state_dict()
529
+
530
+ # Simple permissive loading
531
+ pretrained_dict = {}
532
+ for k, v in state_dict.items():
533
+ if k in model_dict:
534
+ if v.shape == model_dict[k].shape:
535
+ # Exclude classification head logic
536
+ if 'head' not in k:
537
+ pretrained_dict[k] = v
538
+ else:
539
+ print(f"Shape mismatch for {k}: {v.shape} vs {model_dict[k].shape}")
540
+
541
+ missing, unexpected = self.load_state_dict(pretrained_dict, strict=False)
542
+ print("missing_keys: ", missing)
543
+ print("unexpected_keys: ", unexpected)
544
+
545
+ @torch.no_grad()
546
+ def train(self, mode=True):
547
+ super().train(mode)
548
+ # For detection, keep BatchNorm in eval mode
549
+ for m in self.modules():
550
+ if isinstance(m, nn.BatchNorm2d):
551
+ m.eval()
552
+
553
+ def forward(self, x):
554
+ outs = []
555
+ x = self.stem(x)
556
+
557
+ for i, stage in enumerate(self.grid_stages):
558
+ x = stage(x)
559
+ if self.features_only and self.out_indices and i in self.out_indices:
560
+ outs.append(x)
561
+
562
+ # Graph processing
563
+ B, C, H, W = x.shape
564
+ x_nodes, edge_index = img_to_graph(x)
565
+ # We need to know the stride used by SpreadEdgePool to reconstruct the shape later
566
+ # Logic matches SpreadEdgePool: stride 2 if ratio <= 0.25 else 1
567
+ pool_stride = 2 if self.pool_ratio <= 0.25 else 1
568
+
569
+ x_nodes, edge_index = self.to_graph_pool(x_nodes, edge_index, shape=(H, W))
570
+ x_nodes = self.graph_proj(x_nodes)
571
+
572
+ for block in self.graph_stages:
573
+ x_nodes = block(x_nodes, edge_index)
574
+
575
+ if self.features_only:
576
+ graph_stage_idx = len(self.grid_stages)
577
+ if self.out_indices and graph_stage_idx in self.out_indices:
578
+ # Calculate correct new dimensions
579
+ new_h = H // pool_stride
580
+ new_w = W // pool_stride
581
+
582
+ # Verify shape matches
583
+ if new_h * new_w != x_nodes.shape[1]:
584
+ logger.warning(
585
+ "Shape mismatch in Graph Stage reshape. Expected %dx%d=%d, got %d",
586
+ new_h, new_w, new_h * new_w, x_nodes.shape[1]
587
+ )
588
+ # Fallback to square if totally broken (should not happen with stable pooling)
589
+ num_nodes = x_nodes.shape[1]
590
+ new_h = int(num_nodes**0.5)
591
+ new_w = int(num_nodes**0.5)
592
+ if new_h * new_w != num_nodes:
593
+ x_nodes = x_nodes[:, :new_h*new_w, :]
594
+
595
+ x_graph = x_nodes.transpose(1, 2).view(B, self.graph_dim, new_h, new_w)
596
+ outs.append(x_graph)
597
+
598
+ # Apply Adapters
599
+ final_outs = []
600
+ for i, feat in enumerate(outs):
601
+ # Adaptation: Apply GraphAdapter if enabled
602
+ if self.use_detect_adapter and hasattr(self, 'detect_adapters') and i < len(self.detect_adapters):
603
+ feat = self.detect_adapters[i](feat)
604
+
605
+ final_outs.append(feat)
606
+
607
+ return tuple(final_outs)
608
+
609
+ # Global readout (mean) for classification
610
+ x_global = x_nodes.mean(dim=1)
611
+ x_out = self.norm(x_global)
612
+
613
+ if self.distillation:
614
+ x_main = self.head(x_out)
615
+ x_dist = self.head_dist(x_out)
616
+ if self.training:
617
+ return x_main, x_dist
618
+ else:
619
+ return (x_main + x_dist) / 2
620
+ else:
621
+ return self.head(x_out)
622
+
623
+
624
+ if has_mmdet:
625
+ @det_BACKBONES.register_module()
626
+ def geovig_m_feat(pretrained=False, **kwargs):
627
+ if pretrained == 'torchvision://resnet50':
628
+ pretrained = True
629
+
630
+ # Absolute path relative to this file — works from any CWD
631
+ _here = os.path.dirname(os.path.abspath(__file__))
632
+ real_pretrained = os.path.normpath(
633
+ os.path.join(_here, '../final_model_ipa_pth/pth/geovig_m_5e4_8G_300_80_70/checkpoint.pth')
634
+ )
635
+ # Skip backbone pre-loading if path missing; init_detector loads the full ckpt anyway
636
+ if pretrained is True:
637
+ load_path = real_pretrained if os.path.exists(real_pretrained) else None
638
+ elif isinstance(pretrained, str) and os.path.exists(pretrained):
639
+ load_path = pretrained
640
+ else:
641
+ load_path = None
642
+
643
+ model = GeoViG(local_blocks=[3, 3, 9], local_channels=[64, 128, 256],
644
+ graph_blocks=[3], graph_channels=[384],
645
+ pool_ratio=0.25,
646
+ features_only=True,
647
+ out_indices=[0, 1, 2, 3],
648
+ pretrained=load_path)
649
+ model.default_cfg = default_cfgs['mobilevig']
650
+ return model
651
+
652
+ @det_BACKBONES.register_module()
653
+ def geovig_b_feat(pretrained=False, **kwargs):
654
+ if pretrained == 'torchvision://resnet50':
655
+ pretrained = True
656
+
657
+ _here = os.path.dirname(os.path.abspath(__file__))
658
+ real_pretrained = os.path.normpath(
659
+ os.path.join(_here, '../final_model_ipa_pth/pth/geovig_b_5e4_8G_300_82_38/checkpoint.pth')
660
+ )
661
+ if pretrained is True:
662
+ load_path = real_pretrained if os.path.exists(real_pretrained) else None
663
+ elif isinstance(pretrained, str) and os.path.exists(pretrained):
664
+ load_path = pretrained
665
+ else:
666
+ load_path = None
667
+
668
+ model = GeoViG(local_blocks=[5, 5, 9], local_channels=[80, 160, 320],
669
+ graph_blocks=[4], graph_channels=[512],
670
+ pool_ratio=0.25,
671
+ features_only=True,
672
+ out_indices=[0, 1, 2, 3],
673
+ pretrained=load_path)
674
+ model.default_cfg = default_cfgs['mobilevig']
675
+ return model
676
+
imagenet_labels.json ADDED
@@ -0,0 +1,1000 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ["tench",
2
+ "goldfish",
3
+ "great white shark",
4
+ "tiger shark",
5
+ "hammerhead shark",
6
+ "electric ray",
7
+ "stingray",
8
+ "cock",
9
+ "hen",
10
+ "ostrich",
11
+ "brambling",
12
+ "goldfinch",
13
+ "house finch",
14
+ "junco",
15
+ "indigo bunting",
16
+ "American robin",
17
+ "bulbul",
18
+ "jay",
19
+ "magpie",
20
+ "chickadee",
21
+ "American dipper",
22
+ "kite",
23
+ "bald eagle",
24
+ "vulture",
25
+ "great grey owl",
26
+ "fire salamander",
27
+ "smooth newt",
28
+ "newt",
29
+ "spotted salamander",
30
+ "axolotl",
31
+ "American bullfrog",
32
+ "tree frog",
33
+ "tailed frog",
34
+ "loggerhead sea turtle",
35
+ "leatherback sea turtle",
36
+ "mud turtle",
37
+ "terrapin",
38
+ "box turtle",
39
+ "banded gecko",
40
+ "green iguana",
41
+ "Carolina anole",
42
+ "desert grassland whiptail lizard",
43
+ "agama",
44
+ "frilled-necked lizard",
45
+ "alligator lizard",
46
+ "Gila monster",
47
+ "European green lizard",
48
+ "chameleon",
49
+ "Komodo dragon",
50
+ "Nile crocodile",
51
+ "American alligator",
52
+ "triceratops",
53
+ "worm snake",
54
+ "ring-necked snake",
55
+ "eastern hog-nosed snake",
56
+ "smooth green snake",
57
+ "kingsnake",
58
+ "garter snake",
59
+ "water snake",
60
+ "vine snake",
61
+ "night snake",
62
+ "boa constrictor",
63
+ "African rock python",
64
+ "Indian cobra",
65
+ "green mamba",
66
+ "sea snake",
67
+ "Saharan horned viper",
68
+ "eastern diamondback rattlesnake",
69
+ "sidewinder",
70
+ "trilobite",
71
+ "harvestman",
72
+ "scorpion",
73
+ "yellow garden spider",
74
+ "barn spider",
75
+ "European garden spider",
76
+ "southern black widow",
77
+ "tarantula",
78
+ "wolf spider",
79
+ "tick",
80
+ "centipede",
81
+ "black grouse",
82
+ "ptarmigan",
83
+ "ruffed grouse",
84
+ "prairie grouse",
85
+ "peacock",
86
+ "quail",
87
+ "partridge",
88
+ "grey parrot",
89
+ "macaw",
90
+ "sulphur-crested cockatoo",
91
+ "lorikeet",
92
+ "coucal",
93
+ "bee eater",
94
+ "hornbill",
95
+ "hummingbird",
96
+ "jacamar",
97
+ "toucan",
98
+ "duck",
99
+ "red-breasted merganser",
100
+ "goose",
101
+ "black swan",
102
+ "tusker",
103
+ "echidna",
104
+ "platypus",
105
+ "wallaby",
106
+ "koala",
107
+ "wombat",
108
+ "jellyfish",
109
+ "sea anemone",
110
+ "brain coral",
111
+ "flatworm",
112
+ "nematode",
113
+ "conch",
114
+ "snail",
115
+ "slug",
116
+ "sea slug",
117
+ "chiton",
118
+ "chambered nautilus",
119
+ "Dungeness crab",
120
+ "rock crab",
121
+ "fiddler crab",
122
+ "red king crab",
123
+ "American lobster",
124
+ "spiny lobster",
125
+ "crayfish",
126
+ "hermit crab",
127
+ "isopod",
128
+ "white stork",
129
+ "black stork",
130
+ "spoonbill",
131
+ "flamingo",
132
+ "little blue heron",
133
+ "great egret",
134
+ "bittern",
135
+ "crane (bird)",
136
+ "limpkin",
137
+ "common gallinule",
138
+ "American coot",
139
+ "bustard",
140
+ "ruddy turnstone",
141
+ "dunlin",
142
+ "common redshank",
143
+ "dowitcher",
144
+ "oystercatcher",
145
+ "pelican",
146
+ "king penguin",
147
+ "albatross",
148
+ "grey whale",
149
+ "killer whale",
150
+ "dugong",
151
+ "sea lion",
152
+ "Chihuahua",
153
+ "Japanese Chin",
154
+ "Maltese",
155
+ "Pekingese",
156
+ "Shih Tzu",
157
+ "King Charles Spaniel",
158
+ "Papillon",
159
+ "toy terrier",
160
+ "Rhodesian Ridgeback",
161
+ "Afghan Hound",
162
+ "Basset Hound",
163
+ "Beagle",
164
+ "Bloodhound",
165
+ "Bluetick Coonhound",
166
+ "Black and Tan Coonhound",
167
+ "Treeing Walker Coonhound",
168
+ "English foxhound",
169
+ "Redbone Coonhound",
170
+ "borzoi",
171
+ "Irish Wolfhound",
172
+ "Italian Greyhound",
173
+ "Whippet",
174
+ "Ibizan Hound",
175
+ "Norwegian Elkhound",
176
+ "Otterhound",
177
+ "Saluki",
178
+ "Scottish Deerhound",
179
+ "Weimaraner",
180
+ "Staffordshire Bull Terrier",
181
+ "American Staffordshire Terrier",
182
+ "Bedlington Terrier",
183
+ "Border Terrier",
184
+ "Kerry Blue Terrier",
185
+ "Irish Terrier",
186
+ "Norfolk Terrier",
187
+ "Norwich Terrier",
188
+ "Yorkshire Terrier",
189
+ "Wire Fox Terrier",
190
+ "Lakeland Terrier",
191
+ "Sealyham Terrier",
192
+ "Airedale Terrier",
193
+ "Cairn Terrier",
194
+ "Australian Terrier",
195
+ "Dandie Dinmont Terrier",
196
+ "Boston Terrier",
197
+ "Miniature Schnauzer",
198
+ "Giant Schnauzer",
199
+ "Standard Schnauzer",
200
+ "Scottish Terrier",
201
+ "Tibetan Terrier",
202
+ "Australian Silky Terrier",
203
+ "Soft-coated Wheaten Terrier",
204
+ "West Highland White Terrier",
205
+ "Lhasa Apso",
206
+ "Flat-Coated Retriever",
207
+ "Curly-coated Retriever",
208
+ "Golden Retriever",
209
+ "Labrador Retriever",
210
+ "Chesapeake Bay Retriever",
211
+ "German Shorthaired Pointer",
212
+ "Vizsla",
213
+ "English Setter",
214
+ "Irish Setter",
215
+ "Gordon Setter",
216
+ "Brittany Spaniel",
217
+ "Clumber Spaniel",
218
+ "English Springer Spaniel",
219
+ "Welsh Springer Spaniel",
220
+ "Cocker Spaniels",
221
+ "Sussex Spaniel",
222
+ "Irish Water Spaniel",
223
+ "Kuvasz",
224
+ "Schipperke",
225
+ "Groenendael",
226
+ "Malinois",
227
+ "Briard",
228
+ "Australian Kelpie",
229
+ "Komondor",
230
+ "Old English Sheepdog",
231
+ "Shetland Sheepdog",
232
+ "collie",
233
+ "Border Collie",
234
+ "Bouvier des Flandres",
235
+ "Rottweiler",
236
+ "German Shepherd Dog",
237
+ "Dobermann",
238
+ "Miniature Pinscher",
239
+ "Greater Swiss Mountain Dog",
240
+ "Bernese Mountain Dog",
241
+ "Appenzeller Sennenhund",
242
+ "Entlebucher Sennenhund",
243
+ "Boxer",
244
+ "Bullmastiff",
245
+ "Tibetan Mastiff",
246
+ "French Bulldog",
247
+ "Great Dane",
248
+ "St. Bernard",
249
+ "husky",
250
+ "Alaskan Malamute",
251
+ "Siberian Husky",
252
+ "Dalmatian",
253
+ "Affenpinscher",
254
+ "Basenji",
255
+ "pug",
256
+ "Leonberger",
257
+ "Newfoundland",
258
+ "Pyrenean Mountain Dog",
259
+ "Samoyed",
260
+ "Pomeranian",
261
+ "Chow Chow",
262
+ "Keeshond",
263
+ "Griffon Bruxellois",
264
+ "Pembroke Welsh Corgi",
265
+ "Cardigan Welsh Corgi",
266
+ "Toy Poodle",
267
+ "Miniature Poodle",
268
+ "Standard Poodle",
269
+ "Mexican hairless dog",
270
+ "grey wolf",
271
+ "Alaskan tundra wolf",
272
+ "red wolf",
273
+ "coyote",
274
+ "dingo",
275
+ "dhole",
276
+ "African wild dog",
277
+ "hyena",
278
+ "red fox",
279
+ "kit fox",
280
+ "Arctic fox",
281
+ "grey fox",
282
+ "tabby cat",
283
+ "tiger cat",
284
+ "Persian cat",
285
+ "Siamese cat",
286
+ "Egyptian Mau",
287
+ "cougar",
288
+ "lynx",
289
+ "leopard",
290
+ "snow leopard",
291
+ "jaguar",
292
+ "lion",
293
+ "tiger",
294
+ "cheetah",
295
+ "brown bear",
296
+ "American black bear",
297
+ "polar bear",
298
+ "sloth bear",
299
+ "mongoose",
300
+ "meerkat",
301
+ "tiger beetle",
302
+ "ladybug",
303
+ "ground beetle",
304
+ "longhorn beetle",
305
+ "leaf beetle",
306
+ "dung beetle",
307
+ "rhinoceros beetle",
308
+ "weevil",
309
+ "fly",
310
+ "bee",
311
+ "ant",
312
+ "grasshopper",
313
+ "cricket",
314
+ "stick insect",
315
+ "cockroach",
316
+ "mantis",
317
+ "cicada",
318
+ "leafhopper",
319
+ "lacewing",
320
+ "dragonfly",
321
+ "damselfly",
322
+ "red admiral",
323
+ "ringlet",
324
+ "monarch butterfly",
325
+ "small white",
326
+ "sulphur butterfly",
327
+ "gossamer-winged butterfly",
328
+ "starfish",
329
+ "sea urchin",
330
+ "sea cucumber",
331
+ "cottontail rabbit",
332
+ "hare",
333
+ "Angora rabbit",
334
+ "hamster",
335
+ "porcupine",
336
+ "fox squirrel",
337
+ "marmot",
338
+ "beaver",
339
+ "guinea pig",
340
+ "common sorrel",
341
+ "zebra",
342
+ "pig",
343
+ "wild boar",
344
+ "warthog",
345
+ "hippopotamus",
346
+ "ox",
347
+ "water buffalo",
348
+ "bison",
349
+ "ram",
350
+ "bighorn sheep",
351
+ "Alpine ibex",
352
+ "hartebeest",
353
+ "impala",
354
+ "gazelle",
355
+ "dromedary",
356
+ "llama",
357
+ "weasel",
358
+ "mink",
359
+ "European polecat",
360
+ "black-footed ferret",
361
+ "otter",
362
+ "skunk",
363
+ "badger",
364
+ "armadillo",
365
+ "three-toed sloth",
366
+ "orangutan",
367
+ "gorilla",
368
+ "chimpanzee",
369
+ "gibbon",
370
+ "siamang",
371
+ "guenon",
372
+ "patas monkey",
373
+ "baboon",
374
+ "macaque",
375
+ "langur",
376
+ "black-and-white colobus",
377
+ "proboscis monkey",
378
+ "marmoset",
379
+ "white-headed capuchin",
380
+ "howler monkey",
381
+ "titi",
382
+ "Geoffroy's spider monkey",
383
+ "common squirrel monkey",
384
+ "ring-tailed lemur",
385
+ "indri",
386
+ "Asian elephant",
387
+ "African bush elephant",
388
+ "red panda",
389
+ "giant panda",
390
+ "snoek",
391
+ "eel",
392
+ "coho salmon",
393
+ "rock beauty",
394
+ "clownfish",
395
+ "sturgeon",
396
+ "garfish",
397
+ "lionfish",
398
+ "pufferfish",
399
+ "abacus",
400
+ "abaya",
401
+ "academic gown",
402
+ "accordion",
403
+ "acoustic guitar",
404
+ "aircraft carrier",
405
+ "airliner",
406
+ "airship",
407
+ "altar",
408
+ "ambulance",
409
+ "amphibious vehicle",
410
+ "analog clock",
411
+ "apiary",
412
+ "apron",
413
+ "waste container",
414
+ "assault rifle",
415
+ "backpack",
416
+ "bakery",
417
+ "balance beam",
418
+ "balloon",
419
+ "ballpoint pen",
420
+ "Band-Aid",
421
+ "banjo",
422
+ "baluster",
423
+ "barbell",
424
+ "barber chair",
425
+ "barbershop",
426
+ "barn",
427
+ "barometer",
428
+ "barrel",
429
+ "wheelbarrow",
430
+ "baseball",
431
+ "basketball",
432
+ "bassinet",
433
+ "bassoon",
434
+ "swimming cap",
435
+ "bath towel",
436
+ "bathtub",
437
+ "station wagon",
438
+ "lighthouse",
439
+ "beaker",
440
+ "military cap",
441
+ "beer bottle",
442
+ "beer glass",
443
+ "bell-cot",
444
+ "bib",
445
+ "tandem bicycle",
446
+ "bikini",
447
+ "ring binder",
448
+ "binoculars",
449
+ "birdhouse",
450
+ "boathouse",
451
+ "bobsleigh",
452
+ "bolo tie",
453
+ "poke bonnet",
454
+ "bookcase",
455
+ "bookstore",
456
+ "bottle cap",
457
+ "bow",
458
+ "bow tie",
459
+ "brass",
460
+ "bra",
461
+ "breakwater",
462
+ "breastplate",
463
+ "broom",
464
+ "bucket",
465
+ "buckle",
466
+ "bulletproof vest",
467
+ "high-speed train",
468
+ "butcher shop",
469
+ "taxicab",
470
+ "cauldron",
471
+ "candle",
472
+ "cannon",
473
+ "canoe",
474
+ "can opener",
475
+ "cardigan",
476
+ "car mirror",
477
+ "carousel",
478
+ "tool kit",
479
+ "carton",
480
+ "car wheel",
481
+ "automated teller machine",
482
+ "cassette",
483
+ "cassette player",
484
+ "castle",
485
+ "catamaran",
486
+ "CD player",
487
+ "cello",
488
+ "mobile phone",
489
+ "chain",
490
+ "chain-link fence",
491
+ "chain mail",
492
+ "chainsaw",
493
+ "chest",
494
+ "chiffonier",
495
+ "chime",
496
+ "china cabinet",
497
+ "Christmas stocking",
498
+ "church",
499
+ "movie theater",
500
+ "cleaver",
501
+ "cliff dwelling",
502
+ "cloak",
503
+ "clogs",
504
+ "cocktail shaker",
505
+ "coffee mug",
506
+ "coffeemaker",
507
+ "coil",
508
+ "combination lock",
509
+ "computer keyboard",
510
+ "confectionery store",
511
+ "container ship",
512
+ "convertible",
513
+ "corkscrew",
514
+ "cornet",
515
+ "cowboy boot",
516
+ "cowboy hat",
517
+ "cradle",
518
+ "crane (machine)",
519
+ "crash helmet",
520
+ "crate",
521
+ "infant bed",
522
+ "Crock Pot",
523
+ "croquet ball",
524
+ "crutch",
525
+ "cuirass",
526
+ "dam",
527
+ "desk",
528
+ "desktop computer",
529
+ "rotary dial telephone",
530
+ "diaper",
531
+ "digital clock",
532
+ "digital watch",
533
+ "dining table",
534
+ "dishcloth",
535
+ "dishwasher",
536
+ "disc brake",
537
+ "dock",
538
+ "dog sled",
539
+ "dome",
540
+ "doormat",
541
+ "drilling rig",
542
+ "drum",
543
+ "drumstick",
544
+ "dumbbell",
545
+ "Dutch oven",
546
+ "electric fan",
547
+ "electric guitar",
548
+ "electric locomotive",
549
+ "entertainment center",
550
+ "envelope",
551
+ "espresso machine",
552
+ "face powder",
553
+ "feather boa",
554
+ "filing cabinet",
555
+ "fireboat",
556
+ "fire engine",
557
+ "fire screen sheet",
558
+ "flagpole",
559
+ "flute",
560
+ "folding chair",
561
+ "football helmet",
562
+ "forklift",
563
+ "fountain",
564
+ "fountain pen",
565
+ "four-poster bed",
566
+ "freight car",
567
+ "French horn",
568
+ "frying pan",
569
+ "fur coat",
570
+ "garbage truck",
571
+ "gas mask",
572
+ "gas pump",
573
+ "goblet",
574
+ "go-kart",
575
+ "golf ball",
576
+ "golf cart",
577
+ "gondola",
578
+ "gong",
579
+ "gown",
580
+ "grand piano",
581
+ "greenhouse",
582
+ "grille",
583
+ "grocery store",
584
+ "guillotine",
585
+ "barrette",
586
+ "hair spray",
587
+ "half-track",
588
+ "hammer",
589
+ "hamper",
590
+ "hair dryer",
591
+ "hand-held computer",
592
+ "handkerchief",
593
+ "hard disk drive",
594
+ "harmonica",
595
+ "harp",
596
+ "harvester",
597
+ "hatchet",
598
+ "holster",
599
+ "home theater",
600
+ "honeycomb",
601
+ "hook",
602
+ "hoop skirt",
603
+ "horizontal bar",
604
+ "horse-drawn vehicle",
605
+ "hourglass",
606
+ "iPod",
607
+ "clothes iron",
608
+ "jack-o'-lantern",
609
+ "jeans",
610
+ "jeep",
611
+ "T-shirt",
612
+ "jigsaw puzzle",
613
+ "pulled rickshaw",
614
+ "joystick",
615
+ "kimono",
616
+ "knee pad",
617
+ "knot",
618
+ "lab coat",
619
+ "ladle",
620
+ "lampshade",
621
+ "laptop computer",
622
+ "lawn mower",
623
+ "lens cap",
624
+ "paper knife",
625
+ "library",
626
+ "lifeboat",
627
+ "lighter",
628
+ "limousine",
629
+ "ocean liner",
630
+ "lipstick",
631
+ "slip-on shoe",
632
+ "lotion",
633
+ "speaker",
634
+ "loupe",
635
+ "sawmill",
636
+ "magnetic compass",
637
+ "mail bag",
638
+ "mailbox",
639
+ "tights",
640
+ "tank suit",
641
+ "manhole cover",
642
+ "maraca",
643
+ "marimba",
644
+ "mask",
645
+ "match",
646
+ "maypole",
647
+ "maze",
648
+ "measuring cup",
649
+ "medicine chest",
650
+ "megalith",
651
+ "microphone",
652
+ "microwave oven",
653
+ "military uniform",
654
+ "milk can",
655
+ "minibus",
656
+ "miniskirt",
657
+ "minivan",
658
+ "missile",
659
+ "mitten",
660
+ "mixing bowl",
661
+ "mobile home",
662
+ "Model T",
663
+ "modem",
664
+ "monastery",
665
+ "monitor",
666
+ "moped",
667
+ "mortar",
668
+ "square academic cap",
669
+ "mosque",
670
+ "mosquito net",
671
+ "scooter",
672
+ "mountain bike",
673
+ "tent",
674
+ "computer mouse",
675
+ "mousetrap",
676
+ "moving van",
677
+ "muzzle",
678
+ "nail",
679
+ "neck brace",
680
+ "necklace",
681
+ "nipple",
682
+ "notebook computer",
683
+ "obelisk",
684
+ "oboe",
685
+ "ocarina",
686
+ "odometer",
687
+ "oil filter",
688
+ "organ",
689
+ "oscilloscope",
690
+ "overskirt",
691
+ "bullock cart",
692
+ "oxygen mask",
693
+ "packet",
694
+ "paddle",
695
+ "paddle wheel",
696
+ "padlock",
697
+ "paintbrush",
698
+ "pajamas",
699
+ "palace",
700
+ "pan flute",
701
+ "paper towel",
702
+ "parachute",
703
+ "parallel bars",
704
+ "park bench",
705
+ "parking meter",
706
+ "passenger car",
707
+ "patio",
708
+ "payphone",
709
+ "pedestal",
710
+ "pencil case",
711
+ "pencil sharpener",
712
+ "perfume",
713
+ "Petri dish",
714
+ "photocopier",
715
+ "plectrum",
716
+ "Pickelhaube",
717
+ "picket fence",
718
+ "pickup truck",
719
+ "pier",
720
+ "piggy bank",
721
+ "pill bottle",
722
+ "pillow",
723
+ "ping-pong ball",
724
+ "pinwheel",
725
+ "pirate ship",
726
+ "pitcher",
727
+ "hand plane",
728
+ "planetarium",
729
+ "plastic bag",
730
+ "plate rack",
731
+ "plow",
732
+ "plunger",
733
+ "Polaroid camera",
734
+ "pole",
735
+ "police van",
736
+ "poncho",
737
+ "billiard table",
738
+ "soda bottle",
739
+ "pot",
740
+ "potter's wheel",
741
+ "power drill",
742
+ "prayer rug",
743
+ "printer",
744
+ "prison",
745
+ "projectile",
746
+ "projector",
747
+ "hockey puck",
748
+ "punching bag",
749
+ "purse",
750
+ "quill",
751
+ "quilt",
752
+ "race car",
753
+ "racket",
754
+ "radiator",
755
+ "radio",
756
+ "radio telescope",
757
+ "rain barrel",
758
+ "recreational vehicle",
759
+ "reel",
760
+ "reflex camera",
761
+ "refrigerator",
762
+ "remote control",
763
+ "restaurant",
764
+ "revolver",
765
+ "rifle",
766
+ "rocking chair",
767
+ "rotisserie",
768
+ "eraser",
769
+ "rugby ball",
770
+ "ruler",
771
+ "running shoe",
772
+ "safe",
773
+ "safety pin",
774
+ "salt shaker",
775
+ "sandal",
776
+ "sarong",
777
+ "saxophone",
778
+ "scabbard",
779
+ "weighing scale",
780
+ "school bus",
781
+ "schooner",
782
+ "scoreboard",
783
+ "CRT screen",
784
+ "screw",
785
+ "screwdriver",
786
+ "seat belt",
787
+ "sewing machine",
788
+ "shield",
789
+ "shoe store",
790
+ "shoji",
791
+ "shopping basket",
792
+ "shopping cart",
793
+ "shovel",
794
+ "shower cap",
795
+ "shower curtain",
796
+ "ski",
797
+ "ski mask",
798
+ "sleeping bag",
799
+ "slide rule",
800
+ "sliding door",
801
+ "slot machine",
802
+ "snorkel",
803
+ "snowmobile",
804
+ "snowplow",
805
+ "soap dispenser",
806
+ "soccer ball",
807
+ "sock",
808
+ "solar thermal collector",
809
+ "sombrero",
810
+ "soup bowl",
811
+ "space bar",
812
+ "space heater",
813
+ "space shuttle",
814
+ "spatula",
815
+ "motorboat",
816
+ "spider web",
817
+ "spindle",
818
+ "sports car",
819
+ "spotlight",
820
+ "stage",
821
+ "steam locomotive",
822
+ "through arch bridge",
823
+ "steel drum",
824
+ "stethoscope",
825
+ "scarf",
826
+ "stone wall",
827
+ "stopwatch",
828
+ "stove",
829
+ "strainer",
830
+ "tram",
831
+ "stretcher",
832
+ "couch",
833
+ "stupa",
834
+ "submarine",
835
+ "suit",
836
+ "sundial",
837
+ "sunglass",
838
+ "sunglasses",
839
+ "sunscreen",
840
+ "suspension bridge",
841
+ "mop",
842
+ "sweatshirt",
843
+ "swimsuit",
844
+ "swing",
845
+ "switch",
846
+ "syringe",
847
+ "table lamp",
848
+ "tank",
849
+ "tape player",
850
+ "teapot",
851
+ "teddy bear",
852
+ "television",
853
+ "tennis ball",
854
+ "thatched roof",
855
+ "front curtain",
856
+ "thimble",
857
+ "threshing machine",
858
+ "throne",
859
+ "tile roof",
860
+ "toaster",
861
+ "tobacco shop",
862
+ "toilet seat",
863
+ "torch",
864
+ "totem pole",
865
+ "tow truck",
866
+ "toy store",
867
+ "tractor",
868
+ "semi-trailer truck",
869
+ "tray",
870
+ "trench coat",
871
+ "tricycle",
872
+ "trimaran",
873
+ "tripod",
874
+ "triumphal arch",
875
+ "trolleybus",
876
+ "trombone",
877
+ "tub",
878
+ "turnstile",
879
+ "typewriter keyboard",
880
+ "umbrella",
881
+ "unicycle",
882
+ "upright piano",
883
+ "vacuum cleaner",
884
+ "vase",
885
+ "vault",
886
+ "velvet",
887
+ "vending machine",
888
+ "vestment",
889
+ "viaduct",
890
+ "violin",
891
+ "volleyball",
892
+ "waffle iron",
893
+ "wall clock",
894
+ "wallet",
895
+ "wardrobe",
896
+ "military aircraft",
897
+ "sink",
898
+ "washing machine",
899
+ "water bottle",
900
+ "water jug",
901
+ "water tower",
902
+ "whiskey jug",
903
+ "whistle",
904
+ "wig",
905
+ "window screen",
906
+ "window shade",
907
+ "Windsor tie",
908
+ "wine bottle",
909
+ "wing",
910
+ "wok",
911
+ "wooden spoon",
912
+ "wool",
913
+ "split-rail fence",
914
+ "shipwreck",
915
+ "yawl",
916
+ "yurt",
917
+ "website",
918
+ "comic book",
919
+ "crossword",
920
+ "traffic sign",
921
+ "traffic light",
922
+ "dust jacket",
923
+ "menu",
924
+ "plate",
925
+ "guacamole",
926
+ "consomme",
927
+ "hot pot",
928
+ "trifle",
929
+ "ice cream",
930
+ "ice pop",
931
+ "baguette",
932
+ "bagel",
933
+ "pretzel",
934
+ "cheeseburger",
935
+ "hot dog",
936
+ "mashed potato",
937
+ "cabbage",
938
+ "broccoli",
939
+ "cauliflower",
940
+ "zucchini",
941
+ "spaghetti squash",
942
+ "acorn squash",
943
+ "butternut squash",
944
+ "cucumber",
945
+ "artichoke",
946
+ "bell pepper",
947
+ "cardoon",
948
+ "mushroom",
949
+ "Granny Smith",
950
+ "strawberry",
951
+ "orange",
952
+ "lemon",
953
+ "fig",
954
+ "pineapple",
955
+ "banana",
956
+ "jackfruit",
957
+ "custard apple",
958
+ "pomegranate",
959
+ "hay",
960
+ "carbonara",
961
+ "chocolate syrup",
962
+ "dough",
963
+ "meatloaf",
964
+ "pizza",
965
+ "pot pie",
966
+ "burrito",
967
+ "red wine",
968
+ "espresso",
969
+ "cup",
970
+ "eggnog",
971
+ "alp",
972
+ "bubble",
973
+ "cliff",
974
+ "coral reef",
975
+ "geyser",
976
+ "lakeshore",
977
+ "promontory",
978
+ "shoal",
979
+ "seashore",
980
+ "valley",
981
+ "volcano",
982
+ "baseball player",
983
+ "bridegroom",
984
+ "scuba diver",
985
+ "rapeseed",
986
+ "daisy",
987
+ "yellow lady's slipper",
988
+ "corn",
989
+ "acorn",
990
+ "rose hip",
991
+ "horse chestnut seed",
992
+ "coral fungus",
993
+ "agaric",
994
+ "gyromitra",
995
+ "stinkhorn mushroom",
996
+ "earth star",
997
+ "hen-of-the-woods",
998
+ "bolete",
999
+ "ear of corn",
1000
+ "toilet paper"]
pre_run.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # HF Spaces pre-run hook — installs mmcv-full with the correct CUDA/torch wheel
3
+ set -e
4
+ echo "Installing mmcv-full via openmim..."
5
+ mim install mmcv-full==1.7.2
6
+ echo "mmcv-full installed successfully."
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Core — classification tab (always works)
2
+ torch>=1.12.0
3
+ torchvision>=0.13.0
4
+ timm==0.6.12
5
+ gradio>=4.0.0
6
+ huggingface_hub>=0.20.0
7
+ Pillow>=10.0.0
8
+ numpy>=1.23.0
9
+ scipy
10
+
11
+ # Detection & Medical tabs
12
+ openmim
13
+ mmdet==2.28.0
14
+ pycocotools
15
+ opencv-python-headless