ok-script 0.0.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. autohelper/AutoHelper.py +116 -0
  2. autohelper/__init__.py +0 -0
  3. autohelper/capture/BaseCaptureMethod.py +30 -0
  4. autohelper/capture/HwndWindow.py +138 -0
  5. autohelper/capture/__init__.py +0 -0
  6. autohelper/capture/adb/ADBCaptureMethod.py +29 -0
  7. autohelper/capture/adb/DeviceManager.py +209 -0
  8. autohelper/capture/adb/__init__.py +0 -0
  9. autohelper/capture/adb/bin/__init__.py +0 -0
  10. autohelper/capture/adb/targets.py +61 -0
  11. autohelper/capture/adb/vbox.py +140 -0
  12. autohelper/capture/windows/WindowsGraphicsCaptureMethod.py +228 -0
  13. autohelper/capture/windows/__init__.py +0 -0
  14. autohelper/capture/windows/d3d11.py +74 -0
  15. autohelper/capture/windows/utils.py +149 -0
  16. autohelper/capture/windows/window.py +36 -0
  17. autohelper/color/Color.py +41 -0
  18. autohelper/color/__init__.py +0 -0
  19. autohelper/config/Config.py +79 -0
  20. autohelper/config/InfoDict.py +16 -0
  21. autohelper/config/__init__.py +0 -0
  22. autohelper/feature/Box.py +199 -0
  23. autohelper/feature/Feature.py +20 -0
  24. autohelper/feature/FeatureSet.py +207 -0
  25. autohelper/feature/FindFeature.py +52 -0
  26. autohelper/feature/__init__.py +0 -0
  27. autohelper/gui/App.py +120 -0
  28. autohelper/gui/Communicate.py +22 -0
  29. autohelper/gui/MainWindow.py +69 -0
  30. autohelper/gui/TabTitles.py +56 -0
  31. autohelper/gui/__init__.py +5 -0
  32. autohelper/gui/debug/AspectRatioWidget.py +31 -0
  33. autohelper/gui/debug/DebugTab.py +37 -0
  34. autohelper/gui/debug/FrameWidget.py +125 -0
  35. autohelper/gui/debug/InfoWidget.py +40 -0
  36. autohelper/gui/debug/LoggerWidget.py +74 -0
  37. autohelper/gui/debug/__init__.py +0 -0
  38. autohelper/gui/i18n/__init__.py +0 -0
  39. autohelper/gui/i18n/path.py +3 -0
  40. autohelper/gui/icon/__init__.py +0 -0
  41. autohelper/gui/icon/icon.py +6 -0
  42. autohelper/gui/loading/LoadingWindow.py +142 -0
  43. autohelper/gui/loading/__init__.py +0 -0
  44. autohelper/gui/overlay/OverlayWindow.py +34 -0
  45. autohelper/gui/overlay/__init__.py +0 -0
  46. autohelper/gui/resources.py +485 -0
  47. autohelper/gui/tasks/ConfigItemFactory.py +14 -0
  48. autohelper/gui/tasks/StartButton.py +47 -0
  49. autohelper/gui/tasks/TaskOpButton.py +33 -0
  50. autohelper/gui/tasks/TaskTab.py +121 -0
  51. autohelper/gui/tasks/TooltipTableWidget.py +39 -0
  52. autohelper/gui/tasks/__init__.py +0 -0
  53. autohelper/gui/util/Alert.py +20 -0
  54. autohelper/gui/util/InitWorker.py +17 -0
  55. autohelper/gui/util/__init__.py +0 -0
  56. autohelper/gui/widget/ListTableWidgetItem.py +27 -0
  57. autohelper/gui/widget/NumericTableWidgetItem.py +33 -0
  58. autohelper/gui/widget/RoundCornerContainer.py +59 -0
  59. autohelper/gui/widget/TabWidget.py +30 -0
  60. autohelper/gui/widget/UpdateConfigWidgetItem.py +16 -0
  61. autohelper/gui/widget/YesNonWidgetItem.py +23 -0
  62. autohelper/gui/widget/__init__.py +0 -0
  63. autohelper/interaction/ADBInteraction.py +33 -0
  64. autohelper/interaction/BaseInteraction.py +36 -0
  65. autohelper/interaction/Win32Interaction.py +56 -0
  66. autohelper/interaction/__init__.py +0 -0
  67. autohelper/logging/Logger.py +103 -0
  68. autohelper/logging/__init__.py +0 -0
  69. autohelper/ocr/OCR.py +50 -0
  70. autohelper/ocr/__init__.py +0 -0
  71. autohelper/predict/__init__.py +0 -0
  72. autohelper/predict/predict.py +45 -0
  73. autohelper/rotypes/Windows/Foundation/Collections.py +60 -0
  74. autohelper/rotypes/Windows/Foundation/__init__.py +153 -0
  75. autohelper/rotypes/Windows/Globalization/__init__.py +25 -0
  76. autohelper/rotypes/Windows/Graphics/Capture/__init__.py +110 -0
  77. autohelper/rotypes/Windows/Graphics/DirectX/Direct3D11/__init__.py +41 -0
  78. autohelper/rotypes/Windows/Graphics/DirectX/__init__.py +167 -0
  79. autohelper/rotypes/Windows/Graphics/Imaging/__init__.py +34 -0
  80. autohelper/rotypes/Windows/Graphics/__init__.py +5 -0
  81. autohelper/rotypes/Windows/Media/Ocr/__init__.py +76 -0
  82. autohelper/rotypes/Windows/Media/__init__.py +0 -0
  83. autohelper/rotypes/Windows/Security/Cryptography/__init__.py +18 -0
  84. autohelper/rotypes/Windows/Security/__init__.py +0 -0
  85. autohelper/rotypes/Windows/Storage/Streams/__init__.py +27 -0
  86. autohelper/rotypes/Windows/Storage/__init__.py +0 -0
  87. autohelper/rotypes/Windows/__init__.py +0 -0
  88. autohelper/rotypes/__init__.py +4 -0
  89. autohelper/rotypes/delegate.py +105 -0
  90. autohelper/rotypes/export.py +21 -0
  91. autohelper/rotypes/idldsl.py +272 -0
  92. autohelper/rotypes/inspectable.py +79 -0
  93. autohelper/rotypes/roapi.py +30 -0
  94. autohelper/rotypes/types.py +77 -0
  95. autohelper/rotypes/winstring.py +44 -0
  96. autohelper/save/BlackBarProcessor.py +25 -0
  97. autohelper/save/PostProcessor.py +6 -0
  98. autohelper/save/SaveByInterval.py +30 -0
  99. autohelper/save/SaveByKeyPress.py +27 -0
  100. autohelper/save/SaveMethodBase.py +46 -0
  101. autohelper/save/__init__.py +0 -0
  102. autohelper/scene/FeatureScene.py +14 -0
  103. autohelper/scene/Scene.py +21 -0
  104. autohelper/scene/__init__.py +0 -0
  105. autohelper/stats/StreamStats.py +34 -0
  106. autohelper/stats/__init__.py +0 -0
  107. autohelper/task/BaseTask.py +109 -0
  108. autohelper/task/ExecutorOperation.py +119 -0
  109. autohelper/task/FindFeatureTask.py +6 -0
  110. autohelper/task/TaskExecutor.py +249 -0
  111. autohelper/task/__init__.py +0 -0
  112. autohelper/util/__init__.py +0 -0
  113. autohelper/util/json.py +27 -0
  114. autohelper/util/list.py +2 -0
  115. autohelper/util/path.py +37 -0
  116. autohelper/util/thread.py +12 -0
  117. autohelper/util/win32_process.py +86 -0
  118. autohelper/util/yaml.py +28 -0
  119. ok_script-0.0.7.dist-info/METADATA +64 -0
  120. ok_script-0.0.7.dist-info/RECORD +122 -0
  121. ok_script-0.0.7.dist-info/WHEEL +5 -0
  122. ok_script-0.0.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,79 @@
