Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/code_quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
# name: Check code with pylint
steps:
- name: Checkout the repository
uses: actions/checkout@v4
uses: actions/checkout@v5

- name: Set up Python 3
uses: actions/setup-python@v5
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
steps:
# Step 1: Checkout the repository
- uses: actions/checkout@v4
- uses: actions/checkout@v5

# Step 2: Set up Python
- name: Set up Python 3.12
Expand Down
12 changes: 8 additions & 4 deletions SCR/valetudo_map_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from .config.drawable import Drawable
from .config.drawable_elements import DrawableElement, DrawingConfig
from .config.enhanced_drawable import EnhancedDrawable
from .config.utils import webp_bytes_to_pil
from .config.rand256_parser import RRMapParser
from .config.shared import CameraShared, CameraSharedManager
from .config.types import (
Expand All @@ -15,7 +14,10 @@
SnapshotStore,
TrimCropData,
UserLanguageStore,
WebPBytes,
JsonType,
PilPNG,
NumpyArray,
ImageSize,
)
from .hypfer_handler import HypferMapImageHandler
from .rand256_handler import ReImageHandler
Expand All @@ -41,6 +43,8 @@
"RoomsProperties",
"TrimCropData",
"CameraModes",
"WebPBytes",
"webp_bytes_to_pil",
"JsonType",
"PilPNG",
"NumpyArray",
"ImageSize",
]
93 changes: 93 additions & 0 deletions SCR/valetudo_map_parser/config/async_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Async utility functions for making NumPy and PIL operations truly async."""

import asyncio
import io
from typing import Any, Callable

import numpy as np
from numpy import rot90
from PIL import Image


async def make_async(func: Callable, *args, **kwargs) -> Any:
"""Convert a synchronous function to async by yielding control to the event loop."""
return await asyncio.to_thread(func, *args, **kwargs)


class AsyncNumPy:
"""Async wrappers for NumPy operations that yield control to the event loop."""

@staticmethod
async def async_copy(array: np.ndarray) -> np.ndarray:
"""Async array copying."""
return await make_async(np.copy, array)

@staticmethod
async def async_full(
shape: tuple, fill_value: Any, dtype: np.dtype = None
) -> np.ndarray:
"""Async array creation with fill value."""
return await make_async(np.full, shape, fill_value, dtype=dtype)

@staticmethod
async def async_rot90(array: np.ndarray, k: int = 1) -> np.ndarray:
"""Async array rotation."""
return await make_async(rot90, array, k)


class AsyncPIL:
"""Async wrappers for PIL operations that yield control to the event loop."""

@staticmethod
async def async_fromarray(array: np.ndarray, mode: str = "RGBA") -> Image.Image:
"""Async PIL Image creation from NumPy array."""
return await make_async(Image.fromarray, array, mode)

@staticmethod
async def async_resize(
image: Image.Image, size: tuple, resample: int = None
) -> Image.Image:
"""Async image resizing."""
if resample is None:
resample = Image.LANCZOS
return await make_async(image.resize, size, resample)

@staticmethod
async def async_save_to_bytes(
image: Image.Image, format_type: str = "WEBP", **kwargs
) -> bytes:
"""Async image saving to bytes."""

def save_to_bytes():
buffer = io.BytesIO()
image.save(buffer, format=format_type, **kwargs)
return buffer.getvalue()

return await make_async(save_to_bytes)


class AsyncParallel:
"""Helper functions for parallel processing with asyncio.gather()."""

@staticmethod
async def parallel_data_preparation(*tasks):
"""Execute multiple data preparation tasks in parallel."""
return await asyncio.gather(*tasks, return_exceptions=True)

@staticmethod
async def parallel_array_operations(base_array: np.ndarray, operations: list):
"""Execute multiple array operations in parallel on copies of the base array."""

# Create tasks for parallel execution
tasks = []
for operation_func, *args in operations:
# Each operation works on a copy of the base array
array_copy = await AsyncNumPy.async_copy(base_array)
tasks.append(operation_func(array_copy, *args))

# Execute all operations in parallel
results = await asyncio.gather(*tasks, return_exceptions=True)

# Filter out exceptions and return successful results
successful_results = [r for r in results if not isinstance(r, Exception)]
return successful_results
7 changes: 4 additions & 3 deletions SCR/valetudo_map_parser/config/auto_crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from numpy import rot90
from scipy import ndimage

from .async_utils import AsyncNumPy, make_async
from .types import Color, NumpyArray, TrimCropData, TrimsData
from .utils import BaseHandler

Expand Down Expand Up @@ -364,18 +365,18 @@ async def async_rotate_the_image(
) -> NumpyArray:
"""Rotate the image and return the new array."""
if rotate == 90:
rotated = rot90(trimmed)
rotated = await AsyncNumPy.async_rot90(trimmed)
self.crop_area = [
self.trim_left,
self.trim_up,
self.trim_right,
self.trim_down,
]
elif rotate == 180:
rotated = rot90(trimmed, 2)
rotated = await AsyncNumPy.async_rot90(trimmed, 2)
self.crop_area = self.auto_crop
elif rotate == 270:
rotated = rot90(trimmed, 3)
rotated = await AsyncNumPy.async_rot90(trimmed, 3)
self.crop_area = [
self.trim_left,
self.trim_up,
Expand Down
Loading