diff --git a/.gitignore b/.gitignore index 487767d..0d5a95e 100644 --- a/.gitignore +++ b/.gitignore @@ -231,3 +231,4 @@ Thumbs.db # experiment garbage *.png +src/calib_info.json diff --git a/src/df_tools/dfwindow.py b/src/df_tools/dfwindow.py index 770b6c9..c54c995 100644 --- a/src/df_tools/dfwindow.py +++ b/src/df_tools/dfwindow.py @@ -1,3 +1,4 @@ +import json import time import timeit from pathlib import Path @@ -49,7 +50,7 @@ class DFWINDOW: logger.debug(f"Content origin ({content_x}, {content_y})") - return (content_x, content_y) + return (int(content_x), int(content_y)) @staticmethod def isRightBorder(img, num_columns=20, border_threshold: int = 10) -> bool: @@ -61,6 +62,20 @@ class DFWINDOW: # Are all pixels in the strip "black" 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 def isLeftBorder(img, num_columns=20, border_threshold: int = 10) -> bool: # grab a greyscale strip to look at @@ -93,19 +108,29 @@ class DFWINDOW: @staticmethod 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) @staticmethod 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) - bottom_to_ignore = 120 - sleep_after_mouse = 0.2 - sleep_after_key = 0.08 - sleep_after_focus = 0.3 - sleep_after_panning = 0.3 + @staticmethod + def lastNotBlackY(img) -> int: + first_y = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 5)[0][-1] + return int(first_y) + + 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" def __init__(self) -> None: @@ -227,8 +252,8 @@ class DFWINDOW: self.sendKeys("a", 30) img = self.capWindow() self._content_left, self._content_top = self.TOOLS.find_content_origin(img) - self._content_right = img.shape[1] - self._content_left - self._content_bottom = img.shape[0] - self.bottom_to_ignore + self._content_right = int(img.shape[1] - self._content_left) + 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] logger.debug(f"Content width {self.contentWidth}. Content height {self.contentHeight}.") @@ -242,8 +267,8 @@ class DFWINDOW: img = self.capContent() mx2 = self.TOOLS.firstNotBlackX(img) my2 = self.TOOLS.firstNotBlackY(img) - self._step_size_x = mx2 - mx1 - self._step_size_y = my2 - my1 + self._step_size_x = mx1 - mx2 + self._step_size_y = my1 - my2 logger.info(f"Step sizes calculated: x={self._step_size_x} and y={self._step_size_y}") self.sendKeys("w") self.sendKeys("a") @@ -306,13 +331,6 @@ class DFWINDOW: self._gridx = steps_right 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 self.setGridPos(0, 0) time.sleep(self.sleep_after_panning) @@ -332,17 +350,46 @@ class DFWINDOW: logger.debug("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( 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): - 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] tlb = self.TOOLS.firstNotBlackX(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( "np.where(np.mean(cv2.cvtColor(timg, cv2.COLOR_BGR2GRAY), axis=0) > 15)[0][0]", setup=tt_setup, @@ -363,30 +410,57 @@ class DFWINDOW: 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): - self.test1() - return + # self.test1() + # return self.calibrateGrid() - - # Test getting pieces and stitching - 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}") + # self.test_saveGrids() + # self.test_loadCalib() return None