feng-x commited on
Commit
d3d0932
·
verified ·
1 Parent(s): 594386a

Upload folder using huggingface_hub

Browse files
AGENTS.md CHANGED
@@ -21,8 +21,7 @@ For tasks of **reboot** from a new codex session:
21
  1. Read doc/v0/PRD.md, doc/v0/Plan.md, doc/v0/Progress.md for baseline implementation
22
  2. Read doc/v1/PRD.md, doc/v1/Plan.md, doc/v1/Progress.md for edge refinement (v1)
23
  3. Read doc/v4/PRD.md, doc/v4/Plan.md, doc/v4/Progress.md for SAM 2.1 integration (card + hand)
24
- 4. Read doc/v5/PRD.md, doc/v5/Plan.md, doc/v5/Progress.md for the Gradio/ZeroGPU deployment port
25
- 5. Assume this is a continuation of an existing project.
26
  5. Summarize your understanding of the current state and propose the next concrete step without writing code yet.
27
 
28
  ## Project Overview
 
21
  1. Read doc/v0/PRD.md, doc/v0/Plan.md, doc/v0/Progress.md for baseline implementation
22
  2. Read doc/v1/PRD.md, doc/v1/Plan.md, doc/v1/Progress.md for edge refinement (v1)
23
  3. Read doc/v4/PRD.md, doc/v4/Plan.md, doc/v4/Progress.md for SAM 2.1 integration (card + hand)
24
+ 4. Assume this is a continuation of an existing project.
 
25
  5. Summarize your understanding of the current state and propose the next concrete step without writing code yet.
26
 
27
  ## Project Overview
CLAUDE.md CHANGED
@@ -21,8 +21,7 @@ For tasks of **reboot** from a new codex session:
21
  1. Read doc/v0/PRD.md, doc/v0/Plan.md, doc/v0/Progress.md for baseline implementation
22
  2. Read doc/v1/PRD.md, doc/v1/Plan.md, doc/v1/Progress.md for edge refinement (v1)
23
  3. Read doc/v4/PRD.md, doc/v4/Plan.md, doc/v4/Progress.md for SAM 2.1 integration (card + hand)
24
- 4. Read doc/v5/PRD.md, doc/v5/Plan.md, doc/v5/Progress.md for the Gradio/ZeroGPU deployment port
25
- 5. Assume this is a continuation of an existing project.
26
  5. Summarize your understanding of the current state and propose the next concrete step without writing code yet.
27
 
28
  ## Project Overview
 
21
  1. Read doc/v0/PRD.md, doc/v0/Plan.md, doc/v0/Progress.md for baseline implementation
22
  2. Read doc/v1/PRD.md, doc/v1/Plan.md, doc/v1/Progress.md for edge refinement (v1)
23
  3. Read doc/v4/PRD.md, doc/v4/Plan.md, doc/v4/Progress.md for SAM 2.1 integration (card + hand)
24
+ 4. Assume this is a continuation of an existing project.
 
25
  5. Summarize your understanding of the current state and propose the next concrete step without writing code yet.
26
 
27
  ## Project Overview
README.md CHANGED
@@ -3,10 +3,8 @@ title: Ring Sizer
3
  emoji: "\U0001F48D"
4
  colorFrom: blue
5
  colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.49.0
8
- app_file: app.py
9
- python_version: "3.10"
10
  ---
11
 
12
  # Ring Sizer
 
3
  emoji: "\U0001F48D"
4
  colorFrom: blue
5
  colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
 
 
8
  ---
9
 
10
  # Ring Sizer
requirements.txt CHANGED
@@ -7,22 +7,8 @@ flask>=3.0.0
7
  gunicorn>=21.2.0
8
  openai>=1.0.0
9
  supabase>=2.0.0
10
- # SAM 2.1 via HuggingFace transformers (card segmentation).
11
- # transformers pinned to the 4.x track for stability (SAM integration was
12
- # validated there). 4.x caps huggingface_hub at <1.0, which is fine for
13
- # everything downstream.
14
  torch>=2.4.0
15
  torchvision>=0.19.0
16
- transformers>=4.47.0,<5.0
17
- huggingface_hub>=0.33.5,<1.0
18
  pillow>=10.0.0
19
- # v5: HF ZeroGPU requires Gradio SDK; `spaces` provides @spaces.GPU (no-op off ZeroGPU).
20
- # Pinned to Gradio 5.x because:
21
- # - Gradio 4.x imports `HfFolder` from huggingface_hub; 5.x dropped it.
22
- # - Gradio 4.44 passes a dict as the template name to Starlette's
23
- # `TemplateResponse(...)`, which breaks against the newer
24
- # `starlette>=0.40` that Gradio's own deps pull in — crashes with
25
- # `TypeError: unhashable type: 'dict'` on every page load.
26
- # - Gradio 5.49 fixes both above.
27
- gradio>=5.49.0
28
- spaces>=0.30.0
 
