Fixes and a few additional helper functions

- corrected the return type of a few functions to int
- Add DFWINDOW.TOOLS.getImageDiff to get how diff 2 images are
- Added complimentary lastNotBlack(X/Y)
- Increase blindly chosen preset "bottom_to_ignore". Wasnt enough when full screen.
- Fixed the sign/polarity of step sizes
- First attempt at calculating the size of the map. Probably wrong. Will replace when writing the map maker
- removed cv2.Stitcher stuff. Not what I needed
- Added some test_ functions for experimenting. Ignore.
This commit is contained in:
Doc
2026-01-13 09:52:09 -05:00
parent 941eae5d57
commit 3ab553fb6d
2 changed files with 118 additions and 43 deletions

1
.gitignore vendored
View File

@@ -231,3 +231,4 @@ Thumbs.db
# experiment garbage # experiment garbage
*.png *.png
src/calib_info.json

View File

@@ -1,3 +1,4 @@
import json
import time import time
import timeit import timeit
from pathlib import Path from pathlib import Path
@@ -49,7 +50,7 @@ class DFWINDOW:
logger.debug(f"Content origin ({content_x}, {content_y})") logger.debug(f"Content origin ({content_x}, {content_y})")
return (content_x, content_y) return (int(content_x), int(content_y))
@staticmethod @staticmethod
def isRightBorder(img, num_columns=20, border_threshold: int = 10) -> bool: def isRightBorder(img, num_columns=20, border_threshold: int = 10) -> bool:
@@ -61,6 +62,20 @@ class DFWINDOW:
# Are all pixels in the strip "black" # Are all pixels in the strip "black"
return not np.any(thresh) return not np.any(thresh)
@staticmethod
def getImageDiff(image1, image2, conversion=cv2.COLOR_BGR2GRAY) -> float:
# Diff size, very dif img
if image1.shape != image2.shape:
return float("inf")
grey1 = cv2.cvtColor(image1, conversion)
grey2 = cv2.cvtColor(image2, conversion)
# Apparently this is the Mean Squared Error (MES). Thanks Gemini (LLM)
err = np.sum((grey1.astype("float") - grey2.astype("float")) ** 2)
err /= float(grey1.shape[0] * grey1.shape[0])
return float(err)
@staticmethod @staticmethod
def isLeftBorder(img, num_columns=20, border_threshold: int = 10) -> bool: def isLeftBorder(img, num_columns=20, border_threshold: int = 10) -> bool:
# grab a greyscale strip to look at # grab a greyscale strip to look at
@@ -93,19 +108,29 @@ class DFWINDOW:
@staticmethod @staticmethod
def firstNotBlackX(img) -> int: def firstNotBlackX(img) -> int:
first_x = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 15)[0][0] first_x = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 5)[0][0]
return int(first_x)
@staticmethod
def lastNotBlackX(img) -> int:
first_x = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 5)[0][-1]
return int(first_x) return int(first_x)
@staticmethod @staticmethod
def firstNotBlackY(img) -> int: def firstNotBlackY(img) -> int:
first_y = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 15)[0][0] first_y = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 5)[0][0]
return int(first_y) return int(first_y)
bottom_to_ignore = 120 @staticmethod
sleep_after_mouse = 0.2 def lastNotBlackY(img) -> int:
sleep_after_key = 0.08 first_y = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 5)[0][-1]
sleep_after_focus = 0.3 return int(first_y)
sleep_after_panning = 0.3
bottom_to_ignore = 160
sleep_after_mouse = 0.2 # 2
sleep_after_key = 0.08 # 08
sleep_after_focus = 0.2 # 3
sleep_after_panning = 0.2 # 3
query_for_window = "dwarfort" query_for_window = "dwarfort"
def __init__(self) -> None: def __init__(self) -> None:
@@ -227,8 +252,8 @@ class DFWINDOW:
self.sendKeys("a", 30) self.sendKeys("a", 30)
img = self.capWindow() img = self.capWindow()
self._content_left, self._content_top = self.TOOLS.find_content_origin(img) self._content_left, self._content_top = self.TOOLS.find_content_origin(img)
self._content_right = img.shape[1] - self._content_left self._content_right = int(img.shape[1] - self._content_left)
self._content_bottom = img.shape[0] - self.bottom_to_ignore self._content_bottom = int(img.shape[0] - self.bottom_to_ignore)
img = img[self._content_top : self._content_bottom, self._content_left : self._content_right] # pyright: ignore[reportOptionalSubscript] img = img[self._content_top : self._content_bottom, self._content_left : self._content_right] # pyright: ignore[reportOptionalSubscript]
logger.debug(f"Content width {self.contentWidth}. Content height {self.contentHeight}.") logger.debug(f"Content width {self.contentWidth}. Content height {self.contentHeight}.")
@@ -242,8 +267,8 @@ class DFWINDOW:
img = self.capContent() img = self.capContent()
mx2 = self.TOOLS.firstNotBlackX(img) mx2 = self.TOOLS.firstNotBlackX(img)
my2 = self.TOOLS.firstNotBlackY(img) my2 = self.TOOLS.firstNotBlackY(img)
self._step_size_x = mx2 - mx1 self._step_size_x = mx1 - mx2
self._step_size_y = my2 - my1 self._step_size_y = my1 - my2
logger.info(f"Step sizes calculated: x={self._step_size_x} and y={self._step_size_y}") logger.info(f"Step sizes calculated: x={self._step_size_x} and y={self._step_size_y}")
self.sendKeys("w") self.sendKeys("w")
self.sendKeys("a") self.sendKeys("a")
@@ -306,13 +331,6 @@ class DFWINDOW:
self._gridx = steps_right self._gridx = steps_right
self._gridy = steps_down self._gridy = steps_down
# TODO: Use seek tests to calculate mapsize in pixels
# at (0,0) save left_edge_offset and top_edge_offset
# at (max,max) save right_edge_offset and bottom_edge_offset
# width = (contentWidth - l_e_o) + (gridyx_max * _step_size_x) - abs(r_e_o)
# | <==|====|====|==> |
# (max*size) is too far, so we subract the ofset/border from the right map edge
# Test going to 0,0 # Test going to 0,0
self.setGridPos(0, 0) self.setGridPos(0, 0)
time.sleep(self.sleep_after_panning) time.sleep(self.sleep_after_panning)
@@ -332,17 +350,46 @@ class DFWINDOW:
logger.debug("Calibration error. Not at requested lower right of map") logger.debug("Calibration error. Not at requested lower right of map")
raise Exception("Calibration error. Not at requested lower right of map") raise Exception("Calibration error. Not at requested lower right of map")
cal_right_border = self.TOOLS.lastNotBlackX(img)
cal_bottom_border = self.TOOLS.lastNotBlackY(img)
# TODO: Use seek tests to calculate mapsize in pixels
# at (0,0) save left_edge_offset and top_edge_offset
# at (max,max) save right_edge_offset and bottom_edge_offset
# width = (contentWidth - l_e_o) + (gridyx_max * _step_size_x) - abs(r_e_o)
# | <==|====|====|==> |
# (max*size) is too far, so we subract the ofset/border from the right map edge
self._map_width = (
(img.shape[1] - cal_left_border) # Grid x = 0
+ ((self._gridx_max - 1) * self._step_size_x) # All the middle
+ cal_right_border # grid x = max
)
self._map_height = (
(img.shape[0] - cal_top_border) # Grid x = 0
+ ((self._gridy_max - 1) * self._step_size_y) # All the middle
+ cal_right_border # grid x = max
)
self.setGridPos(0, 0)
logger.debug(f"Map dimensions calculated as {self._map_width} x {self._map_height}")
logger.info( logger.info(
f"Grid calibration complete. Grid steps ({self._gridy_max + 1},{self._gridy_max + 1}), step sizes({self._step_size_x},{self._step_size_y})" f"Grid calibration complete. Grid steps ({self._gridy_max + 1},{self._gridy_max + 1}), step sizes({self._step_size_x},{self._step_size_y})"
) )
def test1(self): def test1(self):
rawimg = cv2.imread("./test_img.png") # rawimg = cv2.imread("./test_img.png")
rawimg = cv2.imread("grid_base_3.png")
img = rawimg[100 : -self.bottom_to_ignore - 70, 65:-65] img = rawimg[100 : -self.bottom_to_ignore - 70, 65:-65]
tlb = self.TOOLS.firstNotBlackX(img) tlb = self.TOOLS.firstNotBlackX(img)
ttb = self.TOOLS.firstNotBlackY(img) ttb = self.TOOLS.firstNotBlackY(img)
tt_setup = r"gc.enable() ; import cv2 ; import numpy as np ; timg = cv2.imread('./test_img.png', cv2.IMREAD_UNCHANGED)" tt_setup = (
r"gc.enable() ; import cv2 ; import numpy as np ; timg = cv2.imread('./test_img.png', cv2.IMREAD_UNCHANGED)"
)
tt1 = timeit.Timer( tt1 = timeit.Timer(
"np.where(np.mean(cv2.cvtColor(timg, cv2.COLOR_BGR2GRAY), axis=0) > 15)[0][0]", "np.where(np.mean(cv2.cvtColor(timg, cv2.COLOR_BGR2GRAY), axis=0) > 15)[0][0]",
setup=tt_setup, setup=tt_setup,
@@ -363,30 +410,57 @@ class DFWINDOW:
logger.debug("Pause here for testing") logger.debug("Pause here for testing")
def test_saveGrids(self):
savedir = Path()
savefile_base = "cached_grid"
savefile_ext = "png"
for x in range(0, self._gridx_max + 1):
for y in range(0, self._gridy_max):
self.setGridPos(x, y)
time.sleep(self.sleep_after_panning)
img = self.capContent()
savefilename = savedir.joinpath(f"{savefile_base}_{x}_{y}.{savefile_ext}")
cv2.imwrite(str(savefilename.resolve()), img)
calib_info = {
"gridx": int(self._gridx),
"gridy": int(self._gridy),
"gridx_max": int(self._gridx_max),
"gridy_max": int(self._gridy_max),
"step_size_x": int(self._step_size_x),
"step_size_y": int(self._step_size_y),
"content_top": int(self._content_top),
"content_bottom": int(self._content_bottom),
"content_left": int(self._content_left),
"content_right": int(self._content_right),
"map_height": int(self._map_height),
"map_width": int(self._map_width),
}
with open("./calib_info.json", "w") as fh:
json.dump(calib_info, fh)
def test_loadCalib(self):
with open("./calib_info.json") as fh:
calib_info = json.load(fh)
self._gridx = calib_info["gridx"]
self._gridy = calib_info["gridy"]
self._gridx_max = calib_info["gridx_max"]
self._gridy_max = calib_info["gridy_max"]
self._step_size_x = calib_info["step_size_x"]
self._step_size_y = calib_info["step_size_y"]
self._content_top = calib_info["content_top"]
self._content_bottom = calib_info["content_bottom"]
self._content_left = calib_info["content_left"]
self._content_right = calib_info["content_right"]
self._map_height = calib_info["map_height"]
self._map_width = calib_info["map_width"]
def getPanoramaMap(self): def getPanoramaMap(self):
self.test1() # self.test1()
return # return
self.calibrateGrid() self.calibrateGrid()
# self.test_saveGrids()
# Test getting pieces and stitching # self.test_loadCalib()
stitcher = cv2.Stitcher.create(cv2.STITCHER_SCANS)
stitcher.setPanoConfidenceThresh(0.1) # Dont be confident
imgs_in_row = []
# Get a row
self.setGridPos(0, 0)
time.sleep(self.sleep_after_panning)
for x in range(0, self.maxGridX + 1, 3):
self.setGridPos(x, self.curGridY)
time.sleep(self.sleep_after_panning)
img = self.capContent()
if img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
imgs_in_row.append(img)
status, strip = stitcher.stitch(imgs_in_row)
logger.debug(f"{len(imgs_in_row)} images. {status=} {strip=} {status == cv2.Stitcher_OK}")
return None return None