1
+ from autohelper.util.json import read_json_file, write_json_file
2
+ from autohelper.util.path import get_path_relative_to_exe
3
+
4
+
5
+ class Config:
6
+ def __init__(self, default, folder, name):
7
+ super().__init__()
8
+ self.config_file = get_path_relative_to_exe(folder, f"{name}.json")
9
+ self.config = read_json_file(self.config_file)
10
+ if self.config is None:
11
+ self.config = default
12
+ elif verify_config(self.config, default):
13
+ self.save_file()
14
+
15
+ def save_file(self):
16
+ write_json_file(self.config_file, self.config)
17
+
18
+ def update(self, *args, **kwargs):
19
+ self.config.update(*args, **kwargs)
20
+ self.save_file()
21
+
22
+ def get(self, key, default=None):
23
+ return self.config.get(key, default)
24
+
25
+ def keys(self):
26
+ return self.config.keys()
27
+
28
+ def values(self):
29
+ return self.config.values()
30
+
31
+ def items(self):
32
+ return self.config.items()
33
+
34
+ def pop(self, key, default=None):
35
+ result = self.config.pop(key, default)
36
+ self.save_file()
37
+ return result
38
+
39
+ def popitem(self):
40
+ result = self.config.popitem()
41
+ self.save_file()
42
+ return result
43
+
44
+ def setdefault(self, key, default=None):
45
+ result = self.config.setdefault(key, default)
46
+ self.save_file()
47
+ return result
48
+
49
+ def clear(self):
50
+ self.config.clear()
51
+ self.save_file()
52
+
53
+ def __len__(self):
54
+ return len(self.config)
55
+
56
+ def __setitem__(self, key, value):
57
+ self.config[key] = value
58
+ self.save_file()
59
+
60
+ def __getitem__(self, key):
61
+ return self.config[key]
62
+
63
+
64
+ def verify_config(config, default_config):
65
+ modified = False
66
+
67
+ # Remove entries that do not exist in default_config
68
+ for key in list(config.keys()):
69
+ if key not in default_config:
70
+ del config[key]
71
+ modified = True
72
+
73
+ # Check entries in default_config
74
+ for key, default_value in default_config.items():
75
+ if key not in config or type(config[key]) != type(default_value):
76
+ config[key] = default_value
77
+ modified = True
78
+
79
+ return not modified
@@ -0,0 +1,16 @@
1
+ from autohelper.gui.Communicate import communicate
2
+
3
+
4
+ class InfoDict(dict):
5
+
6
+ def __delitem__(self, key):
7
+ super().__delitem__(key)
8
+ communicate.task_info.emit()
9
+
10
+ def clear(self):
11
+ super().clear()
12
+ communicate.task_info.emit()
13
+
14
+ def __setitem__(self, key, value):
15
+ super().__setitem__(key, value)
16
+ communicate.task_info.emit()
File without changes
@@ -0,0 +1,199 @@
1
+ import math
2
+ import random
3
+ import re
4
+
5
+
6
+ class Box:
7
+ def __init__(self, x: int, y: int, width: int, height: int, confidence: float = 1, name=None) -> None:
8
+ self.name = name
9
+ self.x = x
10
+ self.y = y
11
+ self.width = width
12
+ self.height = height
13
+ self.confidence = confidence
14
+
15
+ def __eq__(self, other):
16
+ if not isinstance(other, Box):
17
+ # Don't attempt to compare against unrelated types
18
+ return NotImplemented
19
+
20
+ return (self.x == other.x and
21
+ self.y == other.y and
22
+ self.width == other.width and
23
+ self.height == other.height and
24
+ self.confidence == other.confidence and
25
+ self.name == other.name)
26
+
27
+ def __repr__(self):
28
+ return self.name
29
+
30
+ def __str__(self) -> str:
31
+ if self.name is not None:
32
+ return f"Box(name='{self.name}', x={self.x}, y={self.y}, width={self.width}, height={self.height}, confidence={round(self.confidence * 100)})"
33
+ return f"Box(x={self.x}, y={self.y}, width={self.width}, height={self.height}, confidence={round(self.confidence * 100)})"
34
+
35
+ def closest_distance(self, other):
36
+ # Calculate the sides of the boxes
37
+ left1, right1 = self.x, self.x + self.width
38
+ top1, bottom1 = self.y, self.y + self.height
39
+ left2, right2 = other.x, other.x + other.width
40
+ top2, bottom2 = other.y, other.y + other.height
41
+
42
+ # Horizontal distance
43
+ if right1 < left2:
44
+ horizontal_distance = left2 - right1
45
+ elif right2 < left1:
46
+ horizontal_distance = left1 - right2
47
+ else:
48
+ horizontal_distance = 0
49
+
50
+ # Vertical distance
51
+ if bottom1 < top2:
52
+ vertical_distance = top2 - bottom1
53
+ elif bottom2 < top1:
54
+ vertical_distance = top1 - bottom2
55
+ else:
56
+ vertical_distance = 0
57
+
58
+ # If boxes overlap or touch, the closest distance is 0
59
+ if horizontal_distance == 0 and vertical_distance == 0:
60
+ return 0
61
+
62
+ # If boxes are diagonally aligned, calculate diagonal distance
63
+ return math.sqrt(horizontal_distance ** 2 + vertical_distance ** 2)
64
+
65
+ def relative_with_variance(self, relative_x=0.5, relative_y=0.5):
66
+ # Calculate the center of the box
67
+ center_x = self.x + self.width * relative_x
68
+ center_y = self.y + self.height * relative_y
69
+
70
+ # Add random variance
71
+ variance = random.uniform(0, 0.1)
72
+ center_x_with_variance = center_x + variance
73
+ center_y_with_variance = center_y + variance
74
+ return round(center_x_with_variance), round(center_y_with_variance)
75
+
76
+ def copy(self, x_offset=0, y_offset=0, width_offset=0, height_offset=0, name=None):
77
+ return Box(self.x + x_offset, self.y + y_offset, self.width + width_offset, self.height + height_offset,
78
+ self.confidence, name or self.name)
79
+
80
+ def center(self):
81
+ return self.x + self.width / 2, self.y + self.height / 2
82
+
83
+ def find_closest_box(self, direction: str, boxes: list):
84
+ orig_center_x, orig_center_y = self.center()
85
+
86
+ def distance_criteria(box):
87
+ # Calculate center points for comparison
88
+ box_center_x, box_center_y = box.center()
89
+
90
+ dx = box_center_x - orig_center_x
91
+ dy = box_center_y - orig_center_y
92
+ distance = math.sqrt(dx ** 2 + dy ** 2)
93
+ if box == self:
94
+ return float('inf')
95
+ elif direction == 'up' and self.y - (box.y + box.height / 2) >= 0:
96
+ return distance
97
+ elif direction == 'down' and box.y - (self.y + self.height / 2) >= 0:
98
+ return distance
99
+ elif direction == 'left' and self.x - (box.x + box.width / 2) >= 0:
100
+ return distance
101
+ elif direction == 'right' and box.x - (self.x + self.width / 2) >= 0:
102
+ return distance
103
+ else:
104
+ return float('inf')
105
+
106
+ filtered_boxes = sorted(boxes, key=distance_criteria)
107
+ # Removed debug print statement for cleanliness
108
+
109
+ for box in filtered_boxes:
110
+ if distance_criteria(box) != float('inf'):
111
+ return box
112
+ return None
113
+
114
+
115
+ def sort_boxes(boxes):
116
+ return sorted(boxes, key=lambda box: (box.y, box.x if abs(box.y - boxes[0].y) < 6 else 0))
117
+
118
+
119
+ def find_box_by_name(boxes, names) -> Box:
120
+ if isinstance(names, (str, re.Pattern)):
121
+ names = [names]
122
+
123
+ result = None
124
+ priority = len(names)
125
+
126
+ for box in boxes:
127
+ for i, name in enumerate(names):
128
+ if (isinstance(name, str) and name == box.name) or (
129
+ isinstance(name, re.Pattern) and re.search(name, box.name)):
130
+ if i < priority:
131
+ priority = i
132
+ result = box
133
+ if i == 0:
134
+ break
135
+
136
+ return result
137
+
138
+
139
+ def find_boxes_within_boundary(boxes, boundary_box):
140
+ """
141
+ Find all boxes that are entirely within the specified boundary box.
142
+
143
+ Parameters:
144
+ - boxes (list[Box]): List of Box objects to check.
145
+ - boundary_box (Box): The boundary Box object.
146
+
147
+ Returns:
148
+ - list[Box]: Boxes found within the boundary box.
149
+ """
150
+ within_boundary = []
151
+
152
+ for box in boxes:
153
+ # Check if box is within boundary_box
154
+ if (box.x >= boundary_box.x and
155
+ box.y >= boundary_box.y and
156
+ box.x + box.width <= boundary_box.x + boundary_box.width and
157
+ box.y + box.height <= boundary_box.y + boundary_box.height):
158
+ within_boundary.append(box)
159
+
160
+ return within_boundary
161
+
162
+
163
+ def crop_image(image, box=None):
164
+ if box is not None:
165
+ if (box.x >= 0 and box.y >= 0 and
166
+ box.x + box.width <= image.shape[1] and # image.shape[1] is the width of the image
167
+ box.y + box.height <= image.shape[0]): # image.shape[0] is the height of the image
168
+
169
+ # Extract the region of interest (ROI) using slicing
170
+
171
+ return image[box.y:box.y + box.height, box.x:box.x + box.width, :3]
172
+ else:
173
+ # Return some error value or raise an exception
174
+ # For example, return 0 or None
175
+ return image # or None, or raise an exception
176
+ else:
177
+ return image
178
+
179
+
180
+ def find_boxes_by_name(boxes, names) -> list[Box]:
181
+ # Ensure names is always a list
182
+ if isinstance(names, (str, re.Pattern)):
183
+ names = [names]
184
+
185
+ result = []
186
+
187
+ for box in boxes:
188
+ # Flag to track if the box has been matched and should be added
189
+ matched = False
190
+ for name in names:
191
+ if matched:
192
+ break # Stop checking names if we've already matched this box
193
+ if (isinstance(name, str) and name == box.name) or (
194
+ isinstance(name, re.Pattern) and re.search(name, box.name)):
195
+ matched = True
196
+ if matched:
197
+ result.append(box)
198
+
199
+ return result
@@ -0,0 +1,20 @@
1
+ import numpy as np
2
+
3
+
4
+ class Feature:
5
+ def __init__(self, mat: np.ndarray, x: int, y: int, width: int, height: int) -> None:
6
+ """
7
+ Initialize a Feature with an image (Mat) and its bounding box coordinates.
8
+
9
+ Args:
10
+ mat (np.ndarray): The OpenCV Mat object representing the image.
11
+ x (int): The x-coordinate of the top-left corner of the bounding box.
12
+ y (int): The y-coordinate of the top-left corner of the bounding box.
13
+ width (int): The width of the bounding box.
14
+ height (int): The height of the bounding box.
15
+ """
16
+ self.mat = mat
17
+ self.x = x
18
+ self.y = y
19
+ self.width = width
20
+ self.height = height
@@ -0,0 +1,207 @@
1
+ import json
2
+ import os
3
+ import sys
4
+ from typing import Dict
5
+ from typing import List
6
+
7
+ import cv2
8
+ import numpy as np
9
+
10
+ from autohelper.feature.Box import Box, sort_boxes
11
+ from autohelper.feature.Feature import Feature
12
+ from autohelper.gui.Communicate import communicate
13
+ from autohelper.logging.Logger import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class FeatureSet:
19
+ # Category_name to OpenCV Mat
20
+ featureDict: Dict[str, Feature] = {}
21
+
22
+ def __init__(self, coco_folder: str, default_horizontal_variance=0,
23
+ default_vertical_variance=0, default_threshold=0.95) -> None:
24
+ """
25
+ Initialize the FeatureSet by loading images and annotations from a COCO dataset.
26
+
27
+ Args:
28
+ coco_folder (str): Directory containing the JSON file and images.
29
+ width (int): Scale images to this width.
30
+ height (int): Scale images to this height.
31
+ """
32
+ self.coco_folder = coco_folder
33
+
34
+ # Process images and annotations
35
+ self.width = 0
36
+ self.height = 0
37
+ if default_threshold == 0:
38
+ default_threshold = 0.95
39
+ self.default_threshold = default_threshold
40
+ self.default_horizontal_variance = default_horizontal_variance
41
+ self.default_vertical_variance = default_vertical_variance
42
+
43
+ def check_size(self, frame):
44
+ height, width = frame.shape[:2]
45
+ if self.width != width or self.height != height and height > 0 and width > 0:
46
+ logger.info(f"FeatureSet: Width and height changed from {self.width}x{self.height} to {width}x{height}")
47
+ self.width = width
48
+ self.height = height
49
+ self.process_data()
50
+
51
+ def process_data(self) -> None:
52
+ """
53
+ Process the images and annotations from the COCO dataset.
54
+
55
+ Args:
56
+ width (int): Target width for scaling images.
57
+ height (int): Target height for scaling images.
58
+ """
59
+ self.featureDict.clear()
60
+ json_path = f'{self.coco_folder}/_annotations.coco.json'
61
+ with open(json_path, 'r') as file:
62
+ data = json.load(file)
63
+
64
+ # Create a map from image ID to file name
65
+ image_map = {image['id']: image['file_name'] for image in data['images']}
66
+
67
+ # Create a map from category ID to category name
68
+ category_map = {category['id']: category['name'] for category in data['categories']}
69
+
70
+ for annotation in data['annotations']:
71
+ image_id = annotation['image_id']
72
+ category_id = annotation['category_id']
73
+ bbox = annotation['bbox']
74
+
75
+ # Load and scale the image
76
+ image_path = f'{self.coco_folder}/{image_map[image_id]}'
77
+ image = cv2.imread(image_path)
78
+ original_width, _ = image.shape[:2]
79
+ if image is None:
80
+ logger.error(f'Could not read image {image_path}')
81
+ continue
82
+ scale_x, scale_y = self.width / image.shape[1], self.height / image.shape[0]
83
+ image = cv2.resize(image, (self.width, self.height))
84
+
85
+ # Calculate the scaled bounding box
86
+ x, y, w, h = bbox
87
+ x, y, w, h = round(x * scale_x), round(y * scale_y), round(w * scale_x), round(h * scale_y)
88
+
89
+ # Crop the image to the bounding box
90
+ cropped_image = image[y:y + h, x:x + w, :3]
91
+
92
+ # Store in featureDict using the category name
93
+ category_name = category_map[category_id]
94
+ logger.debug(
95
+ f"loaded {category_name} resized width {self.width} / original_width:{original_width},scale_x:{scale_x},scale_y:{scale_y}")
96
+ if category_name in self.featureDict:
97
+ raise ValueError(f"Multiple boxes found for category {category_name}")
98
+ self.featureDict[category_name] = Feature(cropped_image, x, y, w, h)
99
+
100
+ def save_images(self, target_folder: str) -> None:
101
+ """
102
+ Save all images in the featureDict to the specified folder.
103
+
104
+ Args:
105
+ target_folder (str): The folder where images will be saved.
106
+ """
107
+ # Ensure the target folder exists
108
+ os.makedirs(target_folder, exist_ok=True)
109
+
110
+ # Iterate through the featureDict and save each image
111
+ for category_name, image in self.featureDict.items():
112
+ # Construct the filename
113
+ file_name = f"{category_name}.jpg"
114
+ file_path = os.path.join(target_folder, file_name)
115
+
116
+ # Save the image
117
+ cv2.imwrite(file_path, image.mat)
118
+ print(f"Saved {file_path}")
119
+
120
+ def find_one(self, mat: np.ndarray, category_name: str, horizontal_variance: float = 0,
121
+ vertical_variance: float = 0,
122
+ threshold=0) -> Box:
123
+ boxes = self.find_feature(mat, category_name, horizontal_variance=horizontal_variance,
124
+ vertical_variance=vertical_variance, threshold=threshold)
125
+ if len(boxes) > 1:
126
+ logger.warning(f"find_one:found too many {len(boxes)} return first", file=sys.stderr)
127
+ if len(boxes) >= 1:
128
+ return boxes[0]
129
+
130
+ def find_feature(self, mat: np.ndarray, category_name: str, horizontal_variance: float = 0,
131
+ vertical_variance: float = 0, threshold: float = 0) -> List[Box]:
132
+ """
133
+ Find a feature within a given variance.
134
+
135
+ Args:
136
+ mat (np.ndarray): The image in which to find the feature.
137
+ category_name (str): The category name of the feature to find.
138
+ horizontal_variance (float): Allowed horizontal variance as a percentage of width.
139
+ vertical_variance (float): Allowed vertical variance as a percentage of height.
140
+ threshold: Allowed confidence threshold for the feature
141
+
142
+ Returns:
143
+ List[Box]: A list of boxes where the feature is found.
144
+ """
145
+ self.check_size(mat)
146
+
147
+ if threshold == 0:
148
+ threshold = self.default_threshold
149
+ if horizontal_variance == 0:
150
+ horizontal_variance = self.default_horizontal_variance
151
+ if vertical_variance == 0:
152
+ vertical_variance = self.default_vertical_variance
153
+ if category_name not in self.featureDict:
154
+ raise ValueError(f"FeatureSet: {category_name} not found in featureDict")
155
+
156
+ feature = self.featureDict[category_name]
157
+ feature_width, feature_height = feature.width, feature.height
158
+
159
+ # Define search area using variance
160
+ search_x1 = max(0, round(feature.x - self.width * horizontal_variance))
161
+ search_y1 = max(0, round(feature.y - self.height * vertical_variance))
162
+ search_x2 = min(self.width, round(feature.x + feature_width + self.width * horizontal_variance))
163
+ search_y2 = min(self.height, round(feature.y + feature_height + self.height * vertical_variance))
164
+
165
+ search_area = mat[search_y1:search_y2, search_x1:search_x2, :3]
166
+ # Crop the search area from the image
167
+ # print(f"search_area: ({self.width,self.height})({search_x1},{search_x2},{search_y1},{search_y2}) ({get_depth(search_area),get_depth(feature.mat)})")
168
+
169
+ # cv2.imwrite("images/test.jpg", search_area)
170
+
171
+ # Template matchingTM_CCORR_NORMED
172
+ # result = cv2.matchTemplate(search_area, feature.mat, cv2.TM_CCOEFF_NORMED)
173
+ result = cv2.matchTemplate(search_area, feature.mat, cv2.TM_CCOEFF_NORMED)
174
+
175
+ # Define a threshold for acceptable matches
176
+ locations = filter_and_sort_matches(result, threshold, feature_width, feature_height)
177
+ boxes = []
178
+
179
+ for loc in locations: # Iterate through found locations
180
+ x, y = loc[0] + search_x1, loc[1] + search_y1
181
+ confidence = result[loc[1], loc[0]] # Retrieve the confidence score
182
+ boxes.append(Box(x, y, feature_width, feature_height, confidence, category_name))
183
+ # cv2.rectangle(mat, (x, y), (x + feature_width,y+feature_height),(0, 255, 0), 2)
184
+ # cv2.imwrite("images/test.jpg", mat)
185
+
186
+ result = sort_boxes(boxes)
187
+ communicate.draw_box.emit(category_name, result, "red")
188
+ return result
189
+
190
+
191
+ def filter_and_sort_matches(result, threshold, width, height):
192
+ # Filter matches based on the threshold
193
+ loc = np.where(result >= threshold)
194
+
195
+ # Zip the locations into a list of tuples and sort by threshold in descending order
196
+ matches = sorted(zip(*loc[::-1]), key=lambda p: result[p[::-1]], reverse=True)
197
+
198
+ # Filter out overlapping matches
199
+ unique_matches = []
200
+ for pt in matches:
201
+ if all(not (pt[0] >= m[0] - width and pt[0] <= m[0] + width and
202
+ pt[1] >= m[1] - height and pt[1] <= m[1] + height)
203
+ for m in unique_matches):
204
+ unique_matches.append(pt)
205
+
206
+ # print(f"result {len(result)} loc {len(loc)} matches {len(matches)} unique_matches {unique_matches}")
207
+ return unique_matches
@@ -0,0 +1,52 @@
1
+ import sys
2
+ from typing import List
3
+
4
+ from autohelper.feature.Box import Box
5
+
6
+
7
+ class FindFeature:
8
+
9
+ def __init__(self):
10
+ self.feature_set = None
11
+ self.executor = None
12
+
13
+ def find(self, feature_name, horizontal_variance=0, vertical_variance=0, threshold=0) -> List[Box]:
14
+ if self.executor.frame is None:
15
+ return list()
16
+ return self.feature_set.find_feature(self.executor.frame, feature_name, horizontal_variance, vertical_variance,
17
+ threshold)
18
+
19
+ def find_and_set(self, features, horizontal_variance=0, vertical_variance=0, threshold=0):
20
+ ret = True
21
+ if features is None:
22
+ raise Exception("features cannot be None")
23
+ if isinstance(features, str):
24
+ features = [features]
25
+ for feature in features:
26
+ result = self.find_one(feature, horizontal_variance, vertical_variance, threshold)
27
+ if result is None:
28
+ ret = False
29
+ setattr(self, feature, result)
30
+ return ret
31
+
32
+ def wait_and_click(self, feature, horizontal_variance=0, vertical_variance=0, threshold=0, relative_x=0.5,
33
+ relative_y=0.5,
34
+ time_out=0, pre_action=None, post_action=None):
35
+ box = self.wait_until(lambda: self.find_one(feature, horizontal_variance, vertical_variance, threshold),
36
+ time_out,
37
+ pre_action,
38
+ post_action)
39
+ if box is not None:
40
+ self.click_box(box, relative_x, relative_y)
41
+ return True
42
+ return False
43
+
44
+ def find_one(self, feature_name, horizontal_variance=0, vertical_variance=0, threshold=0) -> Box:
45
+ boxes = self.find(feature_name, horizontal_variance, vertical_variance, threshold)
46
+ if len(boxes) > 0:
47
+ if len(boxes) > 1:
48
+ print(f"find_one:found {feature_name} too many {len(boxes)}", file=sys.stderr)
49
+ return boxes[0]
50
+
51
+ def on_feature(self, boxes):
52
+ pass
File without changes