@@ -57,7 +57,10 @@ def parse(self, payload: bytes) -> ParsedMapData:
5757 room_names = _extract_room_names (parsed )
5858
5959 room_pixels = _assign_room_pixels (parsed , grid , size_x = size_x , size_y = size_y )
60- image = _render_occupancy_image (grid , room_pixels , size_x = size_x , size_y = size_y , scale = self ._config .map_scale )
60+ carpet_pixels = _carpet_pixel_indices (parsed , grid , size_x = size_x , size_y = size_y )
61+ image = _render_occupancy_image (
62+ grid , room_pixels , carpet_pixels , size_x = size_x , size_y = size_y , scale = self ._config .map_scale
63+ )
6164
6265 map_data = MapData ()
6366 map_data .image = ImageData (
@@ -80,6 +83,9 @@ def parse(self, payload: bytes) -> ParsedMapData:
8083 has_drawables = _place_poses (map_data , parsed , projector )
8184 map_data .rooms = _extract_rooms (parsed , projector , room_names )
8285 has_drawables = has_drawables or bool (map_data .rooms )
86+ if carpet_pixels :
87+ # Same contract as the Q10 parser: flat top-down grid indices.
88+ map_data .carpet_map = {(size_y - 1 - index // size_x ) * size_x + index % size_x for index in carpet_pixels }
8389
8490 if has_drawables :
8591 generator = _create_image_generator (
@@ -302,8 +308,26 @@ def _assign_room_pixels(parsed: RobotMap, grid: bytes, *, size_x: int, size_y: i
302308 return assignment
303309
304310
311+ def _carpet_pixel_indices (parsed : RobotMap , grid : bytes , * , size_x : int , size_y : int ) -> set [int ]:
312+ """Raw-grid indices of floor pixels covered by enabled carpets."""
313+ head = parsed .mapHead
314+ resolution = head .resolution or 0.05
315+ indices : set [int ] = set ()
316+ for carpet in parsed .carpetInfo :
317+ if not carpet .points or (carpet .HasField ("enabled" ) and not carpet .enabled ):
318+ continue
319+ cols = [int ((point .x - head .minX ) / resolution ) for point in carpet .points ]
320+ rows = [int ((point .y - head .minY ) / resolution ) for point in carpet .points ]
321+ for row in range (max (min (rows ), 0 ), min (max (rows ), size_y - 1 ) + 1 ):
322+ for col in range (max (min (cols ), 0 ), min (max (cols ), size_x - 1 ) + 1 ):
323+ index = row * size_x + col
324+ if grid [index ] == _FLOOR :
325+ indices .add (index )
326+ return indices
327+
328+
305329def _render_occupancy_image (
306- grid : bytes , room_pixels : bytearray , * , size_x : int , size_y : int , scale : int
330+ grid : bytes , room_pixels : bytearray , carpet_pixels : set [ int ], * , size_x : int , size_y : int , scale : int
307331) -> Image .Image :
308332 """Render the B01 occupancy grid with per-room colors."""
309333
@@ -332,9 +356,13 @@ def _render_occupancy_image(
332356 rgba = bytearray ()
333357 for index , value in enumerate (grid ):
334358 if value == _FLOOR and (room_id := room_pixels [index ]):
335- rgba . extend ( room_colors .get (room_id , floor ) )
359+ color = room_colors .get (room_id , floor )
336360 else :
337- rgba .extend (base_colors .get (value , floor ))
361+ color = base_colors .get (value , floor )
362+ if index in carpet_pixels and (index // size_x + index % size_x ) % 2 == 0 :
363+ # Checkerboard stipple, like the V1 carpet texture.
364+ color = tuple (min (channel + 60 , 255 ) for channel in color [:3 ]) + (255 ,)
365+ rgba .extend (color )
338366
339367 # RGBA so the shared V1 ImageGenerator can alpha-composite overlay glyphs.
340368 img = Image .frombytes ("RGBA" , (size_x , size_y ), bytes (rgba ))
0 commit comments