Created
October 25, 2025 22:51
-
-
Save pbasov/6148845dcfa43e0d66941b1225fb3d30 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| def compare_batch(self, proxy_image_b64: str, candidate_images_b64: List[str]) -> List[Optional[int]]: | |
| """ | |
| Compare proxy to multiple candidates in one API call. | |
| Returns list of scores corresponding to candidate_images_b64. | |
| """ | |
| num_candidates = len(candidate_images_b64) | |
| prompt = f"""You are comparing Magic: The Gathering card artwork. I will show you: | |
| 1. First image: The PROXY card (reference) | |
| 2. Next {num_candidates} images: CANDIDATE printings (numbered 1-{num_candidates}) | |
| Your task: Rate how similar each CANDIDATE's artwork is to the PROXY artwork. | |
| Focus ONLY on the central artwork/illustration. Completely ignore borders, frames, card text, and other non-artwork elements. | |
| Scoring guide: | |
| - 100 = Identical artwork (same illustration, same artist, same composition) | |
| - 90-99 = Same artwork with very minor variations (slight color/cropping differences) | |
| - 70-89 = Similar artwork but noticeable differences (extended art, alternate version) | |
| - 40-69 = Different artwork but similar subject/theme | |
| - 0-39 = Completely different artwork | |
| Respond with ONLY the scores as a comma-separated list. Example: 100, 95, 30, 100, 20""" | |
| # Build content list: prompt + proxy + all candidates | |
| content = [{"type": "text", "text": prompt}] | |
| # Add proxy image | |
| content.append({ | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/jpeg;base64,{proxy_image_b64}"} | |
| }) | |
| # Add all candidate images | |
| for candidate_b64 in candidate_images_b64: | |
| content.append({ | |
| "type": "image_url", | |
| "image_url": {"url": f"data:image/jpeg;base64,{candidate_b64}"} | |
| }) | |
| payload = { | |
| "model": "nm-testing/Qwen3-VL-8B-Instruct-NVFP4", | |
| "messages": [{"role": "user", "content": content}], | |
| "max_tokens": 100, # Need more tokens for multiple scores | |
| "temperature": 0.7, | |
| "top_p": 0.8, | |
| "top_k": 20, | |
| "repetition_penalty": 1.0, | |
| "presence_penalty": 1.5 | |
| } | |
| try: | |
| response = requests.post(self.chat_endpoint, json=payload, timeout=60) | |
| if response.status_code != 200: | |
| print(f" ERROR: API returned {response.status_code}") | |
| return [None] * num_candidates | |
| result = response.json() | |
| content = result['choices'][0]['message']['content'].strip() | |
| print(f" Model response: {content[:200]}...") # Debug output | |
| # Parse comma-separated scores | |
| numbers = re.findall(r'\d+', content) | |
| if len(numbers) < num_candidates: | |
| print(f" WARNING: Expected {num_candidates} scores, got {len(numbers)}") | |
| scores = [int(n) for n in numbers[:num_candidates]] # Take first N numbers | |
| scores = [min(100, max(0, s)) for s in scores] # Clamp to 0-100 | |
| # Pad with None if we got fewer scores than expected | |
| while len(scores) < num_candidates: | |
| scores.append(None) | |
| return scores[:num_candidates] | |
| except Exception as e: | |
| print(f" ERROR: {e}") | |
| return [None] * num_candidates |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment