RuslanKain/sorting-searching-recognized-gestures
0
1"""2╔══════════════════════════════════════════════════════════════════════════════╗3║ Models: scoring.py ║4║ Scoring system for the Gesture Confidence Ranker ║5╚══════════════════════════════════════════════════════════════════════════════╝6 7This module contains:8• GestureScorer - Computes quality scores for recognized gestures9 10📚 WHY A SEPARATE SCORING CLASS?11 12 In procedural code, you'd write a function mixed in with everything else.13 With OOP, we create a dedicated class that:14 15 1. ENCAPSULATES the scoring formula and its parameters16 2. Has a clear interface (apply_scores, clear_scores, compute_score)17 3. Is easy to test independently18 4. Can be extended with new formulas without changing existing code19 20💡 DESIGN PATTERN: Single Responsibility Principle21 22 GestureScorer has ONE job: compute quality scores.23 - It does NOT sort (that's the algorithm's job).24 - It does NOT display (that's the renderer's job).25 - It does NOT manage the list (that's ImageList's job).26 27 Each class does one thing well!28"""29 30from typing import List31 32from .gesture import GestureImage, GestureRanking33 34 35# ==============================================================================36# CLASS: GestureScorer37# ==============================================================================38 39class GestureScorer:40 """41 Computes quality scores for gesture recognition results.42 43 The quality score measures how well an AI model recognized a gesture,44 taking into account both confidence and gesture complexity.45 46 ┌─────────────────────────────────────────────────────────────────────────┐47 │ 📊 THE FORMULA │48 │ │49 │ quality_score = confidence × 100 × complexity ^ complexity_weight │50 │ × confidence_weight │51 │ │52 │ Where: │53 │ • confidence (0.0–1.0): How sure the AI was about its prediction │54 │ • complexity (1.0–2.0): How hard the gesture is to recognize │55 │ • confidence_weight: User-tunable importance of confidence │56 │ • complexity_weight: User-tunable importance of complexity │57 │ │58 │ Example: │59 │ ✊ fist with 90% confidence: 0.90 × 100 × 1.0¹·⁰ × 1.0 = 90.0 │60 │ 👌 ok with 80% confidence: 0.80 × 100 × 1.7¹·⁰ × 1.0 = 136.0 │61 │ → OK sign ranks HIGHER because recognizing it is harder! │62 │ │63 │ 💡 This is like the Scholarship Shortlist problem (#4): │64 │ Instead of GPA + volunteer_hours + essay_score, │65 │ we use confidence × complexity to produce a final score. │66 └─────────────────────────────────────────────────────────────────────────┘67 68 ┌─────────────────────────────────────────────────────────────────────────┐69 │ 📚 CONCEPT: Class Methods vs Static Methods vs Instance Methods │70 │ │71 │ This class uses @classmethod (no instance needed): │72 │ GestureScorer.compute_score(gesture) # Called on the CLASS │73 │ │74 │ vs. Instance method (needs an object): │75 │ scorer = GestureScorer() │76 │ scorer.compute_score(gesture) # Called on an OBJECT │77 │ │78 │ We use @classmethod because GestureScorer doesn't need to store │79 │ any per-instance data. It's a utility class with shared logic. │80 └─────────────────────────────────────────────────────────────────────────┘81 """82 83 # -------------------------------------------------------------------------84 # Class Constants: Default weights for the scoring formula85 # -------------------------------------------------------------------------86 DEFAULT_CONFIDENCE_WEIGHT = 1.087 DEFAULT_COMPLEXITY_WEIGHT = 1.088 89 # -------------------------------------------------------------------------90 # Scoring Methods91 # -------------------------------------------------------------------------92 93 @classmethod94 def compute_score(95 cls,96 gesture: GestureImage,97 confidence_weight: float = DEFAULT_CONFIDENCE_WEIGHT,98 complexity_weight: float = DEFAULT_COMPLEXITY_WEIGHT,99 ) -> float:100 """101 Compute the quality score for a single gesture.102 103 Args:104 gesture: The GestureImage to score105 confidence_weight: How much to weight confidence (default 1.0)106 complexity_weight: How much to weight complexity (default 1.0)107 108 Returns:109 The computed quality score (higher = better recognition)110 111 Example:112 >>> fist = GestureImage.create_from_prediction("fist", 1, confidence=0.9)113 >>> GestureScorer.compute_score(fist)114 90.0115 """116 complexity = GestureRanking.get_complexity(gesture.gesture)117 score = (118 gesture.confidence119 * 100120 * (complexity ** complexity_weight)121 * confidence_weight122 )123 return round(score, 2)124 125 @classmethod126 def apply_scores(127 cls,128 images: List[GestureImage],129 confidence_weight: float = DEFAULT_CONFIDENCE_WEIGHT,130 complexity_weight: float = DEFAULT_COMPLEXITY_WEIGHT,131 ) -> None:132 """133 Compute and apply quality scores to a list of gestures.134 135 This sets each gesture's _sort_value to the NEGATED score,136 so that sorting in ascending order produces DESCENDING score order.137 (Highest quality score appears first!)138 139 ┌─────────────────────────────────────────────────────────────────────┐140 │ 💡 WHY NEGATE THE SCORE? │141 │ │142 │ Our sorting algorithms sort in ASCENDING order (small → large). │143 │ But we want the HIGHEST score at the top of the ranked list. │144 │ │145 │ Solution: negate the score before sorting! │146 │ Score 150.0 → sort_value = -150.0 (comes FIRST in ascending) │147 │ Score 90.0 → sort_value = -90.0 (comes SECOND) │148 │ Score 50.0 → sort_value = -50.0 (comes LAST) │149 │ │150 │ This way, the existing algorithms work correctly without │151 │ needing any modifications. Open/Closed Principle in action! │152 └─────────────────────────────────────────────────────────────────────┘153 154 Args:155 images: List of GestureImage objects to score156 confidence_weight: How much to weight confidence157 complexity_weight: How much to weight complexity158 """159 for gesture in images:160 score = cls.compute_score(gesture, confidence_weight, complexity_weight)161 # Negate so ascending sort → descending score order162 gesture._sort_value = -score163 164 @classmethod165 def clear_scores(cls, images: List[GestureImage]) -> None:166 """167 Remove custom scores from all gestures, reverting to rank-based sorting.168 169 After calling this, the gestures will sort by their original170 gesture rank (fist < peace < ok < call, etc.).171 172 Args:173 images: List of GestureImage objects to clear scores from174 """175 for gesture in images:176 gesture._sort_value = None177 178 @classmethod179 def get_score(cls, gesture: GestureImage) -> float:180 """181 Get the positive (non-negated) score for display purposes.182 183 The _sort_value is stored as a negative number (for descending sort),184 but users should see the positive quality score.185 186 Args:187 gesture: The GestureImage to get the display score for188 189 Returns:190 The positive quality score, or 0.0 if no score is set191 """192 if gesture._sort_value is not None:193 return -gesture._sort_value # Negate back to positive194 return 0.0195 196 @classmethod197 def get_formula_description(198 cls,199 confidence_weight: float = DEFAULT_CONFIDENCE_WEIGHT,200 complexity_weight: float = DEFAULT_COMPLEXITY_WEIGHT,201 ) -> str:202 """203 Get a human-readable description of the current scoring formula.204 205 Returns:206 A string explaining the formula with the current weight values.207 208 Example:209 >>> GestureScorer.get_formula_description(1.0, 1.5)210 'quality = confidence × 100 × complexity^1.5 × 1.0'211 """212 return (213 f"quality = confidence × 100 "214 f"× complexity^{complexity_weight:.1f} "215 f"× {confidence_weight:.1f}"216 )217 