Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ docs/_build/

# GitHub App credentials
gha-creds-*.json

# pytest-benchmark storage
.benchmarks/
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ dev = [
"pdoc>=15.0.4,<17",
"pytest-cov>=7.0.0",
"zmqtt>=0.2.0,<0.3.0",
"pytest-benchmark>=5.1.0",
]

[tool.hatch.build.targets.sdist]
Expand Down
20 changes: 13 additions & 7 deletions roborock/map/b01_q10_map_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,8 +801,8 @@ def parse_packet(self, packet: Q10MapPacket) -> ParsedMapData:
"""
return self.parsed_from_packet(packet)

def parsed_from_packet(self, packet: Q10MapPacket) -> ParsedMapData:
"""Render a (possibly erase-modified) packet into a PNG + ``MapData``."""
def map_data_from_packet(self, packet: Q10MapPacket) -> MapData:
"""Build MapData with a rendered base image, without PNG serialization."""
image = self._render(packet)

map_data = MapData()
Expand Down Expand Up @@ -830,17 +830,23 @@ def parsed_from_packet(self, packet: Q10MapPacket) -> ParsedMapData:
if packet.carpet_mask is not None:
map_data.carpet_map = {i for i, value in enumerate(packet.carpet_mask) if value}

return map_data

def parsed_from_packet(self, packet: Q10MapPacket) -> ParsedMapData:
"""Render a (possibly erase-modified) packet into a PNG + ``MapData``."""
map_data = self.map_data_from_packet(packet)
image_bytes = io.BytesIO()
image.save(image_bytes, format=_MAP_FILE_FORMAT)
if map_data.image is not None:
map_data.image.data.save(image_bytes, format=_MAP_FILE_FORMAT)
return ParsedMapData(image_content=image_bytes.getvalue(), map_data=map_data)

def _render(self, packet: Q10MapPacket) -> Image.Image:
"""Render the Q10 grid with the V1 map palette."""
palette = _build_palette(packet.grid, packet.width)
rgba = bytearray()
for value in packet.grid:
rgba.extend(palette[value])
img = Image.frombytes("RGBA", (packet.width, packet.height), bytes(rgba))
flat_palette = [channel for color in palette for channel in color]
p_img = Image.frombytes("P", (packet.width, packet.height), packet.grid)
p_img.putpalette(flat_palette, rawmode="RGBA")
img = p_img.convert("RGBA")
# The ss07 grid is stored top-down (row 0 = top of the home), so it is
# rendered as-is -- unlike the V1/Q7 convention, no vertical flip.
scale = self._config.map_scale
Expand Down
9 changes: 5 additions & 4 deletions roborock/map/b01_q10_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,9 @@ def render_q10_map(
# areas disappear (as the app shows).
render_packet = erased_packet(packet, cells)

parsed = parser.parsed_from_packet(render_packet)
if parsed.image_content is None or parsed.map_data is None:
map_data = parser.map_data_from_packet(render_packet)
if map_data.image is None:
raise RoborockException("Failed to render Q10 map image")
map_data = parsed.map_data

has_drawables = False
if trace_calibration is not None and trace is not None:
Expand All @@ -128,7 +127,9 @@ def render_q10_map(
if has_drawables:
return _draw_map_content(map_data, config=config)

return parsed.image_content
buffer = io.BytesIO()
map_data.image.data.save(buffer, format="PNG")
return buffer.getvalue()


def solve_q10_calibration(
Expand Down
26 changes: 17 additions & 9 deletions roborock/map/room_colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,31 @@ def adjacency_aware_room_colors(
if width <= 0:
return {}

lut = [room_id_from_cell(i) for i in range(256)]
room_ids: set[int] = set()
neighbors: dict[int, set[int]] = {}
for index, value in enumerate(grid):
room_id = room_id_from_cell(value)
room_id = lut[value] if 0 <= value < 256 else room_id_from_cell(value)
if room_id is None:
continue
room_ids.add(room_id)
neighbors.setdefault(room_id, set())

for neighbor_index in (index - 1 if index % width else -1, index - width):
if neighbor_index < 0:
continue
neighbor_id = room_id_from_cell(grid[neighbor_index])
if neighbor_id is None or neighbor_id == room_id:
continue
neighbors[room_id].add(neighbor_id)
neighbors.setdefault(neighbor_id, set()).add(room_id)
if index % width:
left_val = grid[index - 1]
if left_val != value:
neighbor_id = lut[left_val] if 0 <= left_val < 256 else room_id_from_cell(left_val)
if neighbor_id is not None and neighbor_id != room_id:
neighbors[room_id].add(neighbor_id)
neighbors.setdefault(neighbor_id, set()).add(room_id)

if index >= width:
up_val = grid[index - width]
if up_val != value:
neighbor_id = lut[up_val] if 0 <= up_val < 256 else room_id_from_cell(up_val)
if neighbor_id is not None and neighbor_id != room_id:
neighbors[room_id].add(neighbor_id)
neighbors.setdefault(neighbor_id, set()).add(room_id)

candidates: list[Color] = []
for palette_id in map(int, ColorsPalette.ROOM_COLORS):
Expand Down
Loading
Loading