Spaces:
Sleeping
Sleeping
krau
commited on
init commit
Browse files- .env.example +19 -0
- .gitignore +8 -0
- .python-version +1 -0
- Dockerfile +5 -0
- README.md +1 -0
- common.py +242 -0
- config.py +9 -0
- logger.py +8 -0
- main.py +417 -0
- onnx_infer.py +243 -0
- pyproject.toml +24 -0
- requirements.txt +38 -0
- settings.toml +19 -0
- sr_queue.py +288 -0
- test_common.py +115 -0
- uv.lock +576 -0
.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SRAPI_PROVIDER = "CPUExecutionProvider"
|
| 2 |
+
SRAPI_LOG_LEVEL = "INFO"
|
| 3 |
+
SRAPI_TOKEN = "token"
|
| 4 |
+
SRAPI_REDIS_URL = "redis://localhost:6379"
|
| 5 |
+
SRAPI_HOST = "0.0.0.0"
|
| 6 |
+
SRAPI_PORT = 39721
|
| 7 |
+
SRAPI_TEMP_DIR = "./temp"
|
| 8 |
+
SRAPI_OUTPUT_DIR = "./output"
|
| 9 |
+
SRAPI_MAX_TIMEOUT = 300
|
| 10 |
+
SRAPI_TIMEOUT = 30
|
| 11 |
+
SRAPI_MAX_THREAD = 8
|
| 12 |
+
SRAPI_MODE = "master"
|
| 13 |
+
SRAPI_MASTER_URL = "http://localhost:39721"
|
| 14 |
+
SRAPI_MASTER_TOKEN = "token"
|
| 15 |
+
SRAPI_WORKER_ID = "special_id"
|
| 16 |
+
SRAPI_WORKER_URL = "http://localhost:39721"
|
| 17 |
+
SRAPI_WORKER_CHECK_INTERVAL = 5
|
| 18 |
+
SRAPI_WORKER_EXPIRE = 120
|
| 19 |
+
SRAPI_REGISTER_INTERVAL = 30
|
.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
output*/
|
| 3 |
+
__pycache__/
|
| 4 |
+
# Ignore dynaconf secret files
|
| 5 |
+
.secrets.*
|
| 6 |
+
temp*/
|
| 7 |
+
.env
|
| 8 |
+
models/
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.11
|
Dockerfile
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM ghcr.io/astral-sh/uv:python3.11-bookworm-slim
|
| 2 |
+
COPY . /sr_api
|
| 3 |
+
WORKDIR /sr_api
|
| 4 |
+
RUN apt-get update && apt-get install ffmpeg libsm6 libxext6 -y && uv sync --frozen --no-dev
|
| 5 |
+
ENTRYPOINT [ "uv" ,"run", "python", "main.py" ]
|
README.md
CHANGED
|
@@ -6,6 +6,7 @@ colorTo: yellow
|
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: gpl-3.0
|
|
|
|
| 9 |
---
|
| 10 |
|
| 11 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: gpl-3.0
|
| 9 |
+
app_port: 39721
|
| 10 |
---
|
| 11 |
|
| 12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
common.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pathlib
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
import cv2
|
| 5 |
+
import numpy as np
|
| 6 |
+
import redis
|
| 7 |
+
from logger import logger
|
| 8 |
+
|
| 9 |
+
from config import settings
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
logger.info(
|
| 13 |
+
f"Connecting to Redis: {settings.get('redis_url', 'redis://localhost:6379')}"
|
| 14 |
+
)
|
| 15 |
+
redis_client = redis.from_url(settings.get("redis_url", "redis://localhost:6379"))
|
| 16 |
+
logger.info(f"Redis ping: {redis_client.ping()}")
|
| 17 |
+
except Exception as e:
|
| 18 |
+
logger.error(f"Failed to connect to Redis: {e}")
|
| 19 |
+
exit(1)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@dataclass
|
| 23 |
+
class ModelInfo:
|
| 24 |
+
name: str = ""
|
| 25 |
+
path: str = ""
|
| 26 |
+
scale: int = 4
|
| 27 |
+
algo: str = ""
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
BASE_STREAM_NAME = (
|
| 31 |
+
"super_resolution_api_queue"
|
| 32 |
+
if not settings.get("worker_id")
|
| 33 |
+
else f"super_resolution_api_queue_{settings.get('worker_id')}"
|
| 34 |
+
)
|
| 35 |
+
WORKER_KEY_PREFIX = "super_resolution_api_worker_"
|
| 36 |
+
DISTRIBUTED_STREAM_NAME = "super_resolution_api_distributed_queue"
|
| 37 |
+
RESULT_KEY_PREFIX = (
|
| 38 |
+
"super_resolution_api_result_"
|
| 39 |
+
if not settings.get("worker_id")
|
| 40 |
+
else f"super_resolution_api_result_{settings.get('worker_id')}_"
|
| 41 |
+
)
|
| 42 |
+
PROGRESS_TIMEOUT = settings.get("timeout", 30)
|
| 43 |
+
MAX_ALLOWED_TIMEOUT = settings.get("max_timeout", 300)
|
| 44 |
+
MAX_THREAD = settings.get("max_thread", 8)
|
| 45 |
+
MODEL_NAME_DEFAULT = "x4_Anime_6B-Official"
|
| 46 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1 = "x4_JP_Illustration-fix1"
|
| 47 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX2 = "x4_JP_Illustration-fix2"
|
| 48 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1_D = "x4_JP_Illustration-fix1-d"
|
| 49 |
+
MODEL_NAME_X4_ANIME_6B_OFFICIAL = "x4_Anime_6B-Official"
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
model_Anime_Official = ModelInfo(
|
| 53 |
+
MODEL_NAME_X4_ANIME_6B_OFFICIAL,
|
| 54 |
+
"models/x4_Anime_6B-Official.onnx",
|
| 55 |
+
4,
|
| 56 |
+
"real-esrgan",
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
model_JP_Illustration_fix1 = ModelInfo(
|
| 60 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1,
|
| 61 |
+
"models/x4_jp_Illustration-fix1.onnx",
|
| 62 |
+
4,
|
| 63 |
+
"real-hatgan",
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
model_JP_Illustration_fix2 = ModelInfo(
|
| 68 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX2,
|
| 69 |
+
"models/x4_jp_Illustration-fix2.onnx",
|
| 70 |
+
4,
|
| 71 |
+
"real-esrgan",
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
model_JP_Illustration_fix1_d = ModelInfo(
|
| 75 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1_D,
|
| 76 |
+
"models/x4_jp_Illustration-fix1-d.onnx",
|
| 77 |
+
4,
|
| 78 |
+
"real-esrgan",
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
models = {
|
| 82 |
+
MODEL_NAME_X4_ANIME_6B_OFFICIAL: model_Anime_Official,
|
| 83 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1: model_JP_Illustration_fix1,
|
| 84 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX2: model_JP_Illustration_fix2,
|
| 85 |
+
MODEL_NAME_X4_JP_ILLUSTRATION_FIX1_D: model_JP_Illustration_fix1_d,
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def get_image_size(image_path: pathlib.Path) -> tuple[int, int]:
|
| 90 |
+
"""
|
| 91 |
+
return: (width, height)
|
| 92 |
+
"""
|
| 93 |
+
img = cv2.imread(str(image_path))
|
| 94 |
+
if img is None:
|
| 95 |
+
raise Exception(f"Failed to load image: {image_path}")
|
| 96 |
+
return img.shape[1], img.shape[0]
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@dataclass
|
| 100 |
+
class TileInfo:
|
| 101 |
+
x: int
|
| 102 |
+
y: int
|
| 103 |
+
filpath: pathlib.Path
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def split_image(
|
| 107 |
+
img_path: pathlib.Path,
|
| 108 |
+
save_dir: pathlib.Path,
|
| 109 |
+
grid_size: tuple[int, int],
|
| 110 |
+
overlap: int = 16,
|
| 111 |
+
) -> list[TileInfo]:
|
| 112 |
+
save_path = pathlib.Path(save_dir)
|
| 113 |
+
save_path.mkdir(parents=True, exist_ok=True)
|
| 114 |
+
|
| 115 |
+
img = cv2.imread(str(img_path))
|
| 116 |
+
if img is None:
|
| 117 |
+
raise Exception(f"Failed to load image: {img_path}")
|
| 118 |
+
|
| 119 |
+
height, width = img.shape[:2]
|
| 120 |
+
rows, cols = grid_size
|
| 121 |
+
|
| 122 |
+
base_h = height // rows
|
| 123 |
+
base_w = width // cols
|
| 124 |
+
|
| 125 |
+
tiles_info = []
|
| 126 |
+
|
| 127 |
+
for row in range(rows):
|
| 128 |
+
for col in range(cols):
|
| 129 |
+
x1 = max(0, col * base_w - overlap)
|
| 130 |
+
y1 = max(0, row * base_h - overlap)
|
| 131 |
+
x2 = min(width, (col + 1) * base_w + overlap)
|
| 132 |
+
y2 = min(height, (row + 1) * base_h + overlap)
|
| 133 |
+
|
| 134 |
+
tile = img[y1:y2, x1:x2]
|
| 135 |
+
tile_name = f"{img_path.stem}_tile_{row}_{col}.png"
|
| 136 |
+
tile_path = save_path / tile_name
|
| 137 |
+
cv2.imwrite(str(tile_path), tile)
|
| 138 |
+
tiles_info.append(TileInfo(col, row, tile_path))
|
| 139 |
+
|
| 140 |
+
return tiles_info
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def merge_sr_tiles(
|
| 144 |
+
tiles: list[TileInfo],
|
| 145 |
+
output: pathlib.Path,
|
| 146 |
+
original_size: tuple[int, int],
|
| 147 |
+
scale: int,
|
| 148 |
+
overlap: int = 16,
|
| 149 |
+
):
|
| 150 |
+
"""
|
| 151 |
+
合并超分辨率后的图块
|
| 152 |
+
|
| 153 |
+
tiles: 超分辨率后的图块信息列表, 需要根据 filepath 读取图块, 根据 x, y 位置信息进行拼接
|
| 154 |
+
output: 合并后的图片保存路径
|
| 155 |
+
original_size: 原始图片的尺寸
|
| 156 |
+
overlap 为原始图片切割时设定的重叠像素数
|
| 157 |
+
scale 为超分辨率倍数
|
| 158 |
+
"""
|
| 159 |
+
# Calculate output dimensions
|
| 160 |
+
logger.debug(
|
| 161 |
+
f"正在合并 {len(tiles)} 张超分辨率图块, 原尺寸: {original_size}, 缩放倍数: {scale}"
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
width, height = original_size
|
| 165 |
+
out_width = width * scale
|
| 166 |
+
out_height = height * scale
|
| 167 |
+
output_img = np.zeros((out_height, out_width, 3), dtype=np.uint8)
|
| 168 |
+
|
| 169 |
+
# Calculate base tile sizes
|
| 170 |
+
rows = max([t.y for t in tiles]) + 1
|
| 171 |
+
cols = max([t.x for t in tiles]) + 1
|
| 172 |
+
base_h = height // rows
|
| 173 |
+
base_w = width // cols
|
| 174 |
+
|
| 175 |
+
# Scale dimensions
|
| 176 |
+
scaled_base_h = base_h * scale
|
| 177 |
+
scaled_base_w = base_w * scale
|
| 178 |
+
scaled_overlap = overlap * scale
|
| 179 |
+
|
| 180 |
+
for tile_info in tiles:
|
| 181 |
+
# Read tile
|
| 182 |
+
tile = cv2.imread(str(tile_info.filpath))
|
| 183 |
+
if tile is None:
|
| 184 |
+
raise Exception(f"Failed to load tile: {tile_info.filpath}")
|
| 185 |
+
|
| 186 |
+
# Calculate positions
|
| 187 |
+
x1 = max(0, tile_info.x * scaled_base_w - scaled_overlap)
|
| 188 |
+
y1 = max(0, tile_info.y * scaled_base_h - scaled_overlap)
|
| 189 |
+
x2 = min(out_width, (tile_info.x + 1) * scaled_base_w + scaled_overlap)
|
| 190 |
+
y2 = min(out_height, (tile_info.y + 1) * scaled_base_h + scaled_overlap)
|
| 191 |
+
|
| 192 |
+
# Calculate blend mask for overlapping regions
|
| 193 |
+
h, w = y2 - y1, x2 - x1
|
| 194 |
+
blend_mask = np.ones((h, w, 1), dtype=np.float32)
|
| 195 |
+
|
| 196 |
+
# Apply feathering at edges
|
| 197 |
+
if tile_info.x > 0: # Left edge
|
| 198 |
+
blend_mask[:, :scaled_overlap] = np.linspace(0, 1, scaled_overlap).reshape(
|
| 199 |
+
1, -1, 1
|
| 200 |
+
)
|
| 201 |
+
if tile_info.x < cols - 1: # Right edge
|
| 202 |
+
blend_mask[:, -scaled_overlap:] = np.linspace(1, 0, scaled_overlap).reshape(
|
| 203 |
+
1, -1, 1
|
| 204 |
+
)
|
| 205 |
+
if tile_info.y > 0: # Top edge
|
| 206 |
+
blend_mask[:scaled_overlap, :] *= np.linspace(0, 1, scaled_overlap).reshape(
|
| 207 |
+
-1, 1, 1
|
| 208 |
+
)
|
| 209 |
+
if tile_info.y < rows - 1: # Bottom edge
|
| 210 |
+
blend_mask[-scaled_overlap:, :] *= np.linspace(
|
| 211 |
+
1, 0, scaled_overlap
|
| 212 |
+
).reshape(-1, 1, 1)
|
| 213 |
+
|
| 214 |
+
# Blend tiles
|
| 215 |
+
output_img[y1:y2, x1:x2] = (
|
| 216 |
+
output_img[y1:y2, x1:x2] * (1 - blend_mask)
|
| 217 |
+
+ tile[: y2 - y1, : x2 - x1] * blend_mask
|
| 218 |
+
).astype(np.uint8)
|
| 219 |
+
|
| 220 |
+
cv2.imwrite(output.as_posix(), output_img)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def calculate_grid(image_width, image_height, workers):
|
| 224 |
+
if workers <= 0:
|
| 225 |
+
raise ValueError("Worker count must be positive")
|
| 226 |
+
|
| 227 |
+
best_rows, best_cols = 1, workers
|
| 228 |
+
min_aspect_diff = float("inf")
|
| 229 |
+
|
| 230 |
+
for rows in range(1, workers + 1):
|
| 231 |
+
if workers % rows == 0:
|
| 232 |
+
cols = workers // rows
|
| 233 |
+
tile_width = image_width / cols
|
| 234 |
+
tile_height = image_height / rows
|
| 235 |
+
aspect_ratio = max(tile_width, tile_height) / min(tile_width, tile_height)
|
| 236 |
+
aspect_diff = aspect_ratio - 1
|
| 237 |
+
|
| 238 |
+
if aspect_diff < min_aspect_diff:
|
| 239 |
+
best_rows, best_cols = rows, cols
|
| 240 |
+
min_aspect_diff = aspect_diff
|
| 241 |
+
logger.debug(f"calculate_grid: {best_rows}x{best_cols}")
|
| 242 |
+
return best_rows, best_cols
|
config.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dynaconf import Dynaconf
|
| 2 |
+
|
| 3 |
+
settings = Dynaconf(
|
| 4 |
+
envvar_prefix="SRAPI",
|
| 5 |
+
settings_files=["settings.toml", ".secrets.toml"],
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
# `envvar_prefix` = export envvars with `export DYNACONF_FOO=bar`.
|
| 9 |
+
# `settings_files` = Load these files in the order.
|
logger.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from loguru import logger
|
| 3 |
+
|
| 4 |
+
import config
|
| 5 |
+
|
| 6 |
+
logger.remove()
|
| 7 |
+
|
| 8 |
+
logger.add(sys.stdout, level=config.settings.get("log_level", "INFO"))
|
main.py
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import pathlib
|
| 2 |
+
import pickle
|
| 3 |
+
import shutil
|
| 4 |
+
import tempfile
|
| 5 |
+
import threading
|
| 6 |
+
import time
|
| 7 |
+
|
| 8 |
+
import httpx
|
| 9 |
+
from fastapi import (
|
| 10 |
+
Depends,
|
| 11 |
+
FastAPI,
|
| 12 |
+
File,
|
| 13 |
+
Form,
|
| 14 |
+
Header,
|
| 15 |
+
HTTPException,
|
| 16 |
+
UploadFile,
|
| 17 |
+
status,
|
| 18 |
+
)
|
| 19 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 20 |
+
from fastapi.responses import FileResponse
|
| 21 |
+
from logger import logger
|
| 22 |
+
|
| 23 |
+
import common
|
| 24 |
+
from config import settings
|
| 25 |
+
from sr_queue import listen_distributed_queue, listen_queue
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
async def verify_token(x_token: str = Header()):
|
| 29 |
+
if x_token != settings.get("token"):
|
| 30 |
+
raise HTTPException(
|
| 31 |
+
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid X-Token"
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
app = FastAPI(
|
| 36 |
+
dependencies=[Depends(verify_token)],
|
| 37 |
+
title="Super Resolution API",
|
| 38 |
+
description="Super Resolution API for Anime and Illustration",
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
app.add_middleware(
|
| 42 |
+
CORSMiddleware,
|
| 43 |
+
allow_origins=["*"],
|
| 44 |
+
allow_credentials=True,
|
| 45 |
+
allow_methods=["*"],
|
| 46 |
+
allow_headers=["*"],
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def register_routes():
|
| 51 |
+
@app.get("/")
|
| 52 |
+
async def root():
|
| 53 |
+
return {
|
| 54 |
+
"message": f"Super Resolution API is running as {settings.get('mode', 'single')} mode"
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
@app.get("/result/{task_id}")
|
| 58 |
+
async def get_result(task_id: str):
|
| 59 |
+
result = common.redis_client.get(f"{common.RESULT_KEY_PREFIX}{task_id}")
|
| 60 |
+
if result is None:
|
| 61 |
+
raise HTTPException(
|
| 62 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
| 63 |
+
)
|
| 64 |
+
result_data: dict[str, str] = pickle.loads(result)
|
| 65 |
+
return {"result": result_data}
|
| 66 |
+
|
| 67 |
+
@app.get("/result/{task_id}/download")
|
| 68 |
+
async def download_result(task_id: str):
|
| 69 |
+
result = common.redis_client.get(f"{common.RESULT_KEY_PREFIX}{task_id}")
|
| 70 |
+
if result is None:
|
| 71 |
+
raise HTTPException(
|
| 72 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="Task not found"
|
| 73 |
+
)
|
| 74 |
+
result_data: dict[str, str] = pickle.loads(result)
|
| 75 |
+
if result_data["status"] != "success":
|
| 76 |
+
raise HTTPException(
|
| 77 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 78 |
+
detail=f"Task is {result_data['status']}",
|
| 79 |
+
)
|
| 80 |
+
file_path = pathlib.Path(result_data["path"])
|
| 81 |
+
if not file_path.exists():
|
| 82 |
+
raise HTTPException(
|
| 83 |
+
status_code=status.HTTP_404_NOT_FOUND, detail="File not found"
|
| 84 |
+
)
|
| 85 |
+
return FileResponse(
|
| 86 |
+
path=file_path,
|
| 87 |
+
filename=file_path.name,
|
| 88 |
+
headers={"Content-Length": str(file_path.stat().st_size)},
|
| 89 |
+
media_type="image/png",
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def register_single_sr_route():
|
| 94 |
+
@app.post("/sr")
|
| 95 |
+
async def super_resolution(
|
| 96 |
+
file: UploadFile | None = File(default=None),
|
| 97 |
+
tile_size: int = Form(default=64, ge=32, le=128),
|
| 98 |
+
scale: int = Form(default=4, ge=2, le=8),
|
| 99 |
+
skip_alpha: bool = Form(default=False),
|
| 100 |
+
resize_to: str | None = Form(default=None),
|
| 101 |
+
url: str | None = Form(default=None),
|
| 102 |
+
timeout: int = Form(
|
| 103 |
+
default=common.PROGRESS_TIMEOUT, ge=1, le=common.MAX_ALLOWED_TIMEOUT
|
| 104 |
+
),
|
| 105 |
+
model: str = Form(default=common.MODEL_NAME_DEFAULT),
|
| 106 |
+
):
|
| 107 |
+
if (file or url) is None:
|
| 108 |
+
raise HTTPException(
|
| 109 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 110 |
+
detail="No file or url provided",
|
| 111 |
+
)
|
| 112 |
+
temp = tempfile.NamedTemporaryFile(
|
| 113 |
+
dir=settings.get("temp_dir", "./temp"), delete=False
|
| 114 |
+
)
|
| 115 |
+
temp_path = pathlib.Path(temp.name)
|
| 116 |
+
try:
|
| 117 |
+
if url is not None:
|
| 118 |
+
async with httpx.AsyncClient() as client:
|
| 119 |
+
response = await client.get(url)
|
| 120 |
+
if response.status_code != 200:
|
| 121 |
+
return {"message": "Failed to download the image"}
|
| 122 |
+
if response.headers.get("Content-Type") not in [
|
| 123 |
+
"image/jpeg",
|
| 124 |
+
"image/png",
|
| 125 |
+
"image/webp",
|
| 126 |
+
]:
|
| 127 |
+
raise HTTPException(
|
| 128 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 129 |
+
detail="Invalid image format",
|
| 130 |
+
)
|
| 131 |
+
temp.write(response.content)
|
| 132 |
+
else:
|
| 133 |
+
if file.content_type not in ["image/jpeg", "image/png", "image/webp"]:
|
| 134 |
+
raise HTTPException(
|
| 135 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 136 |
+
detail="Invalid image format",
|
| 137 |
+
)
|
| 138 |
+
temp.write(file.file.read())
|
| 139 |
+
except Exception as e:
|
| 140 |
+
logger.error(f"process image error: {e}")
|
| 141 |
+
temp.close()
|
| 142 |
+
raise HTTPException(
|
| 143 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 144 |
+
detail="Failed to process the image",
|
| 145 |
+
)
|
| 146 |
+
resp = common.redis_client.xadd(
|
| 147 |
+
common.BASE_STREAM_NAME,
|
| 148 |
+
{
|
| 149 |
+
"data": pickle.dumps(
|
| 150 |
+
{
|
| 151 |
+
"input_image": temp_path,
|
| 152 |
+
"tile_size": tile_size,
|
| 153 |
+
"scale": scale,
|
| 154 |
+
"skip_alpha": skip_alpha,
|
| 155 |
+
"resize_to": resize_to,
|
| 156 |
+
"timeout": timeout,
|
| 157 |
+
"model": model,
|
| 158 |
+
}
|
| 159 |
+
),
|
| 160 |
+
},
|
| 161 |
+
)
|
| 162 |
+
xlength = common.redis_client.xlen(common.BASE_STREAM_NAME)
|
| 163 |
+
if xlength > 1:
|
| 164 |
+
common.redis_client.set(
|
| 165 |
+
f"{common.RESULT_KEY_PREFIX}{resp.decode('utf-8')}",
|
| 166 |
+
pickle.dumps({"status": "pending"}),
|
| 167 |
+
ex=86400,
|
| 168 |
+
)
|
| 169 |
+
logger.info(f"Task added to queue: {resp.decode('utf-8')}")
|
| 170 |
+
return {"message": "Success", "task_id": f"{resp.decode('utf-8')}"}
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def register_master():
|
| 174 |
+
@app.post("/register")
|
| 175 |
+
async def register_worker(
|
| 176 |
+
worker_id: str = Form(...),
|
| 177 |
+
worker_url: str = Form(...),
|
| 178 |
+
worker_token: str = Form(...),
|
| 179 |
+
):
|
| 180 |
+
try:
|
| 181 |
+
common.redis_client.set(
|
| 182 |
+
f"{common.WORKER_KEY_PREFIX}{worker_id}",
|
| 183 |
+
f"{worker_url}|{worker_token}",
|
| 184 |
+
ex=settings.get("worker_expire", 120),
|
| 185 |
+
)
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logger.error(f"Failed to register worker: {e}")
|
| 188 |
+
raise HTTPException(
|
| 189 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 190 |
+
detail="Failed to register worker",
|
| 191 |
+
)
|
| 192 |
+
return {"message": "Success"}
|
| 193 |
+
|
| 194 |
+
@app.get("/workers")
|
| 195 |
+
async def get_workers():
|
| 196 |
+
workers = common.redis_client.keys(f"{common.WORKER_KEY_PREFIX}*")
|
| 197 |
+
return {
|
| 198 |
+
"workers": [
|
| 199 |
+
{
|
| 200 |
+
"id": worker.decode("utf-8").split("_")[-1],
|
| 201 |
+
"data": common.redis_client.get(worker).decode("utf-8"),
|
| 202 |
+
}
|
| 203 |
+
for worker in workers
|
| 204 |
+
]
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
@app.post("/sr")
|
| 208 |
+
async def super_resolution(
|
| 209 |
+
file: UploadFile | None = File(default=None),
|
| 210 |
+
tile_size: int = Form(default=64, ge=32, le=128),
|
| 211 |
+
scale: int = Form(default=4, ge=2, le=8),
|
| 212 |
+
skip_alpha: bool = Form(default=False),
|
| 213 |
+
resize_to: str | None = Form(default=None),
|
| 214 |
+
url: str | None = Form(default=None),
|
| 215 |
+
timeout: int = Form(
|
| 216 |
+
default=common.PROGRESS_TIMEOUT, ge=1, le=common.MAX_ALLOWED_TIMEOUT
|
| 217 |
+
),
|
| 218 |
+
model: str = Form(default=common.MODEL_NAME_DEFAULT),
|
| 219 |
+
):
|
| 220 |
+
"""
|
| 221 |
+
将输入图片分块, 分发给存储在 Redis 中的 worker
|
| 222 |
+
|
| 223 |
+
对于客户端来说, 该 /sr 路由和 single 模式是兼容的
|
| 224 |
+
"""
|
| 225 |
+
if (file or url) is None:
|
| 226 |
+
raise HTTPException(
|
| 227 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 228 |
+
detail="No file or url provided",
|
| 229 |
+
)
|
| 230 |
+
|
| 231 |
+
workers = common.redis_client.keys(f"{common.WORKER_KEY_PREFIX}*")
|
| 232 |
+
if not workers:
|
| 233 |
+
raise HTTPException(
|
| 234 |
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
| 235 |
+
detail="No available worker",
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
input_temp = tempfile.NamedTemporaryFile(
|
| 239 |
+
dir=settings.get("temp_dir", "./temp"), delete=False
|
| 240 |
+
)
|
| 241 |
+
input_path = pathlib.Path(input_temp.name)
|
| 242 |
+
try:
|
| 243 |
+
if url is not None:
|
| 244 |
+
async with httpx.AsyncClient() as client:
|
| 245 |
+
response = await client.get(url)
|
| 246 |
+
if response.status_code != 200:
|
| 247 |
+
return {"message": "Failed to download the image"}
|
| 248 |
+
if response.headers.get("Content-Type") not in [
|
| 249 |
+
"image/jpeg",
|
| 250 |
+
"image/png",
|
| 251 |
+
"image/webp",
|
| 252 |
+
]:
|
| 253 |
+
raise HTTPException(
|
| 254 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 255 |
+
detail="Invalid image format",
|
| 256 |
+
)
|
| 257 |
+
input_temp.write(response.content)
|
| 258 |
+
else:
|
| 259 |
+
if file.content_type not in ["image/jpeg", "image/png", "image/webp"]:
|
| 260 |
+
raise HTTPException(
|
| 261 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 262 |
+
detail="Invalid image format",
|
| 263 |
+
)
|
| 264 |
+
input_temp.write(file.file.read())
|
| 265 |
+
except Exception as e:
|
| 266 |
+
logger.error(f"process image error: {e}")
|
| 267 |
+
input_temp.close()
|
| 268 |
+
raise HTTPException(
|
| 269 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 270 |
+
detail="Failed to process the image",
|
| 271 |
+
)
|
| 272 |
+
|
| 273 |
+
workers = common.redis_client.keys(f"{common.WORKER_KEY_PREFIX}*")
|
| 274 |
+
if not workers:
|
| 275 |
+
raise HTTPException(
|
| 276 |
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
| 277 |
+
detail="No available worker",
|
| 278 |
+
)
|
| 279 |
+
try:
|
| 280 |
+
save_dir = pathlib.Path(
|
| 281 |
+
f"{settings.get('output_dir','./output')}/{input_temp.name.split('/')[-1]}"
|
| 282 |
+
)
|
| 283 |
+
origin_width, origin_height = common.get_image_size(input_path)
|
| 284 |
+
origin_tiles_info = common.split_image(
|
| 285 |
+
input_path,
|
| 286 |
+
save_dir,
|
| 287 |
+
common.calculate_grid(origin_width, origin_height, len(workers)),
|
| 288 |
+
)
|
| 289 |
+
|
| 290 |
+
response = {}
|
| 291 |
+
|
| 292 |
+
for index, worker_key in enumerate(workers):
|
| 293 |
+
worker = common.redis_client.get(worker_key)
|
| 294 |
+
worker_url, token = worker.decode("utf-8").split("|")
|
| 295 |
+
tile_info = origin_tiles_info[index]
|
| 296 |
+
|
| 297 |
+
with open(tile_info.filpath, "rb") as tile_file:
|
| 298 |
+
async with httpx.AsyncClient() as client:
|
| 299 |
+
resp = await client.get(
|
| 300 |
+
worker_url + "/", headers={"X-Token": token}
|
| 301 |
+
)
|
| 302 |
+
if resp.status_code != 200:
|
| 303 |
+
raise Exception(f"Worker {worker_url} is not available")
|
| 304 |
+
resp = await client.post(
|
| 305 |
+
url=f"{worker_url}/sr",
|
| 306 |
+
files={"file": tile_file},
|
| 307 |
+
data={
|
| 308 |
+
"tile_size": tile_size,
|
| 309 |
+
"scale": scale,
|
| 310 |
+
"skip_alpha": skip_alpha,
|
| 311 |
+
"resize_to": resize_to,
|
| 312 |
+
"timeout": timeout,
|
| 313 |
+
"model": model,
|
| 314 |
+
},
|
| 315 |
+
headers={"X-Token": token},
|
| 316 |
+
)
|
| 317 |
+
if resp.status_code != 200:
|
| 318 |
+
raise Exception(
|
| 319 |
+
f"Woker {worker_url} failed to process the image: {resp.text}"
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
resp_dict = resp.json().copy()
|
| 323 |
+
resp_dict["tile_info"] = tile_info
|
| 324 |
+
response[worker_key] = resp_dict
|
| 325 |
+
|
| 326 |
+
except Exception as e:
|
| 327 |
+
logger.error(f"error: {e}")
|
| 328 |
+
input_temp.close()
|
| 329 |
+
raise HTTPException(
|
| 330 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 331 |
+
detail=f"Failed to process the image: {e}",
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
resp = common.redis_client.xadd(
|
| 335 |
+
common.DISTRIBUTED_STREAM_NAME,
|
| 336 |
+
{
|
| 337 |
+
"data": pickle.dumps(
|
| 338 |
+
{
|
| 339 |
+
"input_image": input_path,
|
| 340 |
+
"worker_response": response,
|
| 341 |
+
"scale": scale,
|
| 342 |
+
}
|
| 343 |
+
)
|
| 344 |
+
},
|
| 345 |
+
)
|
| 346 |
+
common.redis_client.set(
|
| 347 |
+
f"{common.RESULT_KEY_PREFIX}{resp.decode('utf-8')}",
|
| 348 |
+
pickle.dumps({"status": "pending"}),
|
| 349 |
+
ex=86400,
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
return {"message": "Success", "task_id": f"{resp.decode('utf-8')}"}
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def register_slave():
|
| 356 |
+
register_single_sr_route()
|
| 357 |
+
|
| 358 |
+
def register():
|
| 359 |
+
while True:
|
| 360 |
+
try:
|
| 361 |
+
with httpx.Client() as client:
|
| 362 |
+
resp = client.post(
|
| 363 |
+
url=f"{settings.get('master_url')}/register",
|
| 364 |
+
data={
|
| 365 |
+
"worker_id": settings.get("worker_id"),
|
| 366 |
+
"worker_url": settings.get("worker_url"),
|
| 367 |
+
"worker_token": settings.get("token"),
|
| 368 |
+
},
|
| 369 |
+
headers={"X-Token": settings.get("master_token")},
|
| 370 |
+
)
|
| 371 |
+
if resp.status_code != 200:
|
| 372 |
+
logger.error(f"Failed to register to master: {resp.text}")
|
| 373 |
+
except Exception as e:
|
| 374 |
+
logger.error(f"Registration error: {e}")
|
| 375 |
+
finally:
|
| 376 |
+
time.sleep(settings.get("register_interval", 30))
|
| 377 |
+
|
| 378 |
+
register_thread = threading.Thread(target=register)
|
| 379 |
+
register_thread.daemon = True
|
| 380 |
+
register_thread.start()
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
if __name__ == "__main__":
|
| 384 |
+
register_routes()
|
| 385 |
+
if settings.get("mode", "single") == "single":
|
| 386 |
+
register_single_sr_route()
|
| 387 |
+
elif settings.get("mode") == "master":
|
| 388 |
+
register_master()
|
| 389 |
+
queue_thread = threading.Thread(target=listen_distributed_queue)
|
| 390 |
+
queue_thread.daemon = True
|
| 391 |
+
queue_thread.start()
|
| 392 |
+
else:
|
| 393 |
+
register_slave()
|
| 394 |
+
|
| 395 |
+
if settings.get("mode") != "master":
|
| 396 |
+
queue_thread = threading.Thread(target=listen_queue)
|
| 397 |
+
queue_thread.daemon = True
|
| 398 |
+
queue_thread.start()
|
| 399 |
+
|
| 400 |
+
if not pathlib.Path(settings.get("temp_dir", "./temp")).exists():
|
| 401 |
+
pathlib.Path(settings.get("temp_dir", "./temp")).mkdir(parents=True)
|
| 402 |
+
import uvicorn
|
| 403 |
+
|
| 404 |
+
try:
|
| 405 |
+
uvicorn.run(
|
| 406 |
+
app,
|
| 407 |
+
host=settings.get("host", "0.0.0.0"),
|
| 408 |
+
port=settings.get("port", 39721),
|
| 409 |
+
)
|
| 410 |
+
except KeyboardInterrupt:
|
| 411 |
+
pass
|
| 412 |
+
finally:
|
| 413 |
+
logger.info("Shutting down")
|
| 414 |
+
common.redis_client.delete(common.BASE_STREAM_NAME)
|
| 415 |
+
if settings.get("mode") == "master":
|
| 416 |
+
common.redis_client.delete(common.DISTRIBUTED_STREAM_NAME)
|
| 417 |
+
shutil.rmtree(settings.get("temp_dir", "./temp"))
|
onnx_infer.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import time
|
| 3 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 4 |
+
|
| 5 |
+
import cv2
|
| 6 |
+
import numpy as np
|
| 7 |
+
import onnxruntime as ort
|
| 8 |
+
from logger import logger
|
| 9 |
+
|
| 10 |
+
import common
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class OnnxSRInfer:
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
model_path: str,
|
| 17 |
+
scale: int,
|
| 18 |
+
name: str,
|
| 19 |
+
alpha_upsampler="sr model",
|
| 20 |
+
providers=["CUDAExecutionProvider"],
|
| 21 |
+
provider_options=None,
|
| 22 |
+
):
|
| 23 |
+
"""Onnx SR Infer
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
model_path (str): Model path
|
| 27 |
+
scale (int): Model scale
|
| 28 |
+
name (str): Instance name,used to determine whether to continue reusing this instance or destroy it when switching models.
|
| 29 |
+
alpha_upsampler (str, optional): Method of SR the Alpha channel. Defaults to 'sr model'.Optionally "sr model" or "interpolation".
|
| 30 |
+
providers (list, optional): Ort providers. Defaults to ['DmlExecutionProvider'].
|
| 31 |
+
provider_options (list, optional): eg. [{'device_id': 0}]
|
| 32 |
+
"""
|
| 33 |
+
self.sess = ort.InferenceSession(
|
| 34 |
+
model_path, providers=providers, provider_options=provider_options
|
| 35 |
+
)
|
| 36 |
+
self.name = name
|
| 37 |
+
self.scale = scale
|
| 38 |
+
self.alpha_upsampler = alpha_upsampler
|
| 39 |
+
self.model_path = model_path
|
| 40 |
+
|
| 41 |
+
def img_array_norm_expd(self, img):
|
| 42 |
+
img = np.array(img).astype(np.float32) / 255.0
|
| 43 |
+
img = np.transpose(img, (2, 0, 1))
|
| 44 |
+
img = np.expand_dims(img, axis=0)
|
| 45 |
+
return img
|
| 46 |
+
|
| 47 |
+
def img_array_denorm_squeeze(self, img):
|
| 48 |
+
output_image = np.squeeze(img)
|
| 49 |
+
output_image = np.transpose(output_image, (1, 2, 0))
|
| 50 |
+
output_image = (output_image * 255.0).clip(0, 255).astype(np.uint8)
|
| 51 |
+
output_image = cv2.cvtColor(output_image, cv2.COLOR_RGB2BGR)
|
| 52 |
+
return output_image
|
| 53 |
+
|
| 54 |
+
def mod_pad(self, img, mod=16):
|
| 55 |
+
"""
|
| 56 |
+
Pad image with reflect padding along the height and width axes, based on the modulus value.
|
| 57 |
+
|
| 58 |
+
Args:
|
| 59 |
+
img (np.array): The input image.
|
| 60 |
+
mod (int): The modulus value to be used for padding. Default is 16.
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
padded_img (np.array): The padded image.
|
| 64 |
+
pad_height (int): The added padding height.
|
| 65 |
+
pad_width (int): The added padding width.
|
| 66 |
+
"""
|
| 67 |
+
mod_pad_h, mod_pad_w = 0, 0
|
| 68 |
+
h, w, _ = img.shape
|
| 69 |
+
if h % mod != 0:
|
| 70 |
+
mod_pad_h = mod - h % mod
|
| 71 |
+
if w % mod != 0:
|
| 72 |
+
mod_pad_w = mod - w % mod
|
| 73 |
+
pad_img = np.pad(img, ((0, mod_pad_h), (0, mod_pad_w), (0, 0)), "reflect")
|
| 74 |
+
return pad_img, mod_pad_h, mod_pad_w
|
| 75 |
+
|
| 76 |
+
def remove_mod_pad(self, img, pad_height, pad_width):
|
| 77 |
+
h, w, _ = img.shape
|
| 78 |
+
return img[0 : h - self.scale * pad_height, 0 : w - self.scale * pad_width, :]
|
| 79 |
+
|
| 80 |
+
def infer(self, img):
|
| 81 |
+
"""
|
| 82 |
+
infer image
|
| 83 |
+
Args:
|
| 84 |
+
img (np.array)(h,w,c)
|
| 85 |
+
return: img (np.array)(h,w,c)
|
| 86 |
+
"""
|
| 87 |
+
img = self.img_array_norm_expd(img)
|
| 88 |
+
img_sr = self.sess.run(["output"], {"input": img})[0]
|
| 89 |
+
output = self.img_array_denorm_squeeze(img_sr)
|
| 90 |
+
return output
|
| 91 |
+
|
| 92 |
+
def process_tile(self, img, x, y, tile_size, tile_pad, width, height, output):
|
| 93 |
+
"""
|
| 94 |
+
Process a single tile and update the output image.
|
| 95 |
+
"""
|
| 96 |
+
time_start = time.time()
|
| 97 |
+
ofs_x = x * tile_size
|
| 98 |
+
ofs_y = y * tile_size
|
| 99 |
+
|
| 100 |
+
# Input tile area on total image
|
| 101 |
+
input_start_x = ofs_x
|
| 102 |
+
input_end_x = min(ofs_x + tile_size, width)
|
| 103 |
+
input_start_y = ofs_y
|
| 104 |
+
input_end_y = min(ofs_y + tile_size, height)
|
| 105 |
+
|
| 106 |
+
# Input tile area on total image with padding
|
| 107 |
+
input_start_x_pad = max(input_start_x - tile_pad, 0)
|
| 108 |
+
input_end_x_pad = min(input_end_x + tile_pad, width)
|
| 109 |
+
input_start_y_pad = max(input_start_y - tile_pad, 0)
|
| 110 |
+
input_end_y_pad = min(input_end_y + tile_pad, height)
|
| 111 |
+
|
| 112 |
+
# Input tile dimensions
|
| 113 |
+
input_tile_width = input_end_x - input_start_x
|
| 114 |
+
input_tile_height = input_end_y - input_start_y
|
| 115 |
+
|
| 116 |
+
# Extract the input tile with padding
|
| 117 |
+
input_tile = img[
|
| 118 |
+
input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad, :
|
| 119 |
+
]
|
| 120 |
+
|
| 121 |
+
# Infer the output tile
|
| 122 |
+
output_tile = self.infer(input_tile)
|
| 123 |
+
|
| 124 |
+
# Output tile area on total image
|
| 125 |
+
output_start_x = input_start_x * self.scale
|
| 126 |
+
output_end_x = input_end_x * self.scale
|
| 127 |
+
output_start_y = input_start_y * self.scale
|
| 128 |
+
output_end_y = input_end_y * self.scale
|
| 129 |
+
|
| 130 |
+
# Output tile area without padding
|
| 131 |
+
output_start_x_tile = (input_start_x - input_start_x_pad) * self.scale
|
| 132 |
+
output_end_x_tile = output_start_x_tile + input_tile_width * self.scale
|
| 133 |
+
output_start_y_tile = (input_start_y - input_start_y_pad) * self.scale
|
| 134 |
+
output_end_y_tile = output_start_y_tile + input_tile_height * self.scale
|
| 135 |
+
|
| 136 |
+
# Place the processed tile into the output image
|
| 137 |
+
output[output_start_y:output_end_y, output_start_x:output_end_x, :] = (
|
| 138 |
+
output_tile[
|
| 139 |
+
output_start_y_tile:output_end_y_tile,
|
| 140 |
+
output_start_x_tile:output_end_x_tile,
|
| 141 |
+
:,
|
| 142 |
+
]
|
| 143 |
+
)
|
| 144 |
+
logger.debug(
|
| 145 |
+
f"Processed tile {x},{y} in {time.time() - time_start:.2f} seconds"
|
| 146 |
+
)
|
| 147 |
+
|
| 148 |
+
def tile_process(
|
| 149 |
+
self,
|
| 150 |
+
img,
|
| 151 |
+
tile_size,
|
| 152 |
+
tile_pad=8,
|
| 153 |
+
max_workers=common.MAX_THREAD,
|
| 154 |
+
):
|
| 155 |
+
"""
|
| 156 |
+
It will first crop input images to tiles, and then process each tile.
|
| 157 |
+
Finally, all the processed tiles are merged into one images.
|
| 158 |
+
Args:
|
| 159 |
+
img (np.array)(h,w,c): image to be processed.
|
| 160 |
+
tile_size (int): tile size.
|
| 161 |
+
tile_pad (int):tile pad size.
|
| 162 |
+
return: img (np.array)(h,w,c): processed image.
|
| 163 |
+
Modified from: https://github.com/ata4/esrgan-launcher
|
| 164 |
+
"""
|
| 165 |
+
height, width, channels = img.shape
|
| 166 |
+
logger.debug(f"input_shape: {img.shape}")
|
| 167 |
+
output_height = height * self.scale
|
| 168 |
+
output_width = width * self.scale
|
| 169 |
+
output_shape = (output_height, output_width, channels)
|
| 170 |
+
logger.debug(f"output_shape: {output_shape}")
|
| 171 |
+
|
| 172 |
+
# start with black image
|
| 173 |
+
logger.debug(f"tail size: {tile_size}")
|
| 174 |
+
output = np.zeros(output_shape, dtype=np.float32)
|
| 175 |
+
tiles_x = math.ceil(width / tile_size)
|
| 176 |
+
tiles_y = math.ceil(height / tile_size)
|
| 177 |
+
logger.debug(
|
| 178 |
+
f"tiles_x: {tiles_x}, tiles_y: {tiles_y}, total tiles: {tiles_x * tiles_y}"
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
| 182 |
+
futures = []
|
| 183 |
+
for y in range(tiles_y):
|
| 184 |
+
for x in range(tiles_x):
|
| 185 |
+
futures.append(
|
| 186 |
+
executor.submit(
|
| 187 |
+
self.process_tile,
|
| 188 |
+
img,
|
| 189 |
+
x,
|
| 190 |
+
y,
|
| 191 |
+
tile_size,
|
| 192 |
+
tile_pad,
|
| 193 |
+
width,
|
| 194 |
+
height,
|
| 195 |
+
output,
|
| 196 |
+
)
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
for future in futures:
|
| 200 |
+
future.result()
|
| 201 |
+
|
| 202 |
+
return output
|
| 203 |
+
|
| 204 |
+
def rgb_process_pipeline(self, image, tile_size):
|
| 205 |
+
# mod pad
|
| 206 |
+
pad_img, pad_h, pad_w = self.mod_pad(image)
|
| 207 |
+
# tile process
|
| 208 |
+
sr_img = self.tile_process(pad_img, tile_size)
|
| 209 |
+
# remove pad
|
| 210 |
+
final_img = self.remove_mod_pad(sr_img, pad_h, pad_w)
|
| 211 |
+
return final_img
|
| 212 |
+
|
| 213 |
+
def universal_process_pipeline(self, image, tile_size):
|
| 214 |
+
logger.info(f"Processing image with {self.name}...")
|
| 215 |
+
img_mode = "RGB"
|
| 216 |
+
h, w, c = image.shape
|
| 217 |
+
# handle RGBA image
|
| 218 |
+
if c == 4:
|
| 219 |
+
img_mode = "RGBA"
|
| 220 |
+
alpha = image[:, :, 3]
|
| 221 |
+
image = image[:, :, 0:3]
|
| 222 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 223 |
+
if self.alpha_upsampler == "sr model":
|
| 224 |
+
alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2RGB)
|
| 225 |
+
else:
|
| 226 |
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
| 227 |
+
# process image (without alpha channel)
|
| 228 |
+
output_img = self.rgb_process_pipeline(image, tile_size)
|
| 229 |
+
# process alpha channel
|
| 230 |
+
if img_mode == "RGBA":
|
| 231 |
+
if self.alpha_upsampler == "sr model":
|
| 232 |
+
alpha_img = self.rgb_process_pipeline(alpha, tile_size)
|
| 233 |
+
output_alpha = cv2.cvtColor(alpha_img, cv2.COLOR_BGR2GRAY)
|
| 234 |
+
else: # use the cv2 resize for alpha channel
|
| 235 |
+
output_alpha = cv2.resize(
|
| 236 |
+
alpha,
|
| 237 |
+
(w * self.scale, h * self.scale),
|
| 238 |
+
interpolation=cv2.INTER_LINEAR,
|
| 239 |
+
)
|
| 240 |
+
# merge the alpha channel
|
| 241 |
+
output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA)
|
| 242 |
+
output_img[:, :, 3] = output_alpha
|
| 243 |
+
return output_img
|
pyproject.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "super-resolution-api"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "REST API for super resolution"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
license = { file = "LICENSE" }
|
| 7 |
+
requires-python = ">=3.11"
|
| 8 |
+
dependencies = [
|
| 9 |
+
"dynaconf>=3.2.6",
|
| 10 |
+
"fastapi>=0.115.6",
|
| 11 |
+
"func-timeout>=4.3.5",
|
| 12 |
+
"httpx>=0.28.1",
|
| 13 |
+
"loguru>=0.7.3",
|
| 14 |
+
"numpy>=2.1.3",
|
| 15 |
+
"onnxruntime>=1.20.1",
|
| 16 |
+
"opencv-python>=4.10.0.84",
|
| 17 |
+
"python-multipart>=0.0.19",
|
| 18 |
+
"redis>=5.2.1",
|
| 19 |
+
"uvicorn>=0.32.1",
|
| 20 |
+
"uvloop>=0.21.0",
|
| 21 |
+
]
|
| 22 |
+
|
| 23 |
+
[dependency-groups]
|
| 24 |
+
dev = ["ruff>=0.8.2"]
|
requirements.txt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file was autogenerated by uv via the following command:
|
| 2 |
+
# uv export --no-dev --no-hashes -o requirements.txt
|
| 3 |
+
annotated-types==0.7.0
|
| 4 |
+
anyio==4.7.0
|
| 5 |
+
async-timeout==5.0.1 ; python_full_version < '3.11.3'
|
| 6 |
+
certifi==2024.8.30
|
| 7 |
+
click==8.1.7
|
| 8 |
+
colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows'
|
| 9 |
+
coloredlogs==15.0.1
|
| 10 |
+
dynaconf==3.2.6
|
| 11 |
+
fastapi==0.115.6
|
| 12 |
+
flatbuffers==24.3.25
|
| 13 |
+
func-timeout==4.3.5
|
| 14 |
+
h11==0.14.0
|
| 15 |
+
httpcore==1.0.7
|
| 16 |
+
httpx==0.28.1
|
| 17 |
+
humanfriendly==10.0
|
| 18 |
+
idna==3.10
|
| 19 |
+
loguru==0.7.3
|
| 20 |
+
mpmath==1.3.0
|
| 21 |
+
numpy==2.1.3
|
| 22 |
+
onnxruntime==1.20.1
|
| 23 |
+
opencv-python==4.10.0.84
|
| 24 |
+
packaging==24.2
|
| 25 |
+
protobuf==5.29.1
|
| 26 |
+
pydantic==2.10.3
|
| 27 |
+
pydantic-core==2.27.1
|
| 28 |
+
pyreadline3==3.5.4 ; sys_platform == 'win32'
|
| 29 |
+
python-multipart==0.0.19
|
| 30 |
+
redis==5.2.1
|
| 31 |
+
sniffio==1.3.1
|
| 32 |
+
starlette==0.41.3
|
| 33 |
+
sympy==1.13.3
|
| 34 |
+
typing-extensions==4.12.2
|
| 35 |
+
uvicorn==0.32.1
|
| 36 |
+
uvloop==0.21.0
|
| 37 |
+
websockets==14.1
|
| 38 |
+
win32-setctime==1.1.0 ; sys_platform == 'win32'
|
settings.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
provider = "CPUExecutionProvider"
|
| 2 |
+
token = "qwqowo"
|
| 3 |
+
redis_url = "redis://localhost:6379"
|
| 4 |
+
host = "0.0.0.0"
|
| 5 |
+
port = 39721
|
| 6 |
+
max_timeout = 300
|
| 7 |
+
timeout = 30
|
| 8 |
+
max_thread = 8
|
| 9 |
+
mode = "master" # master, slave, single
|
| 10 |
+
master_url = "http://localhost:39721"
|
| 11 |
+
master_token = "qwqowo"
|
| 12 |
+
worker_id = ""
|
| 13 |
+
worker_url = "http://localhost:39721"
|
| 14 |
+
temp_dir = "./temp"
|
| 15 |
+
output_dir = "./output"
|
| 16 |
+
log_level = "DEBUG"
|
| 17 |
+
worker_check_interval = 5
|
| 18 |
+
worker_expire = 120
|
| 19 |
+
register_interval = 30
|
sr_queue.py
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import datetime
|
| 2 |
+
import math
|
| 3 |
+
import pickle
|
| 4 |
+
import time
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import cv2
|
| 8 |
+
import httpx
|
| 9 |
+
import numpy as np
|
| 10 |
+
from func_timeout import func_set_timeout
|
| 11 |
+
from func_timeout.exceptions import FunctionTimedOut
|
| 12 |
+
from logger import logger
|
| 13 |
+
|
| 14 |
+
import common
|
| 15 |
+
from config import settings
|
| 16 |
+
from onnx_infer import OnnxSRInfer
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@func_set_timeout(common.PROGRESS_TIMEOUT, allowOverride=True)
|
| 20 |
+
def _process_image(
|
| 21 |
+
model: common.ModelInfo = common.models[common.MODEL_NAME_DEFAULT],
|
| 22 |
+
tile_size: int = 64, # 分块大小
|
| 23 |
+
scale: int = 4, # 放大倍数
|
| 24 |
+
skip_alpha: bool = False, # 是否跳过alpha通道
|
| 25 |
+
resize_to: str = None, # 调整大小 两种格式: 1. 1920x1080 2. 1/2
|
| 26 |
+
input_image: Path = None,
|
| 27 |
+
output_path: Path | str = settings.get("output_dir", "output"),
|
| 28 |
+
gpuid: int = 0,
|
| 29 |
+
clean: bool = True,
|
| 30 |
+
) -> Path:
|
| 31 |
+
logger.info(f"processing image: {input_image}")
|
| 32 |
+
start_time = datetime.datetime.now()
|
| 33 |
+
try:
|
| 34 |
+
provider_options = None
|
| 35 |
+
if int(gpuid) >= 0:
|
| 36 |
+
provider_options = [{"device_id": int(gpuid)}]
|
| 37 |
+
sr_instance = OnnxSRInfer(
|
| 38 |
+
model.path,
|
| 39 |
+
model.scale,
|
| 40 |
+
model.name,
|
| 41 |
+
providers=[settings.get("provider", "CPUExecutionProvider")],
|
| 42 |
+
provider_options=provider_options,
|
| 43 |
+
)
|
| 44 |
+
if skip_alpha:
|
| 45 |
+
logger.debug("Skip Alpha Channel")
|
| 46 |
+
sr_instance.alpha_upsampler = "interpolation"
|
| 47 |
+
logger.debug(f"decoding image: {input_image}")
|
| 48 |
+
img = cv2.imdecode(
|
| 49 |
+
np.fromfile(input_image, dtype=np.uint8), cv2.IMREAD_UNCHANGED
|
| 50 |
+
)
|
| 51 |
+
h, w, _ = img.shape
|
| 52 |
+
sr_img = sr_instance.universal_process_pipeline(img, tile_size=tile_size)
|
| 53 |
+
scale = int(scale)
|
| 54 |
+
target_h = None
|
| 55 |
+
target_w = None
|
| 56 |
+
if scale > model.scale and model.scale != 1:
|
| 57 |
+
logger.debug("re process")
|
| 58 |
+
# calc process times
|
| 59 |
+
scale_log = math.log(scale, model.scale)
|
| 60 |
+
total_times = math.ceil(scale_log)
|
| 61 |
+
# calc target size
|
| 62 |
+
if total_times != int(scale_log):
|
| 63 |
+
target_h = h * scale
|
| 64 |
+
target_w = w * scale
|
| 65 |
+
|
| 66 |
+
for _ in range(total_times - 1):
|
| 67 |
+
sr_img = sr_instance.universal_process_pipeline(
|
| 68 |
+
sr_img, tile_size=tile_size
|
| 69 |
+
)
|
| 70 |
+
elif scale < model.scale:
|
| 71 |
+
logger.debug("down scale")
|
| 72 |
+
target_h = h * scale
|
| 73 |
+
target_w = w * scale
|
| 74 |
+
|
| 75 |
+
if resize_to:
|
| 76 |
+
logger.debug(f"resize to {resize_to}")
|
| 77 |
+
if "x" in resize_to:
|
| 78 |
+
param_w = int(resize_to.split("x")[0])
|
| 79 |
+
target_w = param_w
|
| 80 |
+
target_h = int(h * param_w / w)
|
| 81 |
+
elif "/" in resize_to:
|
| 82 |
+
ratio = int(resize_to.split("/")[0]) / int(resize_to.split("/")[1])
|
| 83 |
+
target_w = int(w * ratio)
|
| 84 |
+
target_h = int(h * ratio)
|
| 85 |
+
|
| 86 |
+
if target_w:
|
| 87 |
+
logger.debug(f"resize to {target_w}x{target_h}")
|
| 88 |
+
img_out = cv2.resize(sr_img, (target_w, target_h))
|
| 89 |
+
else:
|
| 90 |
+
img_out = sr_img
|
| 91 |
+
# save
|
| 92 |
+
final_output_path = Path(output_path) / f"{input_image.stem}_{model.name}.png"
|
| 93 |
+
if not Path(output_path).exists():
|
| 94 |
+
Path(output_path).mkdir(parents=True)
|
| 95 |
+
cv2.imencode(".png", img_out)[1].tofile(final_output_path)
|
| 96 |
+
return final_output_path
|
| 97 |
+
except Exception as e:
|
| 98 |
+
logger.error(f"process image error: {e}")
|
| 99 |
+
return None
|
| 100 |
+
finally:
|
| 101 |
+
logger.info(
|
| 102 |
+
f"Time taken: {(datetime.datetime.now() - start_time).seconds} seconds to process {input_image}"
|
| 103 |
+
)
|
| 104 |
+
if clean and input_image.exists():
|
| 105 |
+
input_image.unlink()
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def listen_queue(
|
| 109 |
+
stream_name: str = common.BASE_STREAM_NAME,
|
| 110 |
+
default_timeout: int = common.PROGRESS_TIMEOUT,
|
| 111 |
+
):
|
| 112 |
+
logger.info(f"Listening to stream: {stream_name}")
|
| 113 |
+
last_id = "0"
|
| 114 |
+
while True:
|
| 115 |
+
messages = common.redis_client.xread({stream_name: last_id}, count=1, block=0)
|
| 116 |
+
if not messages:
|
| 117 |
+
continue
|
| 118 |
+
message_id = messages[0][1][0][0]
|
| 119 |
+
last_id = message_id
|
| 120 |
+
message = messages[0][1][0][1]
|
| 121 |
+
logger.info(f"Processing task: {message_id.decode('utf-8')}")
|
| 122 |
+
data: dict[str, Path | int | bool | str | None] = pickle.loads(message[b"data"])
|
| 123 |
+
input_image = data.get("input_image")
|
| 124 |
+
tile_size = data.get("tile_size", 64)
|
| 125 |
+
scale = data.get("scale", 4)
|
| 126 |
+
skip_alpha = data.get("skip_alpha", False)
|
| 127 |
+
resize_to = data.get("resize_to", None)
|
| 128 |
+
time_out = data.get("timeout", default_timeout)
|
| 129 |
+
model_name = data.get("model", common.MODEL_NAME_DEFAULT)
|
| 130 |
+
common.redis_client.set(
|
| 131 |
+
f"{common.RESULT_KEY_PREFIX}{message_id.decode('utf-8')}",
|
| 132 |
+
pickle.dumps({"status": "processing"}),
|
| 133 |
+
ex=86400,
|
| 134 |
+
)
|
| 135 |
+
processed_path: Path | None = None
|
| 136 |
+
try:
|
| 137 |
+
processed_path = _process_image(
|
| 138 |
+
model=common.models[model_name],
|
| 139 |
+
input_image=input_image,
|
| 140 |
+
tile_size=tile_size,
|
| 141 |
+
scale=scale,
|
| 142 |
+
skip_alpha=skip_alpha,
|
| 143 |
+
resize_to=resize_to,
|
| 144 |
+
forceTimeout=time_out,
|
| 145 |
+
)
|
| 146 |
+
except FunctionTimedOut as e:
|
| 147 |
+
logger.warning(e)
|
| 148 |
+
processed_path = None
|
| 149 |
+
if processed_path:
|
| 150 |
+
common.redis_client.set(
|
| 151 |
+
f"{common.RESULT_KEY_PREFIX}{message_id.decode('utf-8')}",
|
| 152 |
+
pickle.dumps(
|
| 153 |
+
{
|
| 154 |
+
"status": "success",
|
| 155 |
+
"path": processed_path.as_posix(),
|
| 156 |
+
"size": processed_path.stat().st_size,
|
| 157 |
+
}
|
| 158 |
+
),
|
| 159 |
+
ex=86400,
|
| 160 |
+
)
|
| 161 |
+
logger.success(f"Processed image: {processed_path}")
|
| 162 |
+
else:
|
| 163 |
+
common.redis_client.set(
|
| 164 |
+
f"{common.RESULT_KEY_PREFIX}{message_id.decode('utf-8')}",
|
| 165 |
+
pickle.dumps({"status": "failed"}),
|
| 166 |
+
ex=86400,
|
| 167 |
+
)
|
| 168 |
+
common.redis_client.xdel(stream_name, message_id)
|
| 169 |
+
for file in Path(settings.get("output_dir", "output")).iterdir():
|
| 170 |
+
if datetime.datetime.now().timestamp() - file.stat().st_mtime > 86400:
|
| 171 |
+
file.unlink()
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def listen_distributed_queue(stream_name: str = common.DISTRIBUTED_STREAM_NAME):
|
| 175 |
+
logger.info(f"Listening to distributed stream: {stream_name}")
|
| 176 |
+
last_id = "0"
|
| 177 |
+
while True:
|
| 178 |
+
messages = common.redis_client.xread({stream_name: last_id}, count=1, block=0)
|
| 179 |
+
if not messages:
|
| 180 |
+
continue
|
| 181 |
+
task_id = messages[0][1][0][0]
|
| 182 |
+
last_id = task_id
|
| 183 |
+
message = messages[0][1][0][1]
|
| 184 |
+
logger.info(f"Processing task: {task_id.decode('utf-8')}")
|
| 185 |
+
time_start = datetime.datetime.now()
|
| 186 |
+
data: dict = pickle.loads(message[b"data"])
|
| 187 |
+
worker_response: dict = data.get("worker_response")
|
| 188 |
+
input_image = data.get("input_image")
|
| 189 |
+
input_image: Path
|
| 190 |
+
scale: int = data.get("scale", 4)
|
| 191 |
+
|
| 192 |
+
common.redis_client.set(
|
| 193 |
+
f"{common.RESULT_KEY_PREFIX}{task_id.decode('utf-8')}",
|
| 194 |
+
pickle.dumps({"status": "processing"}),
|
| 195 |
+
ex=86400,
|
| 196 |
+
)
|
| 197 |
+
original_w, original_h = common.get_image_size(input_image)
|
| 198 |
+
ok_keys = []
|
| 199 |
+
scaled_tiles: list[common.TileInfo] = []
|
| 200 |
+
while True:
|
| 201 |
+
try:
|
| 202 |
+
for worker_key, worker_data in worker_response.items():
|
| 203 |
+
logger.debug(f"Checking worker: {worker_key.decode('utf-8')}")
|
| 204 |
+
worker = common.redis_client.get(worker_key)
|
| 205 |
+
if not worker:
|
| 206 |
+
raise Exception(f"Worker {worker_key.decode('utf-8')} offline")
|
| 207 |
+
worker_url, token = worker.decode("utf-8").split("|")
|
| 208 |
+
worker_task_id = worker_data["task_id"]
|
| 209 |
+
response = httpx.get(
|
| 210 |
+
f"{worker_url}/result/{worker_task_id}",
|
| 211 |
+
headers={"X-Token": token},
|
| 212 |
+
)
|
| 213 |
+
if response.status_code != 200:
|
| 214 |
+
raise Exception(
|
| 215 |
+
f"Worker {worker_key.decode('utf-8')} get task status failed"
|
| 216 |
+
)
|
| 217 |
+
result = response.json()["result"]
|
| 218 |
+
if result["status"] == "failed":
|
| 219 |
+
raise Exception(
|
| 220 |
+
f"Worker {worker_key.decode('utf-8')} processing failed"
|
| 221 |
+
)
|
| 222 |
+
if result["status"] == "success":
|
| 223 |
+
logger.info(f"Worker {worker_key.decode('utf-8')} processed")
|
| 224 |
+
response = httpx.get(
|
| 225 |
+
f"{worker_url}/result/{worker_task_id}/download",
|
| 226 |
+
headers={"X-Token": token},
|
| 227 |
+
)
|
| 228 |
+
if response.status_code != 200:
|
| 229 |
+
raise Exception(
|
| 230 |
+
f"Worker {worker_key.decode('utf-8')} download failed"
|
| 231 |
+
)
|
| 232 |
+
tile_info: common.TileInfo = worker_data["tile_info"]
|
| 233 |
+
file_path = (
|
| 234 |
+
Path(settings.get("output_dir", "output"))
|
| 235 |
+
/ f"{input_image.stem}"
|
| 236 |
+
/ f"{input_image.stem}_scaled_{tile_info.y}_{tile_info.x}.png"
|
| 237 |
+
)
|
| 238 |
+
with open(file_path, "wb") as f:
|
| 239 |
+
f.write(response.content)
|
| 240 |
+
logger.debug(f"Downloaded tile: {file_path}")
|
| 241 |
+
scaled_tiles.append(
|
| 242 |
+
common.TileInfo(tile_info.x, tile_info.y, file_path)
|
| 243 |
+
)
|
| 244 |
+
ok_keys.append(worker_key)
|
| 245 |
+
|
| 246 |
+
for key in ok_keys:
|
| 247 |
+
worker_response.pop(key, None)
|
| 248 |
+
|
| 249 |
+
if not worker_response:
|
| 250 |
+
logger.info(
|
| 251 |
+
f"All workers processed, start merge {len(scaled_tiles)} tiles"
|
| 252 |
+
)
|
| 253 |
+
output_path = (
|
| 254 |
+
Path(settings.get("output_dir", "output"))
|
| 255 |
+
/ f"{input_image.stem}"
|
| 256 |
+
/ f"{input_image.stem}_scaled_x{scale}.png"
|
| 257 |
+
)
|
| 258 |
+
common.merge_sr_tiles(
|
| 259 |
+
scaled_tiles,
|
| 260 |
+
output_path,
|
| 261 |
+
(original_w, original_h),
|
| 262 |
+
scale,
|
| 263 |
+
)
|
| 264 |
+
logger.success(
|
| 265 |
+
f"Processed image: {output_path}, time taken: {(datetime.datetime.now() - time_start).seconds} seconds"
|
| 266 |
+
)
|
| 267 |
+
common.redis_client.set(
|
| 268 |
+
f"{common.RESULT_KEY_PREFIX}{task_id.decode('utf-8')}",
|
| 269 |
+
pickle.dumps(
|
| 270 |
+
{
|
| 271 |
+
"status": "success",
|
| 272 |
+
"path": output_path.as_posix(),
|
| 273 |
+
"size": output_path.stat().st_size,
|
| 274 |
+
}
|
| 275 |
+
),
|
| 276 |
+
ex=86400,
|
| 277 |
+
)
|
| 278 |
+
break
|
| 279 |
+
|
| 280 |
+
time.sleep(settings.get("worker_check_interval", 5))
|
| 281 |
+
except Exception as e:
|
| 282 |
+
logger.error(f"{e.__class__.__name__}: {e}")
|
| 283 |
+
common.redis_client.set(
|
| 284 |
+
f"{common.RESULT_KEY_PREFIX}{task_id.decode('utf-8')}",
|
| 285 |
+
pickle.dumps({"status": "failed"}),
|
| 286 |
+
ex=86400,
|
| 287 |
+
)
|
| 288 |
+
break
|
test_common.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class TestCalculateTiles(unittest.TestCase):
|
| 5 |
+
def setUp(self):
|
| 6 |
+
"""初始化测试数据"""
|
| 7 |
+
self.image_width = 1920
|
| 8 |
+
self.image_height = 1080
|
| 9 |
+
|
| 10 |
+
def test_exact_match_workers(self):
|
| 11 |
+
"""测试 Worker 数量与网格数完全匹配"""
|
| 12 |
+
from common import calculate_grid
|
| 13 |
+
|
| 14 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 3)
|
| 15 |
+
self.assertEqual(rows * cols, 3)
|
| 16 |
+
self.assertEqual((rows, cols), (1, 3)) # 宽大于高时优先横向切割
|
| 17 |
+
|
| 18 |
+
def test_square_preference(self):
|
| 19 |
+
"""测试尽量生成接近正方形的网格"""
|
| 20 |
+
from common import calculate_grid
|
| 21 |
+
|
| 22 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 4)
|
| 23 |
+
self.assertEqual(rows * cols, 4)
|
| 24 |
+
self.assertEqual((rows, cols), (2, 2)) # 4块时优先正方形分割
|
| 25 |
+
|
| 26 |
+
def test_large_workers(self):
|
| 27 |
+
"""测试较多 Worker 数量"""
|
| 28 |
+
from common import calculate_grid
|
| 29 |
+
|
| 30 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 12)
|
| 31 |
+
self.assertEqual(rows * cols, 12)
|
| 32 |
+
self.assertEqual((rows, cols), (3, 4)) # 优化为接近宽高比的分割
|
| 33 |
+
|
| 34 |
+
def test_one_worker(self):
|
| 35 |
+
"""测试单个 Worker 的情况"""
|
| 36 |
+
from common import calculate_grid
|
| 37 |
+
|
| 38 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 1)
|
| 39 |
+
self.assertEqual(rows * cols, 1)
|
| 40 |
+
self.assertEqual((rows, cols), (1, 1))
|
| 41 |
+
|
| 42 |
+
def test_invalid_worker_count(self):
|
| 43 |
+
"""测试 Worker 数量为 0 或负数的情况"""
|
| 44 |
+
from common import calculate_grid
|
| 45 |
+
|
| 46 |
+
with self.assertRaises(ValueError):
|
| 47 |
+
calculate_grid(self.image_width, self.image_height, 0)
|
| 48 |
+
|
| 49 |
+
with self.assertRaises(ValueError):
|
| 50 |
+
calculate_grid(self.image_width, self.image_height, -1)
|
| 51 |
+
|
| 52 |
+
def test_aspect_ratio_preservation(self):
|
| 53 |
+
"""测试长宽比优先调整"""
|
| 54 |
+
from common import calculate_grid
|
| 55 |
+
|
| 56 |
+
rows, cols = calculate_grid(1080, 1920, 4) # 竖向图片
|
| 57 |
+
self.assertEqual(rows * cols, 4)
|
| 58 |
+
self.assertEqual((rows, cols), (2, 2)) # 竖向也应保持正方形优先
|
| 59 |
+
|
| 60 |
+
def test_non_divisible_workers(self):
|
| 61 |
+
"""测试当 workers 不能被均匀分割时"""
|
| 62 |
+
from common import calculate_grid
|
| 63 |
+
|
| 64 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 5)
|
| 65 |
+
self.assertEqual(rows * cols, 5)
|
| 66 |
+
# 检查返回的行列数是否正确
|
| 67 |
+
self.assertTrue(rows == 1 and cols == 5 or rows == 5 and cols == 1)
|
| 68 |
+
|
| 69 |
+
def test_large_image_size(self):
|
| 70 |
+
"""测试非常大的图像尺寸"""
|
| 71 |
+
from common import calculate_grid
|
| 72 |
+
|
| 73 |
+
rows, cols = calculate_grid(8000, 8000, 16)
|
| 74 |
+
self.assertEqual(rows * cols, 16)
|
| 75 |
+
self.assertEqual((rows, cols), (4, 4)) # 优先正方形分割
|
| 76 |
+
|
| 77 |
+
def test_wide_image(self):
|
| 78 |
+
"""测试宽幅图像"""
|
| 79 |
+
from common import calculate_grid
|
| 80 |
+
|
| 81 |
+
rows, cols = calculate_grid(4000, 1000, 8)
|
| 82 |
+
self.assertEqual(rows * cols, 8)
|
| 83 |
+
# 检查是否更倾向于横向分割
|
| 84 |
+
self.assertTrue(cols > rows)
|
| 85 |
+
|
| 86 |
+
def test_tall_image(self):
|
| 87 |
+
"""测试高幅图像"""
|
| 88 |
+
from common import calculate_grid
|
| 89 |
+
|
| 90 |
+
rows, cols = calculate_grid(1000, 4000, 8)
|
| 91 |
+
self.assertEqual(rows * cols, 8)
|
| 92 |
+
# 检查是否更倾向于纵向分割
|
| 93 |
+
self.assertTrue(rows > cols)
|
| 94 |
+
|
| 95 |
+
def test_prime_number_workers(self):
|
| 96 |
+
"""测试工作者数量为质数的情况"""
|
| 97 |
+
from common import calculate_grid
|
| 98 |
+
|
| 99 |
+
rows, cols = calculate_grid(self.image_width, self.image_height, 7)
|
| 100 |
+
self.assertEqual(rows * cols, 7)
|
| 101 |
+
# 检查返回的行列数是否正确
|
| 102 |
+
self.assertTrue(rows == 1 and cols == 7 or rows == 7 and cols == 1)
|
| 103 |
+
|
| 104 |
+
def test_zero_image_size(self):
|
| 105 |
+
"""测试图像尺寸为 0 的情况"""
|
| 106 |
+
from common import calculate_grid
|
| 107 |
+
|
| 108 |
+
with self.assertRaises(ZeroDivisionError):
|
| 109 |
+
calculate_grid(0, self.image_height, 4)
|
| 110 |
+
with self.assertRaises(ZeroDivisionError):
|
| 111 |
+
calculate_grid(self.image_width, 0, 4)
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
unittest.main()
|
uv.lock
ADDED
|
@@ -0,0 +1,576 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version = 1
|
| 2 |
+
requires-python = ">=3.11"
|
| 3 |
+
resolution-markers = [
|
| 4 |
+
"python_full_version < '3.12' and platform_system == 'Darwin'",
|
| 5 |
+
"python_full_version < '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'",
|
| 6 |
+
"(python_full_version < '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version < '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')",
|
| 7 |
+
"python_full_version >= '3.12' and platform_system == 'Darwin'",
|
| 8 |
+
"python_full_version >= '3.12' and platform_machine == 'aarch64' and platform_system == 'Linux'",
|
| 9 |
+
"(python_full_version >= '3.12' and platform_machine != 'aarch64' and platform_system != 'Darwin') or (python_full_version >= '3.12' and platform_system != 'Darwin' and platform_system != 'Linux')",
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
[[package]]
|
| 13 |
+
name = "annotated-types"
|
| 14 |
+
version = "0.7.0"
|
| 15 |
+
source = { registry = "https://pypi.org/simple" }
|
| 16 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
| 17 |
+
wheels = [
|
| 18 |
+
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
[[package]]
|
| 22 |
+
name = "anyio"
|
| 23 |
+
version = "4.7.0"
|
| 24 |
+
source = { registry = "https://pypi.org/simple" }
|
| 25 |
+
dependencies = [
|
| 26 |
+
{ name = "idna" },
|
| 27 |
+
{ name = "sniffio" },
|
| 28 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 29 |
+
]
|
| 30 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 }
|
| 31 |
+
wheels = [
|
| 32 |
+
{ url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 },
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
[[package]]
|
| 36 |
+
name = "async-timeout"
|
| 37 |
+
version = "5.0.1"
|
| 38 |
+
source = { registry = "https://pypi.org/simple" }
|
| 39 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274 }
|
| 40 |
+
wheels = [
|
| 41 |
+
{ url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233 },
|
| 42 |
+
]
|
| 43 |
+
|
| 44 |
+
[[package]]
|
| 45 |
+
name = "certifi"
|
| 46 |
+
version = "2024.8.30"
|
| 47 |
+
source = { registry = "https://pypi.org/simple" }
|
| 48 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
|
| 49 |
+
wheels = [
|
| 50 |
+
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
+
[[package]]
|
| 54 |
+
name = "click"
|
| 55 |
+
version = "8.1.7"
|
| 56 |
+
source = { registry = "https://pypi.org/simple" }
|
| 57 |
+
dependencies = [
|
| 58 |
+
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
| 59 |
+
]
|
| 60 |
+
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
| 61 |
+
wheels = [
|
| 62 |
+
{ url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 },
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
[[package]]
|
| 66 |
+
name = "colorama"
|
| 67 |
+
version = "0.4.6"
|
| 68 |
+
source = { registry = "https://pypi.org/simple" }
|
| 69 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
| 70 |
+
wheels = [
|
| 71 |
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
| 72 |
+
]
|
| 73 |
+
|
| 74 |
+
[[package]]
|
| 75 |
+
name = "coloredlogs"
|
| 76 |
+
version = "15.0.1"
|
| 77 |
+
source = { registry = "https://pypi.org/simple" }
|
| 78 |
+
dependencies = [
|
| 79 |
+
{ name = "humanfriendly" },
|
| 80 |
+
]
|
| 81 |
+
sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520 }
|
| 82 |
+
wheels = [
|
| 83 |
+
{ url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018 },
|
| 84 |
+
]
|
| 85 |
+
|
| 86 |
+
[[package]]
|
| 87 |
+
name = "dynaconf"
|
| 88 |
+
version = "3.2.6"
|
| 89 |
+
source = { registry = "https://pypi.org/simple" }
|
| 90 |
+
sdist = { url = "https://files.pythonhosted.org/packages/56/1a/324f1bf234cc4f98445305fd8719245318466e310e05caea7ef052748ecd/dynaconf-3.2.6.tar.gz", hash = "sha256:74cc1897396380bb957730eb341cc0976ee9c38bbcb53d3307c50caed0aedfb8", size = 229209 }
|
| 91 |
+
wheels = [
|
| 92 |
+
{ url = "https://files.pythonhosted.org/packages/e2/14/c8a7d861262139688fa465d2e27ff7113764d6fa03b15b9c7b666729ea2e/dynaconf-3.2.6-py2.py3-none-any.whl", hash = "sha256:3911c740d717df4576ed55f616c7cbad6e06bc8ef23ffca444b6e2a12fb1c34c", size = 231063 },
|
| 93 |
+
]
|
| 94 |
+
|
| 95 |
+
[[package]]
|
| 96 |
+
name = "fastapi"
|
| 97 |
+
version = "0.115.6"
|
| 98 |
+
source = { registry = "https://pypi.org/simple" }
|
| 99 |
+
dependencies = [
|
| 100 |
+
{ name = "pydantic" },
|
| 101 |
+
{ name = "starlette" },
|
| 102 |
+
{ name = "typing-extensions" },
|
| 103 |
+
]
|
| 104 |
+
sdist = { url = "https://files.pythonhosted.org/packages/93/72/d83b98cd106541e8f5e5bfab8ef2974ab45a62e8a6c5b5e6940f26d2ed4b/fastapi-0.115.6.tar.gz", hash = "sha256:9ec46f7addc14ea472958a96aae5b5de65f39721a46aaf5705c480d9a8b76654", size = 301336 }
|
| 105 |
+
wheels = [
|
| 106 |
+
{ url = "https://files.pythonhosted.org/packages/52/b3/7e4df40e585df024fac2f80d1a2d579c854ac37109675db2b0cc22c0bb9e/fastapi-0.115.6-py3-none-any.whl", hash = "sha256:e9240b29e36fa8f4bb7290316988e90c381e5092e0cbe84e7818cc3713bcf305", size = 94843 },
|
| 107 |
+
]
|
| 108 |
+
|
| 109 |
+
[[package]]
|
| 110 |
+
name = "flatbuffers"
|
| 111 |
+
version = "24.3.25"
|
| 112 |
+
source = { registry = "https://pypi.org/simple" }
|
| 113 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a9/74/2df95ef84b214d2bee0886d572775a6f38793f5ca6d7630c3239c91104ac/flatbuffers-24.3.25.tar.gz", hash = "sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4", size = 22139 }
|
| 114 |
+
wheels = [
|
| 115 |
+
{ url = "https://files.pythonhosted.org/packages/41/f0/7e988a019bc54b2dbd0ad4182ef2d53488bb02e58694cd79d61369e85900/flatbuffers-24.3.25-py2.py3-none-any.whl", hash = "sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812", size = 26784 },
|
| 116 |
+
]
|
| 117 |
+
|
| 118 |
+
[[package]]
|
| 119 |
+
name = "func-timeout"
|
| 120 |
+
version = "4.3.5"
|
| 121 |
+
source = { registry = "https://pypi.org/simple" }
|
| 122 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b3/0d/bf0567477f7281d9a3926c582bfef21bff7498fc0ffd3e9de21811896a0b/func_timeout-4.3.5.tar.gz", hash = "sha256:74cd3c428ec94f4edfba81f9b2f14904846d5ffccc27c92433b8b5939b5575dd", size = 44264 }
|
| 123 |
+
|
| 124 |
+
[[package]]
|
| 125 |
+
name = "h11"
|
| 126 |
+
version = "0.14.0"
|
| 127 |
+
source = { registry = "https://pypi.org/simple" }
|
| 128 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
|
| 129 |
+
wheels = [
|
| 130 |
+
{ url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
|
| 131 |
+
]
|
| 132 |
+
|
| 133 |
+
[[package]]
|
| 134 |
+
name = "httpcore"
|
| 135 |
+
version = "1.0.7"
|
| 136 |
+
source = { registry = "https://pypi.org/simple" }
|
| 137 |
+
dependencies = [
|
| 138 |
+
{ name = "certifi" },
|
| 139 |
+
{ name = "h11" },
|
| 140 |
+
]
|
| 141 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
|
| 142 |
+
wheels = [
|
| 143 |
+
{ url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
|
| 144 |
+
]
|
| 145 |
+
|
| 146 |
+
[[package]]
|
| 147 |
+
name = "httpx"
|
| 148 |
+
version = "0.28.1"
|
| 149 |
+
source = { registry = "https://pypi.org/simple" }
|
| 150 |
+
dependencies = [
|
| 151 |
+
{ name = "anyio" },
|
| 152 |
+
{ name = "certifi" },
|
| 153 |
+
{ name = "httpcore" },
|
| 154 |
+
{ name = "idna" },
|
| 155 |
+
]
|
| 156 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
|
| 157 |
+
wheels = [
|
| 158 |
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
[[package]]
|
| 162 |
+
name = "humanfriendly"
|
| 163 |
+
version = "10.0"
|
| 164 |
+
source = { registry = "https://pypi.org/simple" }
|
| 165 |
+
dependencies = [
|
| 166 |
+
{ name = "pyreadline3", marker = "sys_platform == 'win32'" },
|
| 167 |
+
]
|
| 168 |
+
sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702 }
|
| 169 |
+
wheels = [
|
| 170 |
+
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794 },
|
| 171 |
+
]
|
| 172 |
+
|
| 173 |
+
[[package]]
|
| 174 |
+
name = "idna"
|
| 175 |
+
version = "3.10"
|
| 176 |
+
source = { registry = "https://pypi.org/simple" }
|
| 177 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
| 178 |
+
wheels = [
|
| 179 |
+
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
| 180 |
+
]
|
| 181 |
+
|
| 182 |
+
[[package]]
|
| 183 |
+
name = "loguru"
|
| 184 |
+
version = "0.7.3"
|
| 185 |
+
source = { registry = "https://pypi.org/simple" }
|
| 186 |
+
dependencies = [
|
| 187 |
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
| 188 |
+
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
| 189 |
+
]
|
| 190 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559 }
|
| 191 |
+
wheels = [
|
| 192 |
+
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595 },
|
| 193 |
+
]
|
| 194 |
+
|
| 195 |
+
[[package]]
|
| 196 |
+
name = "mpmath"
|
| 197 |
+
version = "1.3.0"
|
| 198 |
+
source = { registry = "https://pypi.org/simple" }
|
| 199 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106 }
|
| 200 |
+
wheels = [
|
| 201 |
+
{ url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198 },
|
| 202 |
+
]
|
| 203 |
+
|
| 204 |
+
[[package]]
|
| 205 |
+
name = "numpy"
|
| 206 |
+
version = "2.1.3"
|
| 207 |
+
source = { registry = "https://pypi.org/simple" }
|
| 208 |
+
sdist = { url = "https://files.pythonhosted.org/packages/25/ca/1166b75c21abd1da445b97bf1fa2f14f423c6cfb4fc7c4ef31dccf9f6a94/numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761", size = 20166090 }
|
| 209 |
+
wheels = [
|
| 210 |
+
{ url = "https://files.pythonhosted.org/packages/ad/81/c8167192eba5247593cd9d305ac236847c2912ff39e11402e72ae28a4985/numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d", size = 21156252 },
|
| 211 |
+
{ url = "https://files.pythonhosted.org/packages/da/74/5a60003fc3d8a718d830b08b654d0eea2d2db0806bab8f3c2aca7e18e010/numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41", size = 13784119 },
|
| 212 |
+
{ url = "https://files.pythonhosted.org/packages/47/7c/864cb966b96fce5e63fcf25e1e4d957fe5725a635e5f11fe03f39dd9d6b5/numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9", size = 5352978 },
|
| 213 |
+
{ url = "https://files.pythonhosted.org/packages/09/ac/61d07930a4993dd9691a6432de16d93bbe6aa4b1c12a5e573d468eefc1ca/numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09", size = 6892570 },
|
| 214 |
+
{ url = "https://files.pythonhosted.org/packages/27/2f/21b94664f23af2bb52030653697c685022119e0dc93d6097c3cb45bce5f9/numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a", size = 13896715 },
|
| 215 |
+
{ url = "https://files.pythonhosted.org/packages/7a/f0/80811e836484262b236c684a75dfc4ba0424bc670e765afaa911468d9f39/numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b", size = 16339644 },
|
| 216 |
+
{ url = "https://files.pythonhosted.org/packages/fa/81/ce213159a1ed8eb7d88a2a6ef4fbdb9e4ffd0c76b866c350eb4e3c37e640/numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee", size = 16712217 },
|
| 217 |
+
{ url = "https://files.pythonhosted.org/packages/7d/84/4de0b87d5a72f45556b2a8ee9fc8801e8518ec867fc68260c1f5dcb3903f/numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0", size = 14399053 },
|
| 218 |
+
{ url = "https://files.pythonhosted.org/packages/7e/1c/e5fabb9ad849f9d798b44458fd12a318d27592d4bc1448e269dec070ff04/numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9", size = 6534741 },
|
| 219 |
+
{ url = "https://files.pythonhosted.org/packages/1e/48/a9a4b538e28f854bfb62e1dea3c8fea12e90216a276c7777ae5345ff29a7/numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2", size = 12869487 },
|
| 220 |
+
{ url = "https://files.pythonhosted.org/packages/8a/f0/385eb9970309643cbca4fc6eebc8bb16e560de129c91258dfaa18498da8b/numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e", size = 20849658 },
|
| 221 |
+
{ url = "https://files.pythonhosted.org/packages/54/4a/765b4607f0fecbb239638d610d04ec0a0ded9b4951c56dc68cef79026abf/numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958", size = 13492258 },
|
| 222 |
+
{ url = "https://files.pythonhosted.org/packages/bd/a7/2332679479c70b68dccbf4a8eb9c9b5ee383164b161bee9284ac141fbd33/numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8", size = 5090249 },
|
| 223 |
+
{ url = "https://files.pythonhosted.org/packages/c1/67/4aa00316b3b981a822c7a239d3a8135be2a6945d1fd11d0efb25d361711a/numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564", size = 6621704 },
|
| 224 |
+
{ url = "https://files.pythonhosted.org/packages/5e/da/1a429ae58b3b6c364eeec93bf044c532f2ff7b48a52e41050896cf15d5b1/numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512", size = 13606089 },
|
| 225 |
+
{ url = "https://files.pythonhosted.org/packages/9e/3e/3757f304c704f2f0294a6b8340fcf2be244038be07da4cccf390fa678a9f/numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b", size = 16043185 },
|
| 226 |
+
{ url = "https://files.pythonhosted.org/packages/43/97/75329c28fea3113d00c8d2daf9bc5828d58d78ed661d8e05e234f86f0f6d/numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc", size = 16410751 },
|
| 227 |
+
{ url = "https://files.pythonhosted.org/packages/ad/7a/442965e98b34e0ae9da319f075b387bcb9a1e0658276cc63adb8c9686f7b/numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0", size = 14082705 },
|
| 228 |
+
{ url = "https://files.pythonhosted.org/packages/ac/b6/26108cf2cfa5c7e03fb969b595c93131eab4a399762b51ce9ebec2332e80/numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9", size = 6239077 },
|
| 229 |
+
{ url = "https://files.pythonhosted.org/packages/a6/84/fa11dad3404b7634aaab50733581ce11e5350383311ea7a7010f464c0170/numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a", size = 12566858 },
|
| 230 |
+
{ url = "https://files.pythonhosted.org/packages/4d/0b/620591441457e25f3404c8057eb924d04f161244cb8a3680d529419aa86e/numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f", size = 20836263 },
|
| 231 |
+
{ url = "https://files.pythonhosted.org/packages/45/e1/210b2d8b31ce9119145433e6ea78046e30771de3fe353f313b2778142f34/numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598", size = 13507771 },
|
| 232 |
+
{ url = "https://files.pythonhosted.org/packages/55/44/aa9ee3caee02fa5a45f2c3b95cafe59c44e4b278fbbf895a93e88b308555/numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57", size = 5075805 },
|
| 233 |
+
{ url = "https://files.pythonhosted.org/packages/78/d6/61de6e7e31915ba4d87bbe1ae859e83e6582ea14c6add07c8f7eefd8488f/numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe", size = 6608380 },
|
| 234 |
+
{ url = "https://files.pythonhosted.org/packages/3e/46/48bdf9b7241e317e6cf94276fe11ba673c06d1fdf115d8b4ebf616affd1a/numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43", size = 13602451 },
|
| 235 |
+
{ url = "https://files.pythonhosted.org/packages/70/50/73f9a5aa0810cdccda9c1d20be3cbe4a4d6ea6bfd6931464a44c95eef731/numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56", size = 16039822 },
|
| 236 |
+
{ url = "https://files.pythonhosted.org/packages/ad/cd/098bc1d5a5bc5307cfc65ee9369d0ca658ed88fbd7307b0d49fab6ca5fa5/numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a", size = 16411822 },
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/83/a2/7d4467a2a6d984549053b37945620209e702cf96a8bc658bc04bba13c9e2/numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef", size = 14079598 },
|
| 238 |
+
{ url = "https://files.pythonhosted.org/packages/e9/6a/d64514dcecb2ee70bfdfad10c42b76cab657e7ee31944ff7a600f141d9e9/numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f", size = 6236021 },
|
| 239 |
+
{ url = "https://files.pythonhosted.org/packages/bb/f9/12297ed8d8301a401e7d8eb6b418d32547f1d700ed3c038d325a605421a4/numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed", size = 12560405 },
|
| 240 |
+
{ url = "https://files.pythonhosted.org/packages/a7/45/7f9244cd792e163b334e3a7f02dff1239d2890b6f37ebf9e82cbe17debc0/numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f", size = 20859062 },
|
| 241 |
+
{ url = "https://files.pythonhosted.org/packages/b1/b4/a084218e7e92b506d634105b13e27a3a6645312b93e1c699cc9025adb0e1/numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4", size = 13515839 },
|
| 242 |
+
{ url = "https://files.pythonhosted.org/packages/27/45/58ed3f88028dcf80e6ea580311dc3edefdd94248f5770deb980500ef85dd/numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e", size = 5116031 },
|
| 243 |
+
{ url = "https://files.pythonhosted.org/packages/37/a8/eb689432eb977d83229094b58b0f53249d2209742f7de529c49d61a124a0/numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0", size = 6629977 },
|
| 244 |
+
{ url = "https://files.pythonhosted.org/packages/42/a3/5355ad51ac73c23334c7caaed01adadfda49544f646fcbfbb4331deb267b/numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408", size = 13575951 },
|
| 245 |
+
{ url = "https://files.pythonhosted.org/packages/c4/70/ea9646d203104e647988cb7d7279f135257a6b7e3354ea6c56f8bafdb095/numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6", size = 16022655 },
|
| 246 |
+
{ url = "https://files.pythonhosted.org/packages/14/ce/7fc0612903e91ff9d0b3f2eda4e18ef9904814afcae5b0f08edb7f637883/numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f", size = 16399902 },
|
| 247 |
+
{ url = "https://files.pythonhosted.org/packages/ef/62/1d3204313357591c913c32132a28f09a26357e33ea3c4e2fe81269e0dca1/numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17", size = 14067180 },
|
| 248 |
+
{ url = "https://files.pythonhosted.org/packages/24/d7/78a40ed1d80e23a774cb8a34ae8a9493ba1b4271dde96e56ccdbab1620ef/numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48", size = 6291907 },
|
| 249 |
+
{ url = "https://files.pythonhosted.org/packages/86/09/a5ab407bd7f5f5599e6a9261f964ace03a73e7c6928de906981c31c38082/numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4", size = 12644098 },
|
| 250 |
+
]
|
| 251 |
+
|
| 252 |
+
[[package]]
|
| 253 |
+
name = "onnxruntime"
|
| 254 |
+
version = "1.20.1"
|
| 255 |
+
source = { registry = "https://pypi.org/simple" }
|
| 256 |
+
dependencies = [
|
| 257 |
+
{ name = "coloredlogs" },
|
| 258 |
+
{ name = "flatbuffers" },
|
| 259 |
+
{ name = "numpy" },
|
| 260 |
+
{ name = "packaging" },
|
| 261 |
+
{ name = "protobuf" },
|
| 262 |
+
{ name = "sympy" },
|
| 263 |
+
]
|
| 264 |
+
wheels = [
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/95/8d/2634e2959b34aa8a0037989f4229e9abcfa484e9c228f99633b3241768a6/onnxruntime-1.20.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:06bfbf02ca9ab5f28946e0f912a562a5f005301d0c419283dc57b3ed7969bb7b", size = 30998725 },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/a5/da/c44bf9bd66cd6d9018a921f053f28d819445c4d84b4dd4777271b0fe52a2/onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6243e34d74423bdd1edf0ae9596dd61023b260f546ee17d701723915f06a9f7", size = 11955227 },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/11/ac/4120dfb74c8e45cce1c664fc7f7ce010edd587ba67ac41489f7432eb9381/onnxruntime-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5eec64c0269dcdb8d9a9a53dc4d64f87b9e0c19801d9321246a53b7eb5a7d1bc", size = 13331703 },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/12/f1/cefacac137f7bb7bfba57c50c478150fcd3c54aca72762ac2c05ce0532c1/onnxruntime-1.20.1-cp311-cp311-win32.whl", hash = "sha256:a19bc6e8c70e2485a1725b3d517a2319603acc14c1f1a017dda0afe6d4665b41", size = 9813977 },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/2c/2d/2d4d202c0bcfb3a4cc2b171abb9328672d7f91d7af9ea52572722c6d8d96/onnxruntime-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:8508887eb1c5f9537a4071768723ec7c30c28eb2518a00d0adcd32c89dea3221", size = 11329895 },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/e5/39/9335e0874f68f7d27103cbffc0e235e32e26759202df6085716375c078bb/onnxruntime-1.20.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:22b0655e2bf4f2161d52706e31f517a0e54939dc393e92577df51808a7edc8c9", size = 31007580 },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/c5/9d/a42a84e10f1744dd27c6f2f9280cc3fb98f869dd19b7cd042e391ee2ab61/onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f56e898815963d6dc4ee1c35fc6c36506466eff6d16f3cb9848cea4e8c8172", size = 11952833 },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/47/42/2f71f5680834688a9c81becbe5c5bb996fd33eaed5c66ae0606c3b1d6a02/onnxruntime-1.20.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bb71a814f66517a65628c9e4a2bb530a6edd2cd5d87ffa0af0f6f773a027d99e", size = 13333903 },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/c8/f1/aabfdf91d013320aa2fc46cf43c88ca0182860ff15df872b4552254a9680/onnxruntime-1.20.1-cp312-cp312-win32.whl", hash = "sha256:bd386cc9ee5f686ee8a75ba74037750aca55183085bf1941da8efcfe12d5b120", size = 9814562 },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/dd/80/76979e0b744307d488c79e41051117634b956612cc731f1028eb17ee7294/onnxruntime-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:19c2d843eb074f385e8bbb753a40df780511061a63f9def1b216bf53860223fb", size = 11331482 },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/f7/71/c5d980ac4189589267a06f758bd6c5667d07e55656bed6c6c0580733ad07/onnxruntime-1.20.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:cc01437a32d0042b606f462245c8bbae269e5442797f6213e36ce61d5abdd8cc", size = 31007574 },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/81/0d/13bbd9489be2a6944f4a940084bfe388f1100472f38c07080a46fbd4ab96/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb44b08e017a648924dbe91b82d89b0c105b1adcfe31e90d1dc06b8677ad37be", size = 11951459 },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/c0/ea/4454ae122874fd52bbb8a961262de81c5f932edeb1b72217f594c700d6ef/onnxruntime-1.20.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda6aebdf7917c1d811f21d41633df00c58aff2bef2f598f69289c1f1dabc4b3", size = 13331620 },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/d8/e0/50db43188ca1c945decaa8fc2a024c33446d31afed40149897d4f9de505f/onnxruntime-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:d30367df7e70f1d9fc5a6a68106f5961686d39b54d3221f760085524e8d38e16", size = 11331758 },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/d8/55/3821c5fd60b52a6c82a00bba18531793c93c4addfe64fbf061e235c5617a/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9158465745423b2b5d97ed25aa7740c7d38d2993ee2e5c3bfacb0c4145c49d8", size = 11950342 },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/14/56/fd990ca222cef4f9f4a9400567b9a15b220dee2eafffb16b2adbc55c8281/onnxruntime-1.20.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0df6f2df83d61f46e842dbcde610ede27218947c33e994545a22333491e72a3b", size = 13337040 },
|
| 281 |
+
]
|
| 282 |
+
|
| 283 |
+
[[package]]
|
| 284 |
+
name = "opencv-python"
|
| 285 |
+
version = "4.10.0.84"
|
| 286 |
+
source = { registry = "https://pypi.org/simple" }
|
| 287 |
+
dependencies = [
|
| 288 |
+
{ name = "numpy" },
|
| 289 |
+
]
|
| 290 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/b70a2d9ab205110d715906fc8ec83fbb00404aeb3a37a0654fdb68eb0c8c/opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526", size = 95103981 }
|
| 291 |
+
wheels = [
|
| 292 |
+
{ url = "https://files.pythonhosted.org/packages/66/82/564168a349148298aca281e342551404ef5521f33fba17b388ead0a84dc5/opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251", size = 54835524 },
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/64/4a/016cda9ad7cf18c58ba074628a4eaae8aa55f3fd06a266398cef8831a5b9/opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98", size = 56475426 },
|
| 294 |
+
{ url = "https://files.pythonhosted.org/packages/81/e4/7a987ebecfe5ceaf32db413b67ff18eb3092c598408862fff4d7cc3fd19b/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6", size = 41746971 },
|
| 295 |
+
{ url = "https://files.pythonhosted.org/packages/3f/a4/d2537f47fd7fcfba966bd806e3ec18e7ee1681056d4b0a9c8d983983e4d5/opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f", size = 62548253 },
|
| 296 |
+
{ url = "https://files.pythonhosted.org/packages/1e/39/bbf57e7b9dab623e8773f6ff36385456b7ae7fa9357a5e53db732c347eac/opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236", size = 28737688 },
|
| 297 |
+
{ url = "https://files.pythonhosted.org/packages/ec/6c/fab8113424af5049f85717e8e527ca3773299a3c6b02506e66436e19874f/opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe", size = 38842521 },
|
| 298 |
+
]
|
| 299 |
+
|
| 300 |
+
[[package]]
|
| 301 |
+
name = "packaging"
|
| 302 |
+
version = "24.2"
|
| 303 |
+
source = { registry = "https://pypi.org/simple" }
|
| 304 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 }
|
| 305 |
+
wheels = [
|
| 306 |
+
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
|
| 307 |
+
]
|
| 308 |
+
|
| 309 |
+
[[package]]
|
| 310 |
+
name = "protobuf"
|
| 311 |
+
version = "5.29.1"
|
| 312 |
+
source = { registry = "https://pypi.org/simple" }
|
| 313 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d2/4f/1639b7b1633d8fd55f216ba01e21bf2c43384ab25ef3ddb35d85a52033e8/protobuf-5.29.1.tar.gz", hash = "sha256:683be02ca21a6ffe80db6dd02c0b5b2892322c59ca57fd6c872d652cb80549cb", size = 424965 }
|
| 314 |
+
wheels = [
|
| 315 |
+
{ url = "https://files.pythonhosted.org/packages/50/c7/28669b04691a376cf7d0617d612f126aa0fff763d57df0142f9bf474c5b8/protobuf-5.29.1-cp310-abi3-win32.whl", hash = "sha256:22c1f539024241ee545cbcb00ee160ad1877975690b16656ff87dde107b5f110", size = 422706 },
|
| 316 |
+
{ url = "https://files.pythonhosted.org/packages/e3/33/dc7a7712f457456b7e0b16420ab8ba1cc8686751d3f28392eb43d0029ab9/protobuf-5.29.1-cp310-abi3-win_amd64.whl", hash = "sha256:1fc55267f086dd4050d18ef839d7bd69300d0d08c2a53ca7df3920cc271a3c34", size = 434505 },
|
| 317 |
+
{ url = "https://files.pythonhosted.org/packages/e5/39/44239fb1c6ec557e1731d996a5de89a9eb1ada7a92491fcf9c5d714052ed/protobuf-5.29.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:d473655e29c0c4bbf8b69e9a8fb54645bc289dead6d753b952e7aa660254ae18", size = 417822 },
|
| 318 |
+
{ url = "https://files.pythonhosted.org/packages/fb/4a/ec56f101d38d4bef2959a9750209809242d86cf8b897db00f2f98bfa360e/protobuf-5.29.1-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5ba1d0e4c8a40ae0496d0e2ecfdbb82e1776928a205106d14ad6985a09ec155", size = 319572 },
|
| 319 |
+
{ url = "https://files.pythonhosted.org/packages/04/52/c97c58a33b3d6c89a8138788576d372a90a6556f354799971c6b4d16d871/protobuf-5.29.1-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:8ee1461b3af56145aca2800e6a3e2f928108c749ba8feccc6f5dd0062c410c0d", size = 319671 },
|
| 320 |
+
{ url = "https://files.pythonhosted.org/packages/3b/24/c8c49df8f6587719e1d400109b16c10c6902d0c9adddc8fff82840146f99/protobuf-5.29.1-py3-none-any.whl", hash = "sha256:32600ddb9c2a53dedc25b8581ea0f1fd8ea04956373c0c07577ce58d312522e0", size = 172547 },
|
| 321 |
+
]
|
| 322 |
+
|
| 323 |
+
[[package]]
|
| 324 |
+
name = "pydantic"
|
| 325 |
+
version = "2.10.3"
|
| 326 |
+
source = { registry = "https://pypi.org/simple" }
|
| 327 |
+
dependencies = [
|
| 328 |
+
{ name = "annotated-types" },
|
| 329 |
+
{ name = "pydantic-core" },
|
| 330 |
+
{ name = "typing-extensions" },
|
| 331 |
+
]
|
| 332 |
+
sdist = { url = "https://files.pythonhosted.org/packages/45/0f/27908242621b14e649a84e62b133de45f84c255eecb350ab02979844a788/pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9", size = 786486 }
|
| 333 |
+
wheels = [
|
| 334 |
+
{ url = "https://files.pythonhosted.org/packages/62/51/72c18c55cf2f46ff4f91ebcc8f75aa30f7305f3d726be3f4ebffb4ae972b/pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d", size = 456997 },
|
| 335 |
+
]
|
| 336 |
+
|
| 337 |
+
[[package]]
|
| 338 |
+
name = "pydantic-core"
|
| 339 |
+
version = "2.27.1"
|
| 340 |
+
source = { registry = "https://pypi.org/simple" }
|
| 341 |
+
dependencies = [
|
| 342 |
+
{ name = "typing-extensions" },
|
| 343 |
+
]
|
| 344 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 }
|
| 345 |
+
wheels = [
|
| 346 |
+
{ url = "https://files.pythonhosted.org/packages/27/39/46fe47f2ad4746b478ba89c561cafe4428e02b3573df882334bd2964f9cb/pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8", size = 1895553 },
|
| 347 |
+
{ url = "https://files.pythonhosted.org/packages/1c/00/0804e84a78b7fdb394fff4c4f429815a10e5e0993e6ae0e0b27dd20379ee/pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330", size = 1807220 },
|
| 348 |
+
{ url = "https://files.pythonhosted.org/packages/01/de/df51b3bac9820d38371f5a261020f505025df732ce566c2a2e7970b84c8c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52", size = 1829727 },
|
| 349 |
+
{ url = "https://files.pythonhosted.org/packages/5f/d9/c01d19da8f9e9fbdb2bf99f8358d145a312590374d0dc9dd8dbe484a9cde/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4", size = 1854282 },
|
| 350 |
+
{ url = "https://files.pythonhosted.org/packages/5f/84/7db66eb12a0dc88c006abd6f3cbbf4232d26adfd827a28638c540d8f871d/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c", size = 2037437 },
|
| 351 |
+
{ url = "https://files.pythonhosted.org/packages/34/ac/a2537958db8299fbabed81167d58cc1506049dba4163433524e06a7d9f4c/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de", size = 2780899 },
|
| 352 |
+
{ url = "https://files.pythonhosted.org/packages/4a/c1/3e38cd777ef832c4fdce11d204592e135ddeedb6c6f525478a53d1c7d3e5/pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025", size = 2135022 },
|
| 353 |
+
{ url = "https://files.pythonhosted.org/packages/7a/69/b9952829f80fd555fe04340539d90e000a146f2a003d3fcd1e7077c06c71/pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e", size = 1987969 },
|
| 354 |
+
{ url = "https://files.pythonhosted.org/packages/05/72/257b5824d7988af43460c4e22b63932ed651fe98804cc2793068de7ec554/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919", size = 1994625 },
|
| 355 |
+
{ url = "https://files.pythonhosted.org/packages/73/c3/78ed6b7f3278a36589bcdd01243189ade7fc9b26852844938b4d7693895b/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c", size = 2090089 },
|
| 356 |
+
{ url = "https://files.pythonhosted.org/packages/8d/c8/b4139b2f78579960353c4cd987e035108c93a78371bb19ba0dc1ac3b3220/pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc", size = 2142496 },
|
| 357 |
+
{ url = "https://files.pythonhosted.org/packages/3e/f8/171a03e97eb36c0b51981efe0f78460554a1d8311773d3d30e20c005164e/pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9", size = 1811758 },
|
| 358 |
+
{ url = "https://files.pythonhosted.org/packages/6a/fe/4e0e63c418c1c76e33974a05266e5633e879d4061f9533b1706a86f77d5b/pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5", size = 1980864 },
|
| 359 |
+
{ url = "https://files.pythonhosted.org/packages/50/fc/93f7238a514c155a8ec02fc7ac6376177d449848115e4519b853820436c5/pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89", size = 1864327 },
|
| 360 |
+
{ url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 },
|
| 361 |
+
{ url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 },
|
| 362 |
+
{ url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 },
|
| 363 |
+
{ url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 },
|
| 364 |
+
{ url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 },
|
| 365 |
+
{ url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 },
|
| 366 |
+
{ url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 },
|
| 367 |
+
{ url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 },
|
| 368 |
+
{ url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 },
|
| 369 |
+
{ url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 },
|
| 370 |
+
{ url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 },
|
| 371 |
+
{ url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 },
|
| 372 |
+
{ url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 },
|
| 373 |
+
{ url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 },
|
| 374 |
+
{ url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 },
|
| 375 |
+
{ url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 },
|
| 376 |
+
{ url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 },
|
| 377 |
+
{ url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 },
|
| 378 |
+
{ url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 },
|
| 379 |
+
{ url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 },
|
| 380 |
+
{ url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 },
|
| 381 |
+
{ url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 },
|
| 382 |
+
{ url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 },
|
| 383 |
+
{ url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 },
|
| 384 |
+
{ url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 },
|
| 385 |
+
{ url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 },
|
| 386 |
+
{ url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 },
|
| 387 |
+
{ url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 },
|
| 388 |
+
]
|
| 389 |
+
|
| 390 |
+
[[package]]
|
| 391 |
+
name = "pyreadline3"
|
| 392 |
+
version = "3.5.4"
|
| 393 |
+
source = { registry = "https://pypi.org/simple" }
|
| 394 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839 }
|
| 395 |
+
wheels = [
|
| 396 |
+
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178 },
|
| 397 |
+
]
|
| 398 |
+
|
| 399 |
+
[[package]]
|
| 400 |
+
name = "python-multipart"
|
| 401 |
+
version = "0.0.19"
|
| 402 |
+
source = { registry = "https://pypi.org/simple" }
|
| 403 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c1/19/93bfb43a3c41b1dd0fa1fa66a08286f6467d36d30297a7aaab8c0b176a26/python_multipart-0.0.19.tar.gz", hash = "sha256:905502ef39050557b7a6af411f454bc19526529ca46ae6831508438890ce12cc", size = 36886 }
|
| 404 |
+
wheels = [
|
| 405 |
+
{ url = "https://files.pythonhosted.org/packages/e1/f4/ddd0fcdc454cf3870153ae16a818256523d31c3c8136e216bc6836ed4cd1/python_multipart-0.0.19-py3-none-any.whl", hash = "sha256:f8d5b0b9c618575bf9df01c684ded1d94a338839bdd8223838afacfb4bb2082d", size = 24448 },
|
| 406 |
+
]
|
| 407 |
+
|
| 408 |
+
[[package]]
|
| 409 |
+
name = "redis"
|
| 410 |
+
version = "5.2.1"
|
| 411 |
+
source = { registry = "https://pypi.org/simple" }
|
| 412 |
+
dependencies = [
|
| 413 |
+
{ name = "async-timeout", marker = "python_full_version < '3.11.3'" },
|
| 414 |
+
]
|
| 415 |
+
sdist = { url = "https://files.pythonhosted.org/packages/47/da/d283a37303a995cd36f8b92db85135153dc4f7a8e4441aa827721b442cfb/redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f", size = 4608355 }
|
| 416 |
+
wheels = [
|
| 417 |
+
{ url = "https://files.pythonhosted.org/packages/3c/5f/fa26b9b2672cbe30e07d9a5bdf39cf16e3b80b42916757c5f92bca88e4ba/redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4", size = 261502 },
|
| 418 |
+
]
|
| 419 |
+
|
| 420 |
+
[[package]]
|
| 421 |
+
name = "ruff"
|
| 422 |
+
version = "0.8.2"
|
| 423 |
+
source = { registry = "https://pypi.org/simple" }
|
| 424 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5e/2b/01245f4f3a727d60bebeacd7ee6d22586c7f62380a2597ddb22c2f45d018/ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5", size = 3349020 }
|
| 425 |
+
wheels = [
|
| 426 |
+
{ url = "https://files.pythonhosted.org/packages/91/29/366be70216dba1731a00a41f2f030822b0c96c7c4f3b2c0cdce15cbace74/ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d", size = 10530649 },
|
| 427 |
+
{ url = "https://files.pythonhosted.org/packages/63/82/a733956540bb388f00df5a3e6a02467b16c0e529132625fe44ce4c5fb9c7/ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5", size = 10274069 },
|
| 428 |
+
{ url = "https://files.pythonhosted.org/packages/3d/12/0b3aa14d1d71546c988a28e1b412981c1b80c8a1072e977a2f30c595cc4a/ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c", size = 9909400 },
|
| 429 |
+
{ url = "https://files.pythonhosted.org/packages/23/08/f9f08cefb7921784c891c4151cce6ed357ff49e84b84978440cffbc87408/ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f", size = 10766782 },
|
| 430 |
+
{ url = "https://files.pythonhosted.org/packages/e4/71/bf50c321ec179aa420c8ec40adac5ae9cc408d4d37283a485b19a2331ceb/ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897", size = 10286316 },
|
| 431 |
+
{ url = "https://files.pythonhosted.org/packages/f2/83/c82688a2a6117539aea0ce63fdf6c08e60fe0202779361223bcd7f40bd74/ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58", size = 11338270 },
|
| 432 |
+
{ url = "https://files.pythonhosted.org/packages/7f/d7/bc6a45e5a22e627640388e703160afb1d77c572b1d0fda8b4349f334fc66/ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29", size = 12058579 },
|
| 433 |
+
{ url = "https://files.pythonhosted.org/packages/da/3b/64150c93946ec851e6f1707ff586bb460ca671581380c919698d6a9267dc/ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248", size = 11615172 },
|
| 434 |
+
{ url = "https://files.pythonhosted.org/packages/e4/9e/cf12b697ea83cfe92ec4509ae414dc4c9b38179cc681a497031f0d0d9a8e/ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93", size = 12882398 },
|
| 435 |
+
{ url = "https://files.pythonhosted.org/packages/a9/27/96d10863accf76a9c97baceac30b0a52d917eb985a8ac058bd4636aeede0/ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d", size = 11176094 },
|
| 436 |
+
{ url = "https://files.pythonhosted.org/packages/eb/10/cd2fd77d4a4e7f03c29351be0f53278a393186b540b99df68beb5304fddd/ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0", size = 10771884 },
|
| 437 |
+
{ url = "https://files.pythonhosted.org/packages/71/5d/beabb2ff18870fc4add05fa3a69a4cb1b1d2d6f83f3cf3ae5ab0d52f455d/ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa", size = 10382535 },
|
| 438 |
+
{ url = "https://files.pythonhosted.org/packages/ae/29/6b3fdf3ad3e35b28d87c25a9ff4c8222ad72485ab783936b2b267250d7a7/ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f", size = 10886995 },
|
| 439 |
+
{ url = "https://files.pythonhosted.org/packages/e9/dc/859d889b4d9356a1a2cdbc1e4a0dda94052bc5b5300098647e51a58c430b/ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22", size = 11220750 },
|
| 440 |
+
{ url = "https://files.pythonhosted.org/packages/0b/08/e8f519f61f1d624264bfd6b8829e4c5f31c3c61193bc3cff1f19dbe7626a/ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1", size = 8729396 },
|
| 441 |
+
{ url = "https://files.pythonhosted.org/packages/f8/d4/ba1c7ab72aba37a2b71fe48ab95b80546dbad7a7f35ea28cf66fc5cea5f6/ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea", size = 9594729 },
|
| 442 |
+
{ url = "https://files.pythonhosted.org/packages/23/34/db20e12d3db11b8a2a8874258f0f6d96a9a4d631659d54575840557164c8/ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8", size = 9035131 },
|
| 443 |
+
]
|
| 444 |
+
|
| 445 |
+
[[package]]
|
| 446 |
+
name = "sniffio"
|
| 447 |
+
version = "1.3.1"
|
| 448 |
+
source = { registry = "https://pypi.org/simple" }
|
| 449 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
|
| 450 |
+
wheels = [
|
| 451 |
+
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
|
| 452 |
+
]
|
| 453 |
+
|
| 454 |
+
[[package]]
|
| 455 |
+
name = "starlette"
|
| 456 |
+
version = "0.41.3"
|
| 457 |
+
source = { registry = "https://pypi.org/simple" }
|
| 458 |
+
dependencies = [
|
| 459 |
+
{ name = "anyio" },
|
| 460 |
+
]
|
| 461 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 }
|
| 462 |
+
wheels = [
|
| 463 |
+
{ url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 },
|
| 464 |
+
]
|
| 465 |
+
|
| 466 |
+
[[package]]
|
| 467 |
+
name = "super-resolution-api"
|
| 468 |
+
version = "0.1.0"
|
| 469 |
+
source = { virtual = "." }
|
| 470 |
+
dependencies = [
|
| 471 |
+
{ name = "dynaconf" },
|
| 472 |
+
{ name = "fastapi" },
|
| 473 |
+
{ name = "func-timeout" },
|
| 474 |
+
{ name = "httpx" },
|
| 475 |
+
{ name = "loguru" },
|
| 476 |
+
{ name = "numpy" },
|
| 477 |
+
{ name = "onnxruntime" },
|
| 478 |
+
{ name = "opencv-python" },
|
| 479 |
+
{ name = "python-multipart" },
|
| 480 |
+
{ name = "redis" },
|
| 481 |
+
{ name = "uvicorn" },
|
| 482 |
+
{ name = "uvloop" },
|
| 483 |
+
]
|
| 484 |
+
|
| 485 |
+
[package.dev-dependencies]
|
| 486 |
+
dev = [
|
| 487 |
+
{ name = "ruff" },
|
| 488 |
+
]
|
| 489 |
+
|
| 490 |
+
[package.metadata]
|
| 491 |
+
requires-dist = [
|
| 492 |
+
{ name = "dynaconf", specifier = ">=3.2.6" },
|
| 493 |
+
{ name = "fastapi", specifier = ">=0.115.6" },
|
| 494 |
+
{ name = "func-timeout", specifier = ">=4.3.5" },
|
| 495 |
+
{ name = "httpx", specifier = ">=0.28.1" },
|
| 496 |
+
{ name = "loguru", specifier = ">=0.7.3" },
|
| 497 |
+
{ name = "numpy", specifier = ">=2.1.3" },
|
| 498 |
+
{ name = "onnxruntime", specifier = ">=1.20.1" },
|
| 499 |
+
{ name = "opencv-python", specifier = ">=4.10.0.84" },
|
| 500 |
+
{ name = "python-multipart", specifier = ">=0.0.19" },
|
| 501 |
+
{ name = "redis", specifier = ">=5.2.1" },
|
| 502 |
+
{ name = "uvicorn", specifier = ">=0.32.1" },
|
| 503 |
+
{ name = "uvloop", specifier = ">=0.21.0" },
|
| 504 |
+
]
|
| 505 |
+
|
| 506 |
+
[package.metadata.requires-dev]
|
| 507 |
+
dev = [{ name = "ruff", specifier = ">=0.8.2" }]
|
| 508 |
+
|
| 509 |
+
[[package]]
|
| 510 |
+
name = "sympy"
|
| 511 |
+
version = "1.13.3"
|
| 512 |
+
source = { registry = "https://pypi.org/simple" }
|
| 513 |
+
dependencies = [
|
| 514 |
+
{ name = "mpmath" },
|
| 515 |
+
]
|
| 516 |
+
sdist = { url = "https://files.pythonhosted.org/packages/11/8a/5a7fd6284fa8caac23a26c9ddf9c30485a48169344b4bd3b0f02fef1890f/sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9", size = 7533196 }
|
| 517 |
+
wheels = [
|
| 518 |
+
{ url = "https://files.pythonhosted.org/packages/99/ff/c87e0622b1dadea79d2fb0b25ade9ed98954c9033722eb707053d310d4f3/sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73", size = 6189483 },
|
| 519 |
+
]
|
| 520 |
+
|
| 521 |
+
[[package]]
|
| 522 |
+
name = "typing-extensions"
|
| 523 |
+
version = "4.12.2"
|
| 524 |
+
source = { registry = "https://pypi.org/simple" }
|
| 525 |
+
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
| 526 |
+
wheels = [
|
| 527 |
+
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
| 528 |
+
]
|
| 529 |
+
|
| 530 |
+
[[package]]
|
| 531 |
+
name = "uvicorn"
|
| 532 |
+
version = "0.32.1"
|
| 533 |
+
source = { registry = "https://pypi.org/simple" }
|
| 534 |
+
dependencies = [
|
| 535 |
+
{ name = "click" },
|
| 536 |
+
{ name = "h11" },
|
| 537 |
+
]
|
| 538 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 }
|
| 539 |
+
wheels = [
|
| 540 |
+
{ url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 },
|
| 541 |
+
]
|
| 542 |
+
|
| 543 |
+
[[package]]
|
| 544 |
+
name = "uvloop"
|
| 545 |
+
version = "0.21.0"
|
| 546 |
+
source = { registry = "https://pypi.org/simple" }
|
| 547 |
+
sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741 }
|
| 548 |
+
wheels = [
|
| 549 |
+
{ url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410 },
|
| 550 |
+
{ url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476 },
|
| 551 |
+
{ url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855 },
|
| 552 |
+
{ url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185 },
|
| 553 |
+
{ url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256 },
|
| 554 |
+
{ url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323 },
|
| 555 |
+
{ url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284 },
|
| 556 |
+
{ url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349 },
|
| 557 |
+
{ url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089 },
|
| 558 |
+
{ url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770 },
|
| 559 |
+
{ url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321 },
|
| 560 |
+
{ url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022 },
|
| 561 |
+
{ url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123 },
|
| 562 |
+
{ url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325 },
|
| 563 |
+
{ url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806 },
|
| 564 |
+
{ url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068 },
|
| 565 |
+
{ url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428 },
|
| 566 |
+
{ url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018 },
|
| 567 |
+
]
|
| 568 |
+
|
| 569 |
+
[[package]]
|
| 570 |
+
name = "win32-setctime"
|
| 571 |
+
version = "1.1.0"
|
| 572 |
+
source = { registry = "https://pypi.org/simple" }
|
| 573 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6b/dd/f95a13d2b235a28d613ba23ebad55191514550debb968b46aab99f2e3a30/win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2", size = 3676 }
|
| 574 |
+
wheels = [
|
| 575 |
+
{ url = "https://files.pythonhosted.org/packages/0a/e6/a7d828fef907843b2a5773ebff47fb79ac0c1c88d60c0ca9530ee941e248/win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad", size = 3604 },
|
| 576 |
+
]
|