diff --git a/botcity/core/cv2find.py b/botcity/core/cv2find.py index 39f6f98..0417eae 100644 --- a/botcity/core/cv2find.py +++ b/botcity/core/cv2find.py @@ -154,10 +154,15 @@ def locate_all_opencv( # use a generator for API consistency: matchx = matches[1] * step + region[0] # vectorized matchy = matches[0] * step + region[1] - - # Order results before sending back - ordered = sorted( - zip(matchx, matchy), key=lambda p: result[p[1]][p[0]], reverse=True - ) + scores = result[matches] + + # Order results before sending back. Score by the position inside + # `result`, not by the screen position, which includes the region offset. + ordered = [ + (x, y) + for _, x, y in sorted( + zip(scores, matchx, matchy), key=lambda p: p[0], reverse=True + ) + ] for x, y in ordered: yield Box(x, y, needle_width, needle_height) diff --git a/botcity/core/tests/test_cv2find.py b/botcity/core/tests/test_cv2find.py new file mode 100644 index 0000000..204dbaa --- /dev/null +++ b/botcity/core/tests/test_cv2find.py @@ -0,0 +1,65 @@ +import numpy + +from botcity.core import cv2find + + +def _images(): + rng = numpy.random.default_rng(0) + needle = rng.integers(0, 256, (10, 10, 3), dtype=numpy.uint8) + haystack = rng.integers(0, 256, (100, 200, 3), dtype=numpy.uint8) + return needle, haystack + + +def test_locate_all_opencv_with_region_offset_returns_match(): + needle, haystack = _images() + haystack[60:70, 150:160] = needle + + # Search only the right half of the screen, as find(x=100, ...) does. + found = list( + cv2find.locate_all_opencv( + needle, haystack, region=(100, 0, 100, 100), confidence=0.9 + ) + ) + + assert found == [cv2find.Box(150, 60, 10, 10)] + + +def test_locate_all_opencv_with_region_offset_orders_by_score(): + needle, haystack = _images() + # A slightly damaged copy scores lower than the exact copy. + damaged = needle.copy() + damaged[0:3, 0:3] = 255 - damaged[0:3, 0:3] + haystack[10:20, 110:120] = damaged + haystack[60:70, 150:160] = needle + + found = list( + cv2find.locate_all_opencv( + needle, haystack, region=(100, 0, 100, 100), confidence=0.5 + ) + ) + + assert found[0] == cv2find.Box(150, 60, 10, 10) + assert cv2find.Box(110, 10, 10, 10) in found + + +def test_find_with_x_offset_returns_element(tmp_path, monkeypatch): + from PIL import Image + + from botcity.core import DesktopBot + + needle, haystack = _images() + haystack[60:70, 150:160] = needle + needle_path = tmp_path / "needle.png" + Image.fromarray(needle[:, :, ::-1]).save(needle_path) + + bot = DesktopBot() + bot.add_image("needle", str(needle_path)) + monkeypatch.setattr(bot, "_fix_display_size", lambda: (200, 100)) + monkeypatch.setattr( + bot, "get_screenshot", lambda *a, **k: Image.fromarray(haystack[:, :, ::-1]) + ) + monkeypatch.setattr("botcity.core.bot.is_retina", lambda: False) + + ele = bot.find("needle", x=100, matching=0.9, waiting_time=1000) + + assert ele == cv2find.Box(150, 60, 10, 10)