Skip to content

Commit 017cd33

Browse files
authored
Merge pull request #55 from laracroft37/type-hint-annotations
This pull request adds type hints for all significant interfaces and the main guibot package.
2 parents 9b97082 + b960005 commit 017cd33

30 files changed

+1166
-1181
lines changed

.github/workflows/lint.yml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Lint Check
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
mypy:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v2
11+
12+
- name: Set up Python
13+
uses: actions/setup-python@v2
14+
with:
15+
python-version: '3.x'
16+
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install mypy
21+
22+
- name: Run mypy
23+
run: |
24+
mypy guibot

guibot/calibrator.py

+41-55
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@
2828
import time
2929
import math
3030
import copy
31+
from typing import Generator
3132

3233
from .finder import *
33-
from .target import Target
34+
from .target import Target, Image
3435
from .imagelogger import ImageLogger
3536
from .errors import *
37+
from .location import Location
3638

3739
import logging
3840
log = logging.getLogger('guibot.calibrator')
@@ -56,14 +58,14 @@ class Calibrator(object):
5658
multiple random starts from a uniform or normal probability distribution.
5759
"""
5860

59-
def __init__(self, needle=None, haystack=None, config=None):
61+
def __init__(self, needle: Target = None, haystack: Image = None,
62+
config: str = None) -> None:
6063
"""
6164
Build a calibrator object for a given match case.
6265
63-
:param haystack: image to look in
64-
:type haystack: :py:class:`target.Image` or None
6566
:param needle: target to look for
66-
:type needle: :py:class:`target.Target` or None
67+
:param haystack: image to look in
68+
:param config: config file for calibration
6769
"""
6870
self.cases = []
6971
if needle is not None and haystack is not None:
@@ -86,21 +88,20 @@ def __init__(self, needle=None, haystack=None, config=None):
8688
# this attribute can be changed to use different run function
8789
self.run = self.run_default
8890

89-
def benchmark(self, finder, random_starts=0, uniform=False,
90-
calibration=False, max_attempts=3, **kwargs):
91+
def benchmark(self, finder: Finder, random_starts: int = 0, uniform: bool = False,
92+
calibration: bool = False, max_attempts: int = 3,
93+
**kwargs: dict[str, type]) -> list[tuple[str, float, float]]:
9194
"""
9295
Perform benchmarking on all available algorithms of a finder
9396
for a given needle and haystack.
9497
9598
:param finder: CV backend whose backend algorithms will be benchmarked
96-
:type finder: :py:class:`finder.Finder`
97-
:param int random_starts: number of random starts to try with (0 for nonrandom)
98-
:param bool uniform: whether to use uniform or normal distribution
99-
:param bool calibration: whether to use calibration
100-
:param int max_attempts: maximal number of refinements to reach
101-
the parameter delta below the tolerance
102-
:returns: list of (method, similarity, location, time) tuples sorted according to similarity
103-
:rtype: [(str, float, :py:class:`location.Location`, float)]
99+
:param random_starts: number of random starts to try with (0 for nonrandom)
100+
:param uniform: whether to use uniform or normal distribution
101+
:param calibration: whether to use calibration
102+
:param max_attempts: maximal number of refinements to reach
103+
the parameter delta below the tolerance
104+
:returns: list of (method, similarity, time) tuples sorted according to similarity
104105
105106
.. note:: Methods that are supported by OpenCV and others but currently don't work
106107
are excluded from the dictionary. The dictionary can thus also be used to
@@ -120,7 +121,7 @@ def benchmark(self, finder, random_starts=0, uniform=False,
120121
ordered_categories.remove("find")
121122

122123
# test all matching methods of the current finder
123-
def backend_tuples(category_list, finder):
124+
def backend_tuples(category_list: list[str], finder: Finder) -> Generator[tuple[str, ...], None, None]:
124125
if len(category_list) == 0:
125126
yield ()
126127
else:
@@ -159,22 +160,20 @@ def backend_tuples(category_list, finder):
159160
ImageLogger.accumulate_logging = False
160161
return sorted(results, key=lambda x: x[1], reverse=True)
161162

162-
def search(self, finder, random_starts=1, uniform=False,
163-
calibration=True, max_attempts=3, **kwargs):
163+
def search(self, finder: Finder, random_starts: int = 1, uniform: bool = False,
164+
calibration: bool = True, max_attempts: int = 3, **kwargs: dict[str, type]) -> float:
164165
"""
165166
Search for the best match configuration for a given needle and haystack
166167
using calibration from random initial conditions.
167168
168169
:param finder: CV backend to use in order to determine deltas, fixed, and free
169170
parameters and ultimately tweak to minimize error
170-
:type finder: :py:class:`finder.Finder`
171-
:param int random_starts: number of random starts to try with
172-
:param bool uniform: whether to use uniform or normal distribution
173-
:param bool calibration: whether to use calibration
174-
:param int max_attempts: maximal number of refinements to reach
175-
the parameter delta below the tolerance
171+
:param random_starts: number of random starts to try with
172+
:param uniform: whether to use uniform or normal distribution
173+
:param calibration: whether to use calibration
174+
:param max_attempts: maximal number of refinements to reach
175+
the parameter delta below the tolerance
176176
:returns: maximized similarity
177-
:rtype: float
178177
179178
If normal distribution is used, the mean will be the current value of the
180179
respective CV parameter and the standard variation will be determined from
@@ -225,17 +224,15 @@ def search(self, finder, random_starts=1, uniform=False,
225224
category, key, param.value, param.delta)
226225
return 1.0 - best_error
227226

228-
def calibrate(self, finder, max_attempts=3, **kwargs):
227+
def calibrate(self, finder: Finder, max_attempts: int = 3, **kwargs: dict[str, type]) -> float:
229228
"""
230229
Calibrate the available match configuration for a given needle
231230
and haystack minimizing the matchign error.
232231
233232
:param finder: configuration for the CV backend to calibrate
234-
:type finder: :py:class:`finder.Finder`
235-
:param int max_attempts: maximal number of refinements to reach
236-
the parameter delta below the tolerance
233+
:param max_attempts: maximal number of refinements to reach
234+
the parameter delta below the tolerance
237235
:returns: maximized similarity
238-
:rtype: float
239236
240237
This method calibrates only parameters that are not protected
241238
from calibration, i.e. that have `fixed` attribute set to false.
@@ -291,17 +288,17 @@ def calibrate(self, finder, max_attempts=3, **kwargs):
291288
# add the delta to the current parameter
292289
if isinstance(param.value, float):
293290
if param.range[1] is not None:
294-
param.value = min(start_value + param.delta,
291+
param.value = min(float(start_value) + param.delta,
295292
param.range[1])
296293
else:
297-
param.value = start_value + param.delta
294+
param.value = float(start_value) + param.delta
298295
elif isinstance(param.value, int) and not param.enumerated:
299296
intdelta = int(math.ceil(param.delta))
300297
if param.range[1] is not None:
301-
param.value = min(start_value + intdelta,
298+
param.value = min(int(start_value) + intdelta,
302299
param.range[1])
303300
else:
304-
param.value = start_value + intdelta
301+
param.value = int(start_value) + intdelta
305302
# remaining types require special handling
306303
elif isinstance(param.value, int) and param.enumerated:
307304
delta_coeff = 0.9
@@ -339,17 +336,17 @@ def calibrate(self, finder, max_attempts=3, **kwargs):
339336

340337
if isinstance(param.value, float):
341338
if param.range[0] is not None:
342-
param.value = max(start_value - param.delta,
339+
param.value = max(float(start_value) - param.delta,
343340
param.range[0])
344341
else:
345-
param.value = start_value - param.delta
342+
param.value = float(start_value) - param.delta
346343
elif isinstance(param.value, int):
347344
intdelta = int(math.floor(param.delta))
348345
if param.range[0] is not None:
349-
param.value = max(start_value - intdelta,
346+
param.value = max(int(start_value) - intdelta,
350347
param.range[0])
351348
else:
352-
param.value = start_value - intdelta
349+
param.value = int(start_value) - intdelta
353350
elif isinstance(param.value, bool):
354351
# the default boolean value was already checked
355352
param.value = start_value
@@ -388,14 +385,12 @@ def calibrate(self, finder, max_attempts=3, **kwargs):
388385
category, key, param.value, param.delta)
389386
return 1.0 - best_error
390387

391-
def run_default(self, finder, **_kwargs):
388+
def run_default(self, finder: Finder, **_kwargs: dict[str, type]) -> float:
392389
"""
393390
Run a match case and return error from the match as dissimilarity.
394391
395392
:param finder: finder with match configuration to use for the run
396-
:type finder: :py:class:`finder.Finder`
397393
:returns: error obtained as unity minus similarity
398-
:rtype: float
399394
"""
400395
self._handle_restricted_values(finder)
401396

@@ -414,20 +409,16 @@ def run_default(self, finder, **_kwargs):
414409
error = 1.0 - total_similarity / len(self.cases)
415410
return error
416411

417-
def run_performance(self, finder, **kwargs):
412+
def run_performance(self, finder: Finder, **kwargs: dict[str, type]) -> float:
418413
"""
419414
Run a match case and return error from the match as dissimilarity
420415
and linear performance penalty.
421416
422417
:param finder: finder with match configuration to use for the run
423-
:type finder: :py:class:`finder.Finder`
424-
:param float max_exec_time: maximum execution time before penalizing
425-
the run by increasing the error linearly
426418
:returns: error obtained as unity minus similarity
427-
:rtype: float
428419
"""
429420
self._handle_restricted_values(finder)
430-
max_exec_time = kwargs.get("max_exec_time", 1.0)
421+
max_exec_time: float = kwargs.get("max_exec_time", 1.0)
431422

432423
total_similarity = 0.0
433424
for needle, haystack, maximize in self.cases:
@@ -449,18 +440,13 @@ def run_performance(self, finder, **kwargs):
449440
error += max(total_time - max_exec_time, 0)
450441
return error
451442

452-
def run_peak(self, finder, **kwargs):
443+
def run_peak(self, finder: Finder, **kwargs: dict[str, type]) -> float:
453444
"""
454445
Run a match case and return error from the match as failure to obtain
455446
high similarity of one match and low similarity of all others.
456447
457448
:param finder: finder with match configuration to use for the run
458-
:type finder: :py:class:`finder.Finder`
459-
:param peak_location: (x, y) of the match whose similarity should be
460-
maximized while all the rest minimized
461-
:type peak_location: (int, int)
462449
:returns: error obtained as unity minus similarity
463-
:rtype: float
464450
465451
This run function doesn't just obtain the optimum similarity for the best
466452
match in each case of needle and haystack but it minimizes the similarity
@@ -495,7 +481,7 @@ def run_peak(self, finder, **kwargs):
495481
error = 1.0 - total_similarity / len(self.cases)
496482
return error
497483

498-
def _handle_restricted_values(self, finder):
484+
def _handle_restricted_values(self, finder: Finder) -> None:
499485
if "threshold" in finder.params:
500486
params = finder.params["threshold"]
501487
if params["blurKernelSize"].value % 2 == 0:
@@ -524,7 +510,7 @@ def _handle_restricted_values(self, finder):
524510
diffs = {m: abs(m - params["dt_mask_size"].value) for m in [0, 3, 5]}
525511
params["dt_mask_size"].value = min(diffs, key=diffs.get)
526512

527-
def _prepare_params(self, finder):
513+
def _prepare_params(self, finder: Finder) -> None:
528514
# any similarity parameters will be reset to 0.0 to search optimally
529515
finder.params["find"]["similarity"].value = 0.0
530516
finder.params["find"]["similarity"].fixed = True

0 commit comments

Comments
 (0)