Skip to content

Commit

Permalink
Improve typing with mypy
Browse files Browse the repository at this point in the history
  • Loading branch information
Avasam committed Jul 17, 2023
1 parent 75e16bf commit a34a393
Show file tree
Hide file tree
Showing 20 changed files with 169 additions and 144 deletions.
5 changes: 5 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
120, // Our hard rule
],
},
"mypy-type-checker.importStrategy": "fromEnvironment",
"mypy-type-checker.args": [
// https://github.com/microsoft/vscode-mypy/issues/37#issuecomment-1602702174
"--config-file=mypy.ini",
],
// Important to follow the config in pyrightconfig.json
"python.analysis.useLibraryCodeForTypes": false,
"python.analysis.diagnosticMode": "workspace",
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ Not a developer? You can still help through the following methods:
- <https://bugreports.qt.io/browse/QTBUG-114436>
- <https://github.com/astral-sh/ruff/issues?q=is%3Aopen+involves%3AAvasam>
- <https://github.com/opencv/opencv/issues?q=is%3Aopen+involves%3AAvasam>
- <https://github.com/opencv/opencv/issues/23906>
- <https://github.com/pywinrt/python-winsdk/issues/11>
- <https://github.com/microsoft/vscode/issues/40239>
- <https://github.com/microsoft/vscode/issues/168411>
Expand Down
26 changes: 15 additions & 11 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
; We don't run mypy in the CI. This is just to help anyone who would like to use it manually.
; Namely, the mypy_primer tool.
[mypy]
; strict = true
show_column_numbers = true
mypy_path = $MYPY_CONFIG_FILE_DIR/typings
implicit_reexport = true

strict = true
; Implicit return types !
check_untyped_defs = true
disallow_untyped_calls = false
disallow_untyped_defs = false
disallow_incomplete_defs = false
disable_error_code = return

; exclude mypyc build
exclude = .*(build)/.*

; Auto-generated code, not much we can do there
[mypy-gen.*]
disable_error_code = attr-defined, arg-type

; Of course my stubs are going to be incomplete. Otherwise they'd be on typeshed!
; Mypy becomes really whack with its errors inside these stubs though
mypy_path = typings,src
; exclude doesn't work with strict=true Why?
exclude = .*(typings|gen)/.*