7
  gunicorn>=21.2.0
8
  openai>=1.0.0
9
  supabase>=2.0.0
10
+ # SAM 2.1 via HuggingFace transformers (card segmentation)
 
 
 
11
  torch>=2.4.0
12
  torchvision>=0.19.0
13
+ transformers>=4.47.0
 
14
  pillow>=10.0.0
 
 
 
 
 
 
 
 
 
 
src/sam_backend.py CHANGED
@@ -23,35 +23,6 @@ INFERENCE_MAX_SIDE = 1024
23
 
24
  _model = None
25
  _processor = None
26
- _device: str = "cpu"
27
-
28
-
29
- def _select_device() -> str:
30
- """Pick a torch device for SAM inference.
31
-
32
- Returns ``"cuda"`` when a GPU is visible (HF ZeroGPU exposes CUDA even
33
- at module import time via an emulation shim, so this picks the right
34
- path both at startup and inside ``@spaces.GPU`` functions), otherwise
35
- ``"cpu"``. Import of torch is local so CLI users without it still see
36
- a clean error from the caller.
37
- """
38
- try:
39
- import torch
40
- if torch.cuda.is_available():
41
- return "cuda"
42
- except Exception:
43
- pass
44
- return "cpu"
45
-
46
-
47
- def get_sam2_device() -> str:
48
- """Return the device the SAM singleton was loaded on.
49
-
50
- Callers use this to move their ``processor(..., return_tensors="pt")``
51
- outputs onto the same device as the model before the forward pass.
52
- Returns ``"cpu"`` before ``get_sam2()`` has been called.
53
- """
54
- return _device
55
 
56
 
57
  def get_sam2() -> Tuple[object, object]:
@@ -61,26 +32,19 @@ def get_sam2() -> Tuple[object, object]:
61
  the HEAD-request retry storm that happens when huggingface.co is slow or
62
  unreachable but the weights are already on disk. On a true cache miss we
63
  fall through to a normal online load.
64
-
65
- The model is placed on the device returned by ``_select_device()``.
66
- HF ZeroGPU docs require CUDA placements to happen at module-level
67
- startup for best performance — callers in ZeroGPU Spaces should invoke
68
- ``get_sam2()`` once at import time so this runs before the first
69
- ``@spaces.GPU``-wrapped request.
70
  """
71
- global _model, _processor, _device
72
  if _model is None or _processor is None:
73
  from transformers import Sam2Model, Sam2Processor
74
- _device = _select_device()
75
  t0 = time.time()
76
- print(f" Loading SAM 2.1 ({SAM2_MODEL_ID}) on {_device}...")
77
  try:
78
  _processor = Sam2Processor.from_pretrained(SAM2_MODEL_ID, local_files_only=True)
79
- _model = Sam2Model.from_pretrained(SAM2_MODEL_ID, local_files_only=True).to(_device).eval()
80
- print(f" SAM 2.1 loaded (offline cache, {_device}) in {time.time() - t0:.1f}s")
81
  except (OSError, ValueError):
82
  # Cache miss — fall back to online download.
83
  _processor = Sam2Processor.from_pretrained(SAM2_MODEL_ID)
84
- _model = Sam2Model.from_pretrained(SAM2_MODEL_ID).to(_device).eval()
85
- print(f" SAM 2.1 loaded (online, {_device}) in {time.time() - t0:.1f}s")
86
  return _model, _processor
 
23
 
24
  _model = None
25
  _processor = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
 
28
  def get_sam2() -> Tuple[object, object]:
 
32
  the HEAD-request retry storm that happens when huggingface.co is slow or
33
  unreachable but the weights are already on disk. On a true cache miss we
34
  fall through to a normal online load.
 
 
 
 
 
 
35
  """
36
+ global _model, _processor
37
  if _model is None or _processor is None:
38
  from transformers import Sam2Model, Sam2Processor
 
39
  t0 = time.time()
40
+ print(f" Loading SAM 2.1 ({SAM2_MODEL_ID})...")
41
  try:
42
  _processor = Sam2Processor.from_pretrained(SAM2_MODEL_ID, local_files_only=True)
43
+ _model = Sam2Model.from_pretrained(SAM2_MODEL_ID, local_files_only=True).to("cpu").eval()
44
+ print(f" SAM 2.1 loaded (offline cache) in {time.time() - t0:.1f}s")
45
  except (OSError, ValueError):
46
  # Cache miss — fall back to online download.
47
  _processor = Sam2Processor.from_pretrained(SAM2_MODEL_ID)
