Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- measure_finger.py +8 -5
- script/validate_sam_card.py +4 -4
- src/sam_card_detection.py +35 -18
measure_finger.py
CHANGED
|
@@ -368,6 +368,10 @@ def _overlay_card_seeds(
|
|
| 368 |
precise-rotation) frame; pass ``rotation_matrix`` to align with an image
|
| 369 |
that had the finger rotation applied.
|
| 370 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
if not seed_debug:
|
| 372 |
return image
|
| 373 |
from src.geometry import transform_points_rotation
|
|
@@ -501,13 +505,13 @@ def _sam_card_detect(
|
|
| 501 |
hand_mask = hand_data.get("mask")
|
| 502 |
landmarks = hand_data.get("landmarks")
|
| 503 |
|
| 504 |
-
if hand_mask is None or landmarks is None or len(landmarks) <=
|
| 505 |
return None
|
| 506 |
|
| 507 |
-
|
| 508 |
-
|
| 509 |
|
| 510 |
-
seed_info = suggest_card_seeds(hand_mask, image_canonical.shape[:2],
|
| 511 |
seeds = seed_info["kept"]
|
| 512 |
dropped_seeds = seed_info["dropped"]
|
| 513 |
if not seeds:
|
|
@@ -530,7 +534,6 @@ def _sam_card_detect(
|
|
| 530 |
# Stash seed geometry so the final result PNG can visualize what was
|
| 531 |
# prompted into SAM, even when card detection fails.
|
| 532 |
seed_debug = {
|
| 533 |
-
"anchor": anchor_xy,
|
| 534 |
"seeds": list(seeds),
|
| 535 |
"dropped": list(dropped_seeds),
|
| 536 |
"negatives": list(negatives),
|
|
|
|
| 368 |
precise-rotation) frame; pass ``rotation_matrix`` to align with an image
|
| 369 |
that had the finger rotation applied.
|
| 370 |
"""
|
| 371 |
+
|
| 372 |
+
# temprary bypass by Feng
|
| 373 |
+
return image
|
| 374 |
+
|
| 375 |
if not seed_debug:
|
| 376 |
return image
|
| 377 |
from src.geometry import transform_points_rotation
|
|
|
|
| 505 |
hand_mask = hand_data.get("mask")
|
| 506 |
landmarks = hand_data.get("landmarks")
|
| 507 |
|
| 508 |
+
if hand_mask is None or landmarks is None or len(landmarks) <= 9:
|
| 509 |
return None
|
| 510 |
|
| 511 |
+
middle_mcp = landmarks[9, :2]
|
| 512 |
+
y_limit = int(round(middle_mcp[1]))
|
| 513 |
|
| 514 |
+
seed_info = suggest_card_seeds(hand_mask, image_canonical.shape[:2], y_limit)
|
| 515 |
seeds = seed_info["kept"]
|
| 516 |
dropped_seeds = seed_info["dropped"]
|
| 517 |
if not seeds:
|
|
|
|
| 534 |
# Stash seed geometry so the final result PNG can visualize what was
|
| 535 |
# prompted into SAM, even when card detection fails.
|
| 536 |
seed_debug = {
|
|
|
|
| 537 |
"seeds": list(seeds),
|
| 538 |
"dropped": list(dropped_seeds),
|
| 539 |
"negatives": list(negatives),
|
script/validate_sam_card.py
CHANGED
|
@@ -78,11 +78,11 @@ def run_one(img_path: Path) -> dict:
|
|
| 78 |
if hand_data is not None:
|
| 79 |
prompt_debug = OUT_DIR / img_path.stem / "sam_card_prompt"
|
| 80 |
landmarks = hand_data.get("landmarks")
|
| 81 |
-
if landmarks is None or len(landmarks) <=
|
| 82 |
return rec
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
seed_info = suggest_card_seeds(hand_data["mask"], canonical.shape[:2],
|
| 86 |
seeds = seed_info["kept"]
|
| 87 |
rec["prompt_n_seeds"] = len(seeds)
|
| 88 |
negs = _negatives_from_landmarks(hand_data["landmarks"])
|
|
|
|
| 78 |
if hand_data is not None:
|
| 79 |
prompt_debug = OUT_DIR / img_path.stem / "sam_card_prompt"
|
| 80 |
landmarks = hand_data.get("landmarks")
|
| 81 |
+
if landmarks is None or len(landmarks) <= 9:
|
| 82 |
return rec
|
| 83 |
+
middle_mcp = landmarks[9, :2]
|
| 84 |
+
y_limit = int(round(middle_mcp[1]))
|
| 85 |
+
seed_info = suggest_card_seeds(hand_data["mask"], canonical.shape[:2], y_limit)
|
| 86 |
seeds = seed_info["kept"]
|
| 87 |
rec["prompt_n_seeds"] = len(seeds)
|
| 88 |
negs = _negatives_from_landmarks(hand_data["landmarks"])
|
src/sam_card_detection.py
CHANGED
|
@@ -139,33 +139,50 @@ def _score_card_mask(
|
|
| 139 |
def suggest_card_seeds(
|
| 140 |
hand_mask: np.ndarray,
|
| 141 |
image_shape: Tuple[int, int],
|
| 142 |
-
|
| 143 |
) -> Dict[str, List[Tuple[int, int]]]:
|
| 144 |
-
"""
|
| 145 |
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
"""
|
| 156 |
h, w = image_shape
|
| 157 |
mask_bool = hand_mask.astype(bool) if hand_mask.dtype != bool else hand_mask
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
|
|
|
|
|
|
|
|
|
| 163 |
|
|
|
|
| 164 |
candidates: List[Tuple[int, int]] = []
|
| 165 |
-
for
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
|
|
|
| 169 |
|
| 170 |
kept: List[Tuple[int, int]] = []
|
| 171 |
dropped: List[Tuple[int, int]] = []
|
|
|
|
| 139 |
def suggest_card_seeds(
|
| 140 |
hand_mask: np.ndarray,
|
| 141 |
image_shape: Tuple[int, int],
|
| 142 |
+
y_limit: int,
|
| 143 |
) -> Dict[str, List[Tuple[int, int]]]:
|
| 144 |
+
"""Uniform 4x4 grid seeds in the top band of the canonical image.
|
| 145 |
|
| 146 |
+
In canonical orientation the fingertips point up, so the middle-finger
|
| 147 |
+
MCP is the lowest landmark of the middle finger. Users overwhelmingly
|
| 148 |
+
place the card in the band between the top of the frame and the MCP
|
| 149 |
+
row (beside or above the fingers), so sampling that band catches the
|
| 150 |
+
card with a handful of prompts. The grid excludes a 10% border padding
|
| 151 |
+
on all four sides of the band and drops any seed that lands on the
|
| 152 |
+
hand mask.
|
| 153 |
|
| 154 |
+
Args:
|
| 155 |
+
hand_mask: SAM hand mask (HxW, bool or uint8).
|
| 156 |
+
image_shape: (H, W) of the canonical image.
|
| 157 |
+
y_limit: Y coordinate of the middle-finger MCP; the grid spans
|
| 158 |
+
[0.1·H, y_limit] vertically.
|
| 159 |
+
|
| 160 |
+
Returns:
|
| 161 |
+
Dict with two lists:
|
| 162 |
+
- "kept": seeds that passed the hand-mask filter (sent to SAM).
|
| 163 |
+
- "dropped": seeds whose (x, y) landed inside the hand mask and
|
| 164 |
+
were filtered out. Retained purely for debug visualization.
|
| 165 |
"""
|
| 166 |
h, w = image_shape
|
| 167 |
mask_bool = hand_mask.astype(bool) if hand_mask.dtype != bool else hand_mask
|
| 168 |
|
| 169 |
+
x_min = 0.1 * w
|
| 170 |
+
x_max = 0.9 * w
|
| 171 |
+
y_min = 0.1 * h
|
| 172 |
+
y_max = float(y_limit)
|
| 173 |
+
# Guard against degenerate bands (e.g., MCP above the 10% top padding).
|
| 174 |
+
if y_max <= y_min:
|
| 175 |
+
y_max = y_min + 1.0
|
| 176 |
|
| 177 |
+
n = 4
|
| 178 |
candidates: List[Tuple[int, int]] = []
|
| 179 |
+
for iy in range(n):
|
| 180 |
+
fy = (iy + 0.5) / n
|
| 181 |
+
py = int(round(y_min + fy * (y_max - y_min)))
|
| 182 |
+
for ix in range(n):
|
| 183 |
+
fx = (ix + 0.5) / n
|
| 184 |
+
px = int(round(x_min + fx * (x_max - x_min)))
|
| 185 |
+
candidates.append((px, py))
|
| 186 |
|
| 187 |
kept: List[Tuple[int, int]] = []
|
| 188 |
dropped: List[Tuple[int, int]] = []
|