[mypy-gen.*,cv2.*,]
; strict=false ; Doesn't work in overrides
follow_imports = skip
implicit_reexport = true
strict_optional = false
disable_error_code = attr-defined, misc, name-defined
[mypy-cv2.*]
disable_error_code = misc, name-defined, override
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ignore = [
"SIM105", # flake8-simplify: use-contextlib-suppress
# Checked by type-checker (pyright)
"ANN", # flake-annotations
"PGH003", # blanket-type-ignore
"TCH", # flake8-type-checking
# Already shown by Pylance, checked by pyright, and can be caused by overloads.
"ARG002", # Unused method argument
Expand All @@ -45,10 +46,10 @@ ignore = [
###
# Specific to this project
###
"D205", # Not all docstrings have a short description + desrciption
# We have some Pascal case module names
"N999", # pep8-naming: Invalid module name
# Print are used as debug logs
"D205", # Not all docstrings have a short description + desrciption
"T20", # flake8-print
# This is a relatively small, low contributors project. Git blame suffice.
"TD002", # missing-todo-author
Expand Down
3 changes: 2 additions & 1 deletion scripts/compile_resources.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
$originalDirectory = $pwd
Set-Location "$PSScriptRoot/.."

New-Item -Force -ItemType directory ./src/gen | Out-Null
New-Item ./src/gen -ItemType directory -Force | Out-Null
New-Item ./src/gen/__init__.py -ItemType File -Force | Out-Null
pyside6-uic './res/about.ui' -o './src/gen/about.py'
pyside6-uic './res/design.ui' -o './src/gen/design.py'
pyside6-uic './res/settings.ui' -o './src/gen/settings.py'
Expand Down
16 changes: 9 additions & 7 deletions src/AutoSplit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import certifi
import cv2
import cv2.typing
from cv2.typing import MatLike
from psutil import process_iter
from PySide6 import QtCore, QtGui
from PySide6.QtTest import QTest
Expand Down Expand Up @@ -242,7 +242,7 @@ def __browse(self):
self.split_image_folder_input.setText(f"{new_split_image_directory}/")
self.load_start_image_signal.emit(False, True)

def __update_live_image_details(self, capture: cv2.typing.MatLike | None, called_from_timer: bool = False):
def __update_live_image_details(self, capture: MatLike | None, called_from_timer: bool = False):
# HACK: Since this is also called in __get_capture_for_comparison,
# we don't need to update anything if the app is running
if called_from_timer:
Expand Down Expand Up @@ -531,11 +531,15 @@ def __auto_splitter(self): # noqa: PLR0912,PLR0915

# Construct groups of splits
self.split_groups = []
dummy_splits_array = []
number_of_split_images = len(self.split_images_and_loop_number)
current_group: list[int] = []
self.split_groups.append(current_group)
for i, image in enumerate(self.split_images_and_loop_number):
current_group.append(i)
if not image[0].check_flag(DUMMY_FLAG) and i < len(self.split_images_and_loop_number) - 1:
dummy = image[0].check_flag(DUMMY_FLAG)
dummy_splits_array.append(dummy)
if not dummy and i < number_of_split_images - 1:
current_group = []
self.split_groups.append(current_group)

Expand All @@ -551,8 +555,6 @@ def __auto_splitter(self): # noqa: PLR0912,PLR0915
self.waiting_for_split_delay = False
self.split_below_threshold = False
split_time = 0
number_of_split_images = len(self.split_images_and_loop_number)
dummy_splits_array = [image_loop[0].check_flag(DUMMY_FLAG) for image_loop in self.split_images_and_loop_number]

# First loop: stays in this loop until all of the split images have been split
while self.split_image_number < number_of_split_images:
Expand Down Expand Up @@ -794,7 +796,7 @@ def __get_capture_for_comparison(self):
self.__update_live_image_details(capture)
return capture, is_old_image

def __reset_if_should(self, capture: cv2.typing.MatLike | None):
def __reset_if_should(self, capture: MatLike | None):
"""Checks if we should reset, resets if it's the case, and returns the result."""
if self.reset_image:
if self.settings_dict["enable_auto_reset"]:
Expand Down Expand Up @@ -904,7 +906,7 @@ def exit_program() -> NoReturn:
exit_program()


def set_preview_image(qlabel: QLabel, image: cv2.typing.MatLike | None):
def set_preview_image(qlabel: QLabel, image: MatLike | None):
if not is_valid_image(image):
# Clear current pixmap if no image. But don't clear text
if not qlabel.text():
Expand Down
16 changes: 10 additions & 6 deletions src/AutoSplitImage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from typing import TYPE_CHECKING

import cv2
import cv2.typing
import numpy as np
from cv2.typing import MatLike

import error_messages
from compare import COMPARE_METHODS_BY_INDEX, check_if_image_has_transparency
Expand Down Expand Up @@ -40,8 +40,8 @@ class AutoSplitImage:
flags: int
loops: int
image_type: ImageType
byte_array: cv2.typing.MatLike | None = None
mask: cv2.typing.MatLike | None = None
byte_array: MatLike | None = None
mask: MatLike | None = None
# This value is internal, check for mask instead
_has_transparency = False
# These values should be overriden by some Defaults if None. Use getters instead
Expand Down Expand Up @@ -136,15 +136,19 @@ def check_flag(self, flag: int):
def compare_with_capture(
self,
default: AutoSplit | int,
capture: cv2.typing.MatLike | None,
capture: MatLike | None,
):
"""Compare image with capture using image's comparison method. Falls back to combobox."""
if not is_valid_image(self.byte_array) or not is_valid_image(capture):
return 0.0
capture = cv2.resize(capture, self.byte_array.shape[1::-1])
resized_capture = cv2.resize(capture, self.byte_array.shape[1::-1])
comparison_method = self.__get_comparison_method(default)

return COMPARE_METHODS_BY_INDEX.get(comparison_method, compare_dummy)(self.byte_array, capture, self.mask)
return COMPARE_METHODS_BY_INDEX.get(
comparison_method, compare_dummy,
)(
self.byte_array, resized_capture, self.mask,
)


def compare_dummy(*_: object): return 0.0
Expand Down
7 changes: 3 additions & 4 deletions src/capture_method/BitBltCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import ctypes.wintypes
from typing import TYPE_CHECKING, cast

import cv2
import cv2.typing
import numpy as np
import pywintypes
import win32con
import win32ui
from cv2.typing import MatLike
from typing_extensions import override
from win32 import win32gui

Expand All @@ -36,10 +35,10 @@ class BitBltCaptureMethod(CaptureMethodBase):
_render_full_content = False

@override
def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.typing.MatLike | None, bool]:
def get_frame(self, autosplit: AutoSplit) -> tuple[MatLike | None, bool]:
selection = autosplit.settings_dict["capture_region"]
hwnd = autosplit.hwnd
image: cv2.typing.MatLike | None = None
image: MatLike | None = None

if not self.check_selected_region_exists(autosplit):
return None, False
Expand Down
7 changes: 3 additions & 4 deletions src/capture_method/CaptureMethodBase.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

from typing import TYPE_CHECKING

import cv2
import cv2.typing
from cv2.typing import MatLike

from utils import is_valid_hwnd

Expand All @@ -23,13 +22,13 @@ def __init__(self, autosplit: AutoSplit | None):

def reinitialize(self, autosplit: AutoSplit):
self.close(autosplit)
self.__init__(autosplit)
self.__init__(autosplit) # type: ignore[misc]

def close(self, autosplit: AutoSplit):
# Some capture methods don't need an initialization process
pass

def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.typing.MatLike | None, bool]:
def get_frame(self, autosplit: AutoSplit) -> tuple[MatLike | None, bool]:
"""
Captures an image of the region for a window matching the given
parameters of the bounding box.
Expand Down
14 changes: 7 additions & 7 deletions src/capture_method/VideoCaptureDeviceCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

import cv2
import cv2.Error
import cv2.typing
import numpy as np
from cv2.typing import MatLike
from pygrabber.dshow_graph import FilterGraph
from typing_extensions import override

Expand All @@ -21,7 +21,7 @@
OBS_VIRTUALCAM_PLUGIN_BLANK_PIXEL = [127, 129, 128]


def is_blank(image: cv2.typing.MatLike):
def is_blank(image: MatLike):
# Running np.all on the entire array or looping manually through the
# entire array is extremely slow when we can't stop early.
# Instead we check for a few key pixels, in this case, corners
Expand All @@ -44,23 +44,23 @@ class VideoCaptureDeviceCaptureMethod(CaptureMethodBase):
capture_device: cv2.VideoCapture
capture_thread: Thread | None
stop_thread: Event
last_captured_frame: cv2.typing.MatLike | None = None
last_captured_frame: MatLike | None = None
is_old_image = False

def __read_loop(self, autosplit: AutoSplit):
try:
while not self.stop_thread.is_set():
try:
result, image = self.capture_device.read()
except cv2.error as error:
except cv2.error as cv2_error:
if not (
error.code == cv2.Error.STS_ERROR
cv2_error.code == cv2.Error.STS_ERROR
and (
# Likely means the camera is occupied
error.msg.endswith("in function 'cv::VideoCapture::grab'\n")
cv2_error.msg.endswith("in function 'cv::VideoCapture::grab'\n")
# Some capture cards we cannot use directly
# https://github.com/opencv/opencv/issues/23539
or error.msg.endswith("in function 'cv::VideoCapture::retrieve'\n")
or cv2_error.msg.endswith("in function 'cv::VideoCapture::retrieve'\n")
)
):
raise
Expand Down
10 changes: 4 additions & 6 deletions src/capture_method/WindowsGraphicsCaptureMethod.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import asyncio
from typing import TYPE_CHECKING, cast

import cv2
import cv2.typing
import numpy as np
from cv2.typing import MatLike
from typing_extensions import override
from win32 import win32gui
from winsdk.windows.graphics import SizeInt32
Expand Down Expand Up @@ -42,7 +41,7 @@ class WindowsGraphicsCaptureMethod(CaptureMethodBase):
frame_pool: Direct3D11CaptureFramePool | None = None
session: GraphicsCaptureSession | None = None
"""This is stored to prevent session from being garbage collected"""
last_captured_frame: cv2.typing.MatLike | None = None
last_captured_frame: MatLike | None = None

def __init__(self, autosplit: AutoSplit):
super().__init__(autosplit)
Expand Down Expand Up @@ -86,7 +85,7 @@ def close(self, autosplit: AutoSplit):
self.session = None

@override
def get_frame(self, autosplit: AutoSplit) -> tuple[cv2.typing.MatLike | None, bool]:
def get_frame(self, autosplit: AutoSplit) -> tuple[MatLike | None, bool]:
selection = autosplit.settings_dict["capture_region"]
# We still need to check the hwnd because WGC will return a blank black image
if not (
Expand Down Expand Up @@ -138,9 +137,8 @@ def recover_window(self, captured_window_title: str, autosplit: AutoSplit):
if not is_valid_hwnd(hwnd):
return False
autosplit.hwnd = hwnd
self.close(autosplit)
try:
self.__init__(autosplit)
self.reinitialize(autosplit)
# Unrecordable hwnd found as the game is crashing
except OSError as exception:
if str(exception).endswith("The parameter is incorrect"):
Expand Down
Loading

0 comments on commit a34a393

Please sign in to comment.