48
+ _model = Sam2Model.from_pretrained(SAM2_MODEL_ID).to("cpu").eval()
49
+ print(f" SAM 2.1 loaded (online) in {time.time() - t0:.1f}s")
50
  return _model, _processor
src/sam_card_detection.py CHANGED
@@ -26,7 +26,7 @@ from .card_detection import (
26
  get_quad_dimensions,
27
  order_corners,
28
  )
29
- from .sam_backend import INFERENCE_MAX_SIDE as PROMPT_INFERENCE_MAX_SIDE, get_sam2, get_sam2_device
30
 
31
  # HF Hub model id — tiny, small, base-plus, large
32
  SAM2_MODEL_ID = "facebook/sam2.1-hiera-small"
@@ -531,13 +531,6 @@ def detect_credit_card_sam_prompt(
531
  input_labels=input_labels,
532
  return_tensors="pt",
533
  )
534
- # `original_sizes` is used after the forward pass for mask post-processing
535
- # and scale calculations. Pull it to CPU before moving `inputs` to the
536
- # model device so downstream code never has to chase device placement.
537
- original_sizes_cpu = inputs["original_sizes"].cpu() if hasattr(inputs["original_sizes"], "cpu") else inputs["original_sizes"]
538
- device = get_sam2_device()
539
- if device != "cpu":
540
- inputs = inputs.to(device)
541
  with torch.inference_mode():
542
  # multimask_output=True gives 3 masks per seed (small / medium / large
543
  # disambiguation of the prompt). Empirically this matters for card
@@ -549,13 +542,13 @@ def detect_credit_card_sam_prompt(
549
 
550
  # Score masks in the scaled 1024-space. Only the single winner is
551
  # upscaled to full resolution afterward, which avoids O(N) 12 MP resizes.
552
- scaled_h = int(original_sizes_cpu[0][0].item())
553
- scaled_w = int(original_sizes_cpu[0][1].item())
554
  scaled_area = float(scaled_h * scaled_w)
555
 
556
  masks_list = processor.post_process_masks(
557
  outputs.pred_masks.cpu(),
558
- original_sizes_cpu,
559
  mask_threshold=0.0,
560
  )
561
  masks_tensor = masks_list[0] # (num_prompts, num_candidates, H_s, W_s)
 
26
  get_quad_dimensions,
27
  order_corners,
28
  )
29
+ from .sam_backend import INFERENCE_MAX_SIDE as PROMPT_INFERENCE_MAX_SIDE, get_sam2
30
 
31
  # HF Hub model id — tiny, small, base-plus, large
32
  SAM2_MODEL_ID = "facebook/sam2.1-hiera-small"
 
531
  input_labels=input_labels,
532
  return_tensors="pt",
533
  )
 
 
 
 
 
 
 
534
  with torch.inference_mode():
535
  # multimask_output=True gives 3 masks per seed (small / medium / large
536
  # disambiguation of the prompt). Empirically this matters for card
 
542
 
543
  # Score masks in the scaled 1024-space. Only the single winner is
544
  # upscaled to full resolution afterward, which avoids O(N) 12 MP resizes.
545
+ scaled_h = inputs["original_sizes"][0][0].item()
546
+ scaled_w = inputs["original_sizes"][0][1].item()
547
  scaled_area = float(scaled_h * scaled_w)
548
 
549
  masks_list = processor.post_process_masks(
550
  outputs.pred_masks.cpu(),
551
+ inputs["original_sizes"],
552
  mask_threshold=0.0,
553
  )
554
  masks_tensor = masks_list[0] # (num_prompts, num_candidates, H_s, W_s)
src/sam_hand_segmentation.py CHANGED
@@ -22,7 +22,7 @@ from typing import List, Optional, Tuple
22
  import cv2
23
  import numpy as np
24
 
25
- from .sam_backend import INFERENCE_MAX_SIDE, get_sam2, get_sam2_device
26
 
27
 
28
  def _downscale(image_bgr: np.ndarray) -> Tuple[np.ndarray, float]:
@@ -87,9 +87,6 @@ def segment_hand_sam(
87
  input_labels=[[prompt_labels]],
88
  return_tensors="pt",
89
  )
90
- device = get_sam2_device()
91
- if device != "cpu":
92
- inputs = inputs.to(device)
93
  with torch.inference_mode():
94
  outputs = model(**inputs, multimask_output=True)
95
 
 
22
  import cv2
23
  import numpy as np
24
 
25
+ from .sam_backend import INFERENCE_MAX_SIDE, get_sam2
26
 
27
 
28
  def _downscale(image_bgr: np.ndarray) -> Tuple[np.ndarray, float]:
 
87
  input_labels=[[prompt_labels]],
88
  return_tensors="pt",
89
  )
 
 
 
90
  with torch.inference_mode():
91
  outputs = model(**inputs, multimask_output=True)
92