Compare commits

...

6 Commits

Author SHA1 Message Date
Doc
71758c42cc Progress towards stitching a map
- Add gridHeight and gridWidth properties
- Fix the ability of overriding keypress delays
- Fixed/edited debug messages
- Still not sure about map size estimation formula
- Added addToCanvas for created a stitched map
- Create the veritcal part of the loop for stitching together maps
2026-01-14 00:59:32 -05:00
Doc
2c95aa26bb Fixed error in "faster seeking"
- I made a major oversight when speeding up the grid calibration. I forgot some variables were not set until AFTER the calibration. Fixed it so it is still faster/adaptable, but doesnt try to use values not yet set.
2026-01-13 23:04:54 -05:00
Doc
f2c1d04ed4 Add stepSizeX and stepSizeY properties
- Added properties stepSizeX stepSizeY to DFWINDOW
2026-01-13 23:02:18 -05:00
Doc
98b2e3352b Revert pixel search back to slower mean
- *NotBlack* functions reverted to slower np.mean, to fix edge cases with np.max
2026-01-13 22:59:30 -05:00
Doc
4c01b72b54 Improved the seeking in the grid calibration
- Make the seeking of the upper left corner more intelligent/responsive rather than hard coding "left 15 times up 15 times"
- Commented out more experimentation code
2026-01-13 10:08:15 -05:00
Doc
4e4d3fc854 Fixed variable name in map size calculation
- accidentlally used cal_right_border instead of cal_bottom_border. Always pay attention to warnings of unused variables
- commented out unused code
2026-01-13 09:57:09 -05:00
2 changed files with 188 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
import json import json
import math
import time import time
import timeit
from pathlib import Path from pathlib import Path
import cv2 import cv2
@@ -108,22 +108,22 @@ 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) > 5)[0][0] first_x = np.where(np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 10)[0][0]
return int(first_x) return int(first_x)
@staticmethod @staticmethod
def lastNotBlackX(img) -> int: def lastNotBlackX(img) -> int:
first_x = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 5)[0][-1] first_x = np.where(np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=0) > 10)[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) > 5)[0][0] first_y = np.where(np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 10)[0][0]
return int(first_y) return int(first_y)
@staticmethod @staticmethod
def lastNotBlackY(img) -> int: def lastNotBlackY(img) -> int:
first_y = np.where(np.max(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 5)[0][-1] first_y = np.where(np.mean(cv2.cvtColor(img, cv2.COLOR_BGR2GRAY), axis=1) > 10)[0][-1]
return int(first_y) return int(first_y)
bottom_to_ignore = 160 bottom_to_ignore = 160
@@ -164,6 +164,22 @@ class DFWINDOW:
@property @property
def maxGridY(self) -> int: def maxGridY(self) -> int:
return self._gridy_max return self._gridy_max
@property
def gridHeight(self) -> int:
return int(self._gridy_max + 1)
@property
def gridWidth(self) -> int:
return int(self._gridx_max + 1)
@property
def stepSizeX(self) -> int:
return int(self._step_size_x)
@property
def stepSizeY(self) -> int:
return int(self._step_size_y)
@property @property
def contentWidth(self) -> int: def contentWidth(self) -> int:
@@ -204,16 +220,17 @@ class DFWINDOW:
thekey: str | int, thekey: str | int,
count: int = 1, count: int = 1,
modifier: str | int | list[str | int] | None = None, modifier: str | int | list[str | int] | None = None,
cycle_delay: float = 0.1, cycle_delay: float = 9999,
sub_cycle_delay: float = 0.05, sub_cycle_delay: float = 0.05,
custom_lookup: dict[str, int] | None = None, custom_lookup: dict[str, int] | None = None,
): ):
_cycle_delay = cycle_delay if cycle_delay != 9999 else self.sleep_after_key
self.focusWindow() self.focusWindow()
_sendKey( _sendKey(
thekey=thekey, thekey=thekey,
count=count, count=count,
modifier=modifier, modifier=modifier,
cycle_delay=self.sleep_after_key, cycle_delay=_cycle_delay,
sub_cycle_delay=sub_cycle_delay, sub_cycle_delay=sub_cycle_delay,
custom_lookup=custom_lookup, custom_lookup=custom_lookup,
) )
@@ -248,13 +265,30 @@ class DFWINDOW:
time.sleep(self.sleep_after_mouse) time.sleep(self.sleep_after_mouse)
self.focusWindow() self.focusWindow()
time.sleep(self.sleep_after_focus) time.sleep(self.sleep_after_focus)
self.sendKeys("w", 30)
self.sendKeys("a", 30) img = self.capWindow()
# Improved seeking upper left
self.sendKeys("w", 2)
self.sendKeys("a", 2)
old_img = img
img = self.capWindow()
while self.TOOLS.getImageDiff(old_img, img) > 3:
self.sendKeys("w", 4)
self.sendKeys("a", 4)
old_img = img
img = self.capWindow()
self.sendKeys("w", 4)
self.sendKeys("a", 4)
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 = int(img.shape[1] - self._content_left) self._content_right = int(img.shape[1] - self._content_left)
self._content_bottom = int(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}.")
# Try to measure steps # Try to measure steps
@@ -300,7 +334,7 @@ class DFWINDOW:
# We now know how many steps the map is vertically # We now know how many steps the map is vertically
steps_vertical = steps_down steps_vertical = steps_down
logger.debug(f"Map is about {steps_vertical} steps vertical. Current step is {steps_down}") logger.debug(f"Map is about {steps_vertical+1} steps vertical. Current index is {steps_down}")
# go right until the left map edge disappears # go right until the left map edge disappears
while self.TOOLS.isLeftBorder(img): while self.TOOLS.isLeftBorder(img):
@@ -324,7 +358,7 @@ class DFWINDOW:
# And those are the horrizontal steps # And those are the horrizontal steps
steps_horrizontal = steps_right steps_horrizontal = steps_right
logger.debug(f"Map is about {steps_horrizontal} steps horrizontal. Current step is {steps_right}") logger.debug(f"Map is about {steps_horrizontal+1} steps horrizontal. Current index is {steps_right}")
self._gridx_max = steps_horrizontal self._gridx_max = steps_horrizontal
self._gridy_max = steps_vertical self._gridy_max = steps_vertical
@@ -361,52 +395,54 @@ class DFWINDOW:
# (max*size) is too far, so we subract the ofset/border from the right map edge # (max*size) is too far, so we subract the ofset/border from the right map edge
self._map_width = ( self._map_width = (
(img.shape[1] - cal_left_border) # Grid x = 0 (self.contentWidth - cal_left_border) # Grid x = 0
+ ((self._gridx_max - 1) * self._step_size_x) # All the middle + ((self._gridx_max - 1) * self._step_size_x) # All the middle
+ cal_right_border # grid x = max + cal_right_border # grid x = max
) )
logger.trace(f"|{self.contentWidth} - {cal_left_border}|({self._gridx_max} - 1) * {self._step_size_x}|{cal_right_border}|")
logger.trace(f"{self._map_width} = |{self.contentWidth - cal_left_border}|{(self._gridx_max - 1) * self._step_size_x}|{cal_right_border}|")
self._map_height = ( self._map_height = (
(img.shape[0] - cal_top_border) # Grid x = 0 (img.shape[0] - cal_top_border) # Grid x = 0
+ ((self._gridy_max - 1) * self._step_size_y) # All the middle + ((self._gridy_max - 1) * self._step_size_y) # All the middle
+ cal_right_border # grid x = max + cal_bottom_border # grid x = max
) )
self.setGridPos(0, 0) self.setGridPos(0, 0)
logger.debug(f"Map dimensions calculated as {self._map_width} x {self._map_height}") logger.debug(f"Map dimensions calculated as {self._map_width} x {self._map_height}")
logger.info( logger.debug(
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._gridx_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") # 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 = ( # tt_setup = (
r"gc.enable() ; import cv2 ; import numpy as np ; timg = cv2.imread('./test_img.png', cv2.IMREAD_UNCHANGED)" # 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,
) # )
tt2 = timeit.Timer( # tt2 = timeit.Timer(
"np.where(np.max(cv2.cvtColor(timg, cv2.COLOR_BGR2GRAY), axis=0) > 25)[0][0]", # "np.where(np.max(cv2.cvtColor(timg, cv2.COLOR_BGR2GRAY), axis=0) > 25)[0][0]",
setup=tt_setup, # setup=tt_setup,
) # )
tt3 = timeit.Timer( # tt3 = timeit.Timer(
"np.where(np.max(cv2.cvtColor(timg, cv2.COLOR_BGRA2GRAY), axis=0) > 25)[0][0]", # "np.where(np.max(cv2.cvtColor(timg, cv2.COLOR_BGRA2GRAY), axis=0) > 25)[0][0]",
setup=tt_setup, # setup=tt_setup,
) # )
num_tests = 80 # num_tests = 80
r1 = tt1.timeit(number=num_tests) # r1 = tt1.timeit(number=num_tests)
r2 = tt2.timeit(number=num_tests) # r2 = tt2.timeit(number=num_tests)
r3 = tt3.timeit(number=num_tests) # r3 = tt3.timeit(number=num_tests)
logger.debug("Pause here for testing") logger.debug("Pause here for testing")
@@ -455,12 +491,118 @@ class DFWINDOW:
self._map_height = calib_info["map_height"] self._map_height = calib_info["map_height"]
self._map_width = calib_info["map_width"] self._map_width = calib_info["map_width"]
def getPanoramaMap(self): def addToCanvas(self, tile, x: int, y: int) -> tuple[int, int]:
# self.test1() # calculate safe (in bounds) abs pos of far end
# return safe_farx = min(x + tile.shape[1], self.map_canvas.shape[1])
safe_fary = min(y + tile.shape[0], self.map_canvas.shape[0])
safe_width = safe_farx - x
safe_height = safe_fary - y
self.map_canvas[y:safe_fary, x:safe_farx] = tile[: (safe_fary - y), : (safe_farx - x)]
logger.trace(f"Added {safe_width}x{safe_height} of tile ({tile.shape[1]}x{tile.shape[0]}) at {x},{y} ")
return (int(safe_width), int(safe_height))
def getPanoramaMap(self):
self.calibrateGrid() self.calibrateGrid()
# self.test_saveGrids()
# self.test_loadCalib() # Create the big_map canvas
canvas_width = self.contentWidth + (self.stepSizeX * (self.maxGridX + 1 + 1))
canvas_height = self.contentHeight + (self.stepSizeY * (self.maxGridY + 1 + 1))
self.map_canvas = np.zeros((canvas_height, canvas_width, 4), dtype=np.uint8)
# We want to cap from the content area, minus and black borders.
# starting at canvas_pos of 0,0 Add cap to the canvas
# Then we pan down almost enough to push everything up off the screen
# Then we cap the new stuff, row starting at max(firstNotBlackY, contentHeight - (amount we paned down))
# at canvas_pos add cap to canvas
# increase canvas_pos.y by that new amount
# if we already have a bottom bar, or we are at last grid, break out, otherwise loop
if 1 == 1:
# The initial setup
new_x = self.contentWidth
new_y = self.contentHeight
canvas_pos = [0, 0]
self.setGridPos(0, 0)
# Never do more than this many loops
sanity_steps_left = self.maxGridY + 1
while sanity_steps_left > 0:
# Capture a tile
img = self.capContent()
cap_start_x = max(self.TOOLS.firstNotBlackX(img), self.contentWidth - new_x)
cap_start_y = max(self.TOOLS.firstNotBlackY(img), self.contentHeight - new_y)
# use min with other restriction if needed in the future min(lastNotBlack,Other_limit)
cap_end_x = self.TOOLS.lastNotBlackX(img)
cap_end_y = self.TOOLS.lastNotBlackY(img)
pixels_added = self.addToCanvas(
img[cap_start_y : cap_end_y + 1, cap_start_x : cap_end_x + 1], canvas_pos[0], canvas_pos[1]
)
canvas_pos[1] += pixels_added[1]
# Reasons to finish this column:
# - pixels_added[1] < cap_height
# - (with cur logic) cap_height < self.contentHeight
# - self.curGridY >= self.maxGridY
logger.trace(f"{cap_start_y=} {cap_end_y=} {self.contentHeight=} {pixels_added=} {canvas_pos=}")
logger.trace(f"{self.curGridPos=} {self.map_canvas.shape=}")
sanity_steps_left -= 1 # Prevent runaway loops
if not (
(cap_end_y + 1 < self.contentHeight)
or (pixels_added[1] < ((cap_end_y + 1) - cap_start_y))
or (self.curGridY >= self.maxGridY)
or (canvas_pos[1] >= self.map_canvas.shape[0])
):
# pan down for more map, but watch limits
steps_to_pan_down = min(self.maxGridY - self.curGridY, math.floor(self.contentHeight / self.stepSizeY))
self.setGridPos(0, self.curGridY + steps_to_pan_down)
new_y = steps_to_pan_down * self.stepSizeY
else:
break
if sanity_steps_left < 1:
logger.debug(f"Our loop in the Y axis ran over. {sanity_steps_left=}")
if self.map_canvas is not None:
cv2.imwrite("./test_canvas.png", self.map_canvas)
# if 1 == 0:
# self.setGridPos(0, 0)
# canvas_x = 0
# canvas_y = 0
# img = self.capContent()
# startx = self.TOOLS.firstNotBlackX(img)
# starty = self.TOOLS.firstNotBlackY(img)
# img_ul = img[starty:, startx:]
# # cv2.rectangle(img, (startx, starty), (self.contentWidth, self.contentHeight), (255, 255, 255, 255), 3)
# logger.debug(f"img_ul is {img_ul.shape[1]} x {img_ul.shape[0]}")
# last_add = self.addToCanvas(img_ul, 0, 0)
# steps_to_pan_down = math.floor(self.contentHeight / self.stepSizeY)
# logger.debug(f"{startx=} {starty=} {steps_to_pan_down=}")
# self.setGridPos(0, steps_to_pan_down)
# time.sleep(self.sleep_after_panning)
# img = self.capContent()
# new_starty = self.contentHeight - (steps_to_pan_down * self.stepSizeY)
# img_next = img[new_starty:, startx:]
# logger.debug(f"img_next is {img_next.shape[1]} x {img_next.shape[0]}")
# # cv2.rectangle(img, (startx, new_starty), (self.contentWidth, self.contentHeight), (255, 0, 0), 3)
# self.addToCanvas(img_next, 0, last_add[1])
# cv2.imwrite("./test_canvas.png", self.map_canvas)
# a = 1
# logger.debug(f"{new_starty=}")
logger.debug("place to break")
return None return None

View File

@@ -2,8 +2,6 @@ import json
import subprocess import subprocess
import pydantic as pyd import pydantic as pyd
import cv2
import numpy as np
from .dfwindow import DFWINDOW from .dfwindow import DFWINDOW
from .mylogging import logger, setup_logging from .mylogging import logger, setup_logging
@@ -561,6 +559,7 @@ def test5():
if __name__ == "__main__": if __name__ == "__main__":
setup_logging(level="DEBUG", enqueue=False, console_show_time=False, console_tracebacks=True) # level 5 is TRACE
setup_logging(level=5, enqueue=False, console_show_time=False, console_tracebacks=True)
test5() test5()