增加局域网发布消息+全图+锥桶

This commit is contained in:
cyy_mac
2026-06-14 23:26:38 +08:00
parent f009be7de2
commit 70ff8986d9
13 changed files with 1210 additions and 3 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

1
.gitignore vendored
View File

@@ -5,6 +5,5 @@ build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

176
README.md
View File

@@ -0,0 +1,176 @@
# Origincar Simulation
基于 MotrixSim 的阿克曼小车仿真,支持键盘控制和 HTTP API 局域网共享。
## 启动
```bash
uv run mxpython main.py
```
## HTTP API端口 8765
仿真启动后,局域网内其他设备可通过 HTTP 获取摄像头图像、IMU 数据和发送控制指令。
### GET `/image`
获取前置摄像头最新 JPEG 图像。浏览器可直接打开。
```bash
# 浏览器
http://<仿真机IP>:8765/image
# curl
curl http://192.168.1.100:8765/image -o frame.jpg
# Python
import requests
r = requests.get("http://192.168.1.100:8765/image")
with open("frame.jpg", "wb") as f:
f.write(r.content)
```
### GET `/state`
获取小车当前状态JSON
```bash
curl http://192.168.1.100:8765/state
```
返回示例:
```json
{
"x": -2.0,
"y": -2.3,
"z": 0.08,
"yaw_deg": 0.0,
"speed_target": 1.0,
"speed_actual": 0.98,
"steer_target": 0.0,
"speed_amp": 1.0,
"steer_amp": 0.35
}
```
### GET `/imu`
获取 IMU 数据(含 MPU6050 噪声JSON。
```bash
curl http://192.168.1.100:8765/imu
```
返回示例:
```json
{
"orientation": {"w": 1.0, "x": 0.0, "y": 0.0, "z": 0.0},
"angular_velocity": {"x": 0.001, "y": -0.003, "z": 0.008},
"linear_acceleration": {"x": 0.02, "y": -0.01, "z": 9.81}
}
```
噪声参数MPU6050 级别):
- 陀螺仪白噪声 σ = 0.01 rad/sbias 随机游走
- 加速度计白噪声 σ = 0.05 m/s²bias 随机游走
### POST `/cmd`
发送控制指令JSON
```bash
curl -X POST http://192.168.1.100:8765/cmd \
-H "Content-Type: application/json" \
-d '{"speed": 1.0, "steer": 0.3}'
```
| 字段 | 类型 | 说明 |
|------|------|------|
| `speed` | float | 目标速度 m/s正=前进,负=后退 |
| `steer` | float | 目标转向角 rad正=左转,负=右转 |
**Python 客户端示例**
```python
import time
import requests
IP = "192.168.1.100"
# 获取图像
r = requests.get(f"http://{IP}:8765/image")
with open("frame.jpg", "wb") as f:
f.write(r.content)
# 获取状态
state = requests.get(f"http://{IP}:8765/state").json()
print(f"位置: ({state['x']}, {state['y']}), 速度: {state['speed_actual']} m/s")
# 发送控制指令
requests.post(f"http://{IP}:8765/cmd", json={"speed": 1.0, "steer": 0.2})
time.sleep(2)
requests.post(f"http://{IP}:8765/cmd", json={"speed": 0.0, "steer": 0.0})
```
**ROS2 桥接示例**
```python
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Image as RosImage
from geometry_msgs.msg import Twist
from cv_bridge import CvBridge
import requests
import numpy as np
import cv2
IP = "192.168.1.100"
class OrigincarBridge(Node):
def __init__(self):
super().__init__("origincar_bridge")
self.bridge = CvBridge()
self.pub = self.create_publisher(RosImage, "/image", 10)
self.sub = self.create_subscription(Twist, "/cmd_vel", self.cmd_cb, 10)
self.timer = self.create_timer(0.1, self.fetch_and_publish)
def fetch_and_publish(self):
r = requests.get(f"http://{IP}:8765/image", timeout=1)
if r.status_code == 200:
arr = np.frombuffer(r.content, np.uint8)
cv_img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
msg = self.bridge.cv2_to_imgmsg(cv_img, "bgr8")
self.pub.publish(msg)
def cmd_cb(self, msg: Twist):
requests.post(f"http://{IP}:8765/cmd", json={
"speed": msg.linear.x,
"steer": msg.angular.z
})
rclpy.init()
node = OrigincarBridge()
rclpy.spin(node)
```
## 键盘控制
| 按键 | 方式 | 作用 |
|------|------|------|
| W/S | 按住 | 前进/后退 |
| A/D | 按住 | 左转/右转 |
| ↑/↓ | 点按 | 速度幅度 ±0.2 m/s0~2.0 |
| ←/→ | 点按 | 转向幅度 ±0.05 rad0~0.5 |
| Space | 点按 | 截图保存到 `captures/` |
键盘与 HTTP `/cmd` 可同时使用:按住 W/A/S/D 时键盘优先,松开后自动切回 HTTP 控制。
## 场景结构
```
scene.xml — 5m×5m 地图 + 40cm 白色围栏 + 阿克曼小车
main.py — 仿真控制 + HTTP API 服务
origincar.urdf / origincar.xacro — 小车模型(参考)
```

BIN
captures/front_cam_0000.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

219
cone.obj Normal file
View File

@@ -0,0 +1,219 @@
# Traffic cone
v 0.100000 0.000000 0.000000
v 0.098481 0.017365 0.000000
v 0.093969 0.034202 0.000000
v 0.086603 0.050000 0.000000
v 0.076604 0.064279 0.000000
v 0.064279 0.076604 0.000000
v 0.050000 0.086603 0.000000
v 0.034202 0.093969 0.000000
v 0.017365 0.098481 0.000000
v 0.000000 0.100000 0.000000
v -0.017365 0.098481 0.000000
v -0.034202 0.093969 0.000000
v -0.050000 0.086603 0.000000
v -0.064279 0.076604 0.000000
v -0.076604 0.064279 0.000000
v -0.086603 0.050000 0.000000
v -0.093969 0.034202 0.000000
v -0.098481 0.017365 0.000000
v -0.100000 0.000000 0.000000
v -0.098481 -0.017365 0.000000
v -0.093969 -0.034202 0.000000
v -0.086603 -0.050000 0.000000
v -0.076604 -0.064279 0.000000
v -0.064279 -0.076604 0.000000
v -0.050000 -0.086603 0.000000
v -0.034202 -0.093969 0.000000
v -0.017365 -0.098481 0.000000
v -0.000000 -0.100000 0.000000
v 0.017365 -0.098481 0.000000
v 0.034202 -0.093969 0.000000
v 0.050000 -0.086603 0.000000
v 0.064279 -0.076604 0.000000
v 0.076604 -0.064279 0.000000
v 0.086603 -0.050000 0.000000
v 0.093969 -0.034202 0.000000
v 0.098481 -0.017365 0.000000
v 0.025000 0.000000 0.270000
v 0.024620 0.004341 0.270000
v 0.023492 0.008551 0.270000
v 0.021651 0.012500 0.270000
v 0.019151 0.016070 0.270000
v 0.016070 0.019151 0.270000
v 0.012500 0.021651 0.270000
v 0.008551 0.023492 0.270000
v 0.004341 0.024620 0.270000
v 0.000000 0.025000 0.270000
v -0.004341 0.024620 0.270000
v -0.008551 0.023492 0.270000
v -0.012500 0.021651 0.270000
v -0.016070 0.019151 0.270000
v -0.019151 0.016070 0.270000
v -0.021651 0.012500 0.270000
v -0.023492 0.008551 0.270000
v -0.024620 0.004341 0.270000
v -0.025000 0.000000 0.270000
v -0.024620 -0.004341 0.270000
v -0.023492 -0.008551 0.270000
v -0.021651 -0.012500 0.270000
v -0.019151 -0.016070 0.270000
v -0.016070 -0.019151 0.270000
v -0.012500 -0.021651 0.270000
v -0.008551 -0.023492 0.270000
v -0.004341 -0.024620 0.270000
v -0.000000 -0.025000 0.270000
v 0.004341 -0.024620 0.270000
v 0.008551 -0.023492 0.270000
v 0.012500 -0.021651 0.270000
v 0.016070 -0.019151 0.270000
v 0.019151 -0.016070 0.270000
v 0.021651 -0.012500 0.270000
v 0.023492 -0.008551 0.270000
v 0.024620 -0.004341 0.270000
v 0.000000 0.000000 0.000000
v 0.000000 0.000000 0.270000
f 1 2 38
f 1 38 37
f 2 3 39
f 2 39 38
f 3 4 40
f 3 40 39
f 4 5 41
f 4 41 40
f 5 6 42
f 5 42 41
f 6 7 43
f 6 43 42
f 7 8 44
f 7 44 43
f 8 9 45
f 8 45 44
f 9 10 46
f 9 46 45
f 10 11 47
f 10 47 46
f 11 12 48
f 11 48 47
f 12 13 49
f 12 49 48
f 13 14 50
f 13 50 49
f 14 15 51
f 14 51 50
f 15 16 52
f 15 52 51
f 16 17 53
f 16 53 52
f 17 18 54
f 17 54 53
f 18 19 55
f 18 55 54
f 19 20 56
f 19 56 55
f 20 21 57
f 20 57 56
f 21 22 58
f 21 58 57
f 22 23 59
f 22 59 58
f 23 24 60
f 23 60 59
f 24 25 61
f 24 61 60
f 25 26 62
f 25 62 61
f 26 27 63
f 26 63 62
f 27 28 64
f 27 64 63
f 28 29 65
f 28 65 64
f 29 30 66
f 29 66 65
f 30 31 67
f 30 67 66
f 31 32 68
f 31 68 67
f 32 33 69
f 32 69 68
f 33 34 70
f 33 70 69
f 34 35 71
f 34 71 70
f 35 36 72
f 35 72 71
f 36 1 37
f 36 37 72
f 73 2 1
f 73 3 2
f 73 4 3
f 73 5 4
f 73 6 5
f 73 7 6
f 73 8 7
f 73 9 8
f 73 10 9
f 73 11 10
f 73 12 11
f 73 13 12
f 73 14 13
f 73 15 14
f 73 16 15
f 73 17 16
f 73 18 17
f 73 19 18
f 73 20 19
f 73 21 20
f 73 22 21
f 73 23 22
f 73 24 23
f 73 25 24
f 73 26 25
f 73 27 26
f 73 28 27
f 73 29 28
f 73 30 29
f 73 31 30
f 73 32 31
f 73 33 32
f 73 34 33
f 73 35 34
f 73 36 35
f 73 1 36
f 74 37 38
f 74 38 39
f 74 39 40
f 74 40 41
f 74 41 42
f 74 42 43
f 74 43 44
f 74 44 45
f 74 45 46
f 74 46 47
f 74 47 48
f 74 48 49
f 74 49 50
f 74 50 51
f 74 51 52
f 74 52 53
f 74 53 54
f 74 54 55
f 74 55 56
f 74 56 57
f 74 57 58
f 74 58 59
f 74 59 60
f 74 60 61
f 74 61 62
f 74 62 63
f 74 63 64
f 74 64 65
f 74 65 66
f 74 66 67
f 74 67 68
f 74 68 69
f 74 69 70
f 74 70 71
f 74 71 72
f 74 72 37

145
cone_white.obj Normal file
View File

@@ -0,0 +1,145 @@
# White band
v 0.066700 0.000000 0.000000
v 0.065687 0.011582 0.000000
v 0.062677 0.022813 0.000000
v 0.057764 0.033350 0.000000
v 0.051095 0.042874 0.000000
v 0.042874 0.051095 0.000000
v 0.033350 0.057764 0.000000
v 0.022813 0.062677 0.000000
v 0.011582 0.065687 0.000000
v 0.000000 0.066700 0.000000
v -0.011582 0.065687 0.000000
v -0.022813 0.062677 0.000000
v -0.033350 0.057764 0.000000
v -0.042874 0.051095 0.000000
v -0.051095 0.042874 0.000000
v -0.057764 0.033350 0.000000
v -0.062677 0.022813 0.000000
v -0.065687 0.011582 0.000000
v -0.066700 0.000000 0.000000
v -0.065687 -0.011582 0.000000
v -0.062677 -0.022813 0.000000
v -0.057764 -0.033350 0.000000
v -0.051095 -0.042874 0.000000
v -0.042874 -0.051095 0.000000
v -0.033350 -0.057764 0.000000
v -0.022813 -0.062677 0.000000
v -0.011582 -0.065687 0.000000
v -0.000000 -0.066700 0.000000
v 0.011582 -0.065687 0.000000
v 0.022813 -0.062677 0.000000
v 0.033350 -0.057764 0.000000
v 0.042874 -0.051095 0.000000
v 0.051095 -0.042874 0.000000
v 0.057764 -0.033350 0.000000
v 0.062677 -0.022813 0.000000
v 0.065687 -0.011582 0.000000
v 0.038900 0.000000 0.100000
v 0.038309 0.006755 0.100000
v 0.036554 0.013305 0.100000
v 0.033688 0.019450 0.100000
v 0.029799 0.025004 0.100000
v 0.025004 0.029799 0.100000
v 0.019450 0.033688 0.100000
v 0.013305 0.036554 0.100000
v 0.006755 0.038309 0.100000
v 0.000000 0.038900 0.100000
v -0.006755 0.038309 0.100000
v -0.013305 0.036554 0.100000
v -0.019450 0.033688 0.100000
v -0.025004 0.029799 0.100000
v -0.029799 0.025004 0.100000
v -0.033688 0.019450 0.100000
v -0.036554 0.013305 0.100000
v -0.038309 0.006755 0.100000
v -0.038900 0.000000 0.100000
v -0.038309 -0.006755 0.100000
v -0.036554 -0.013305 0.100000
v -0.033688 -0.019450 0.100000
v -0.029799 -0.025004 0.100000
v -0.025004 -0.029799 0.100000
v -0.019450 -0.033688 0.100000
v -0.013305 -0.036554 0.100000
v -0.006755 -0.038309 0.100000
v -0.000000 -0.038900 0.100000
v 0.006755 -0.038309 0.100000
v 0.013305 -0.036554 0.100000
v 0.019450 -0.033688 0.100000
v 0.025004 -0.029799 0.100000
v 0.029799 -0.025004 0.100000
v 0.033688 -0.019450 0.100000
v 0.036554 -0.013305 0.100000
v 0.038309 -0.006755 0.100000
f 1 2 38
f 1 38 37
f 2 3 39
f 2 39 38
f 3 4 40
f 3 40 39
f 4 5 41
f 4 41 40
f 5 6 42
f 5 42 41
f 6 7 43
f 6 43 42
f 7 8 44
f 7 44 43
f 8 9 45
f 8 45 44
f 9 10 46
f 9 46 45
f 10 11 47
f 10 47 46
f 11 12 48
f 11 48 47
f 12 13 49
f 12 49 48
f 13 14 50
f 13 50 49
f 14 15 51
f 14 51 50
f 15 16 52
f 15 52 51
f 16 17 53
f 16 53 52
f 17 18 54
f 17 54 53
f 18 19 55
f 18 55 54
f 19 20 56
f 19 56 55
f 20 21 57
f 20 57 56
f 21 22 58
f 21 58 57
f 22 23 59
f 22 59 58
f 23 24 60
f 23 60 59
f 24 25 61
f 24 61 60
f 25 26 62
f 25 62 61
f 26 27 63
f 26 63 62
f 27 28 64
f 27 64 63
f 28 29 65
f 28 65 64
f 29 30 66
f 29 66 65
f 30 31 67
f 30 67 66
f 31 32 68
f 31 68 67
f 32 33 69
f 32 69 68
f 33 34 70
f 33 70 69
f 34 35 71
f 34 71 70
f 35 36 72
f 35 72 71
f 36 1 37
f 36 37 72

369
main.py
View File

@@ -1,5 +1,372 @@
import io
import json
import math
import os
import random
import threading
from collections import deque
from http.server import BaseHTTPRequestHandler, HTTPServer
import numpy as np
from PIL import Image as PILImage
from motrixsim import SceneData, load_model, run, step
from motrixsim.render import CaptureTask, Layout, RenderApp
# ── Ackermann geometry ──────────────────────────────────────────────
L = 0.1682 # wheelbase: front-rear axle distance
T = 0.189 # track width: left-right wheel distance
PORT = 8765
# ── MPU6050 noise parameters ────────────────────────────────────────
GYRO_NOISE_STD = 0.01 # rad/s
ACCEL_NOISE_STD = 0.05 # m/s²
GYRO_BIAS_WALK = 1e-5 # rad/s per step
ACCEL_BIAS_WALK = 1e-4 # m/s² per step
def ackermann_angles(steer_cmd_rad):
if abs(steer_cmd_rad) < 1e-6:
return 0.0, 0.0
R = L / math.tan(abs(steer_cmd_rad))
inner = math.atan(L / (R - T / 2))
outer = math.atan(L / (R + T / 2))
if steer_cmd_rad > 0:
return outer, inner
else:
return -inner, -outer
def quat_to_body_rotation(qw, qx, qy, qz):
"""World-frame vector → body-frame vector using quaternion."""
v = np.array
R = np.array([
[1 - 2*(qy*qy + qz*qz), 2*(qx*qy + qw*qz), 2*(qx*qz - qw*qy)],
[ 2*(qx*qy - qw*qz), 1 - 2*(qx*qx + qz*qz), 2*(qy*qz + qw*qx)],
[ 2*(qx*qz + qw*qy), 2*(qy*qz - qw*qx), 1 - 2*(qx*qx + qy*qy)],
])
return R
class MPU6050Sim:
def __init__(self):
self.gyro_bias = np.zeros(3)
self.accel_bias = np.zeros(3)
def apply(self, gyro_true, accel_true):
self.gyro_bias += np.random.normal(0, GYRO_BIAS_WALK, 3)
self.accel_bias += np.random.normal(0, ACCEL_BIAS_WALK, 3)
gyro = gyro_true + self.gyro_bias + np.random.normal(0, GYRO_NOISE_STD, 3)
accel = accel_true + self.accel_bias + np.random.normal(0, ACCEL_NOISE_STD, 3)
return gyro, accel
# ── Shared state (main ↔ HTTP thread) ──────────────────────────────
shared = {
"latest_jpg": None, # bytes
"cmd_speed": 0.0, # from POST /cmd
"cmd_steer": 0.0, # from POST /cmd
"imu_json": b"{}",
"state_json": b"{}",
"key_active": False, # True when W/A/S/D pressed
}
shared_lock = threading.Lock()
class HTTPHandler(BaseHTTPRequestHandler):
def log_message(self, format, *args):
pass # suppress logs
def _send_json(self, data):
body = json.dumps(data).encode()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _send_jpg(self, jpg_bytes):
self.send_response(200)
self.send_header("Content-Type", "image/jpeg")
self.send_header("Content-Length", str(len(jpg_bytes)))
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
self.end_headers()
self.wfile.write(jpg_bytes)
def do_GET(self):
if self.path == "/image":
with shared_lock:
jpg = shared["latest_jpg"]
if jpg:
self._send_jpg(jpg)
else:
self.send_response(204)
self.end_headers()
elif self.path == "/state":
with shared_lock:
body = shared["state_json"]
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
elif self.path == "/imu":
with shared_lock:
body = shared["imu_json"]
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
else:
self.send_response(404)
self.end_headers()
def do_POST(self):
if self.path == "/cmd":
length = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(length))
with shared_lock:
shared["cmd_speed"] = float(body.get("speed", 0))
shared["cmd_steer"] = float(body.get("steer", 0))
self.send_response(200)
self.end_headers()
else:
self.send_response(404)
self.end_headers()
def start_http_server():
server = HTTPServer(("0.0.0.0", PORT), HTTPHandler)
server.serve_forever()
# ── Main ────────────────────────────────────────────────────────────
def main():
print("Hello from motrixsim-examples!")
# Start HTTP server in background thread
http_thread = threading.Thread(target=start_http_server, daemon=True)
http_thread.start()
with RenderApp() as render:
render.opt.set_left_panel_vis(True)
model = load_model("scene.xml")
cameras = model.cameras
front_cam = cameras[0]
front_cam.set_render_target("image", 1280, 960)
render.launch(model)
render.widgets.create_camera_viewport(
front_cam, layout=Layout(right=0, top=0, width=480, height=360)
)
data = SceneData(model)
init_pos = data.dof_pos.copy()
init_pos[0] = -2.0
init_pos[1] = -2.3
init_pos[5] = 0.0
init_pos[6] = 1.0
data.set_dof_pos(init_pos, model)
# Drive actuators
drive_dl = model.get_actuator("down_left_drive")
drive_dr = model.get_actuator("down_right_drive")
drive_ul = model.get_actuator("up_left_drive")
drive_ur = model.get_actuator("up_right_drive")
# Steering actuators
steer_ul = model.get_actuator("up_left_steer")
steer_ur = model.get_actuator("up_right_steer")
speed = 0.0
steer_cmd = 0.0
speed_amp = 1.0
steer_amp = 0.35
frame = 0
base_link = model.get_link("base_link")
# IMU state
imu = MPU6050Sim()
prev_vel_world = np.zeros(3)
timestep = float(model.options.timestep)
# Continuous image capture
capture_task = None
capture_tasks: deque[tuple[int, CaptureTask]] = deque()
capture_index = 0
print("=" * 50)
print("Origincar — HTTP API on port", PORT)
print(" http://<ip>:{}/image — camera JPEG".format(PORT))
print(" http://<ip>:{}/state — position & speed".format(PORT))
print(" http://<ip>:{}/imu — IMU (MPU6050 noise)".format(PORT))
print(" POST /cmd {\"speed\":1.0,\"steer\":0.3}")
print("=" * 50)
def phys_step():
nonlocal prev_vel_world
# ── P-controller ──
vel = base_link.get_linear_velocity(data)
pose = base_link.get_pose(data)
qw, qx, qy, qz = pose[3], pose[4], pose[5], pose[6]
fwd_x = 1 - 2 * (qy * qy + qz * qz)
fwd_y = 2 * (qx * qy + qw * qz)
actual_speed = vel[0] * fwd_x + vel[1] * fwd_y
error = speed - actual_speed
kp = 5.0
base_torque = max(-15.0, min(15.0, kp * error))
# ── Differential ──
if abs(steer_cmd) < 1e-6:
factor_l, factor_r = 1.0, 1.0
else:
R_turn = L / math.tan(abs(steer_cmd))
outer_f = (R_turn + T / 2) / R_turn
inner_f = (R_turn - T / 2) / R_turn
if steer_cmd > 0:
factor_l, factor_r = inner_f, outer_f
else:
factor_l, factor_r = outer_f, inner_f
tq_l = max(-15.0, min(15.0, base_torque * factor_l))
tq_r = max(-15.0, min(15.0, base_torque * factor_r))
drive_dl.set_ctrl(data, tq_l)
drive_dr.set_ctrl(data, -tq_r)
drive_ul.set_ctrl(data, tq_l)
drive_ur.set_ctrl(data, -tq_r)
steer_l, steer_r = ackermann_angles(steer_cmd)
steer_ul.set_ctrl(data, steer_l)
steer_ur.set_ctrl(data, steer_r)
step(model, data)
# ── IMU with MPU6050 noise ──
ang_vel = np.array(base_link.get_angular_velocity(data)) # world frame
vel_world = np.array(vel)
accel_world = (vel_world - prev_vel_world) / timestep
prev_vel_world = vel_world
# Rotate to body frame
R_w2b = quat_to_body_rotation(qw, qx, qy, qz)
ang_vel_body = R_w2b @ ang_vel
accel_body = R_w2b @ accel_world
gyro_n, accel_n = imu.apply(ang_vel_body, accel_body)
with shared_lock:
shared["imu_json"] = json.dumps({
"orientation": {"w": float(qw), "x": float(qx), "y": float(qy), "z": float(qz)},
"angular_velocity": {"x": float(gyro_n[0]), "y": float(gyro_n[1]), "z": float(gyro_n[2])},
"linear_acceleration": {"x": float(accel_n[0]), "y": float(accel_n[1]), "z": float(accel_n[2])},
}).encode()
def render_step():
nonlocal speed, steer_cmd, capture_index, frame, speed_amp, steer_amp
render.sync(data)
inp = render.input
frame += 1
# ── Keyboard controls (speed & steer independent) ──
if inp.is_key_just_pressed("up"):
speed_amp = min(speed_amp + 0.2, 2.0)
if inp.is_key_just_pressed("down"):
speed_amp = max(speed_amp - 0.2, 0.0)
if inp.is_key_just_pressed("left"):
steer_amp = min(steer_amp + 0.05, 0.5)
if inp.is_key_just_pressed("right"):
steer_amp = max(steer_amp - 0.05, 0.0)
speed = 0.0
if inp.is_key_pressed("w"):
speed = speed_amp
if inp.is_key_pressed("s"):
speed = -speed_amp
steer_cmd = 0.0 # always reset — returns to center when A/D released
if inp.is_key_pressed("a"):
steer_cmd = steer_amp
if inp.is_key_pressed("d"):
steer_cmd = -steer_amp
key_active = speed != 0.0 or steer_cmd != 0.0
# ── HTTP /cmd override (if keyboard not active) ──
if not key_active:
with shared_lock:
speed = shared["cmd_speed"]
steer_cmd = shared["cmd_steer"]
with shared_lock:
shared["key_active"] = key_active
# ── Continuous image capture (~30 Hz) ──
if frame % 2 == 0:
rcam = render.get_camera(0)
capture_tasks.append((capture_index, rcam.capture()))
capture_index += 1
while capture_tasks:
idx, task = capture_tasks[0]
if task.state != "pending":
capture_tasks.popleft()
try:
img = task.take_image()
if img is not None:
arr = img.pixels
if arr.shape[2] == 4:
pil = PILImage.fromarray(arr, "RGBA").convert("RGB")
else:
pil = PILImage.fromarray(arr, "RGB")
buf = io.BytesIO()
pil.save(buf, format="JPEG", quality=80)
with shared_lock:
shared["latest_jpg"] = buf.getvalue()
except Exception:
pass
else:
break
# ── Console status ──
if frame % 60 == 0:
pose = base_link.get_pose(data)
x, y, z = pose[0], pose[1], pose[2]
qw, qx, qy, qz = pose[3], pose[4], pose[5], pose[6]
yaw = math.atan2(2 * (qw * qz + qx * qy), 1 - 2 * (qy * qy + qz * qz))
v = base_link.get_linear_velocity(data)
actual_spd = math.hypot(v[0], v[1])
with shared_lock:
shared["state_json"] = json.dumps({
"x": round(float(x), 3), "y": round(float(y), 3), "z": round(float(z), 3),
"yaw_deg": round(math.degrees(yaw), 1),
"speed_target": round(speed, 2),
"speed_actual": round(float(actual_spd), 2),
"steer_target": round(steer_cmd, 3),
"speed_amp": round(speed_amp, 1),
"steer_amp": round(steer_amp, 3),
}).encode()
print(f"[pos] x={x:+.3f} y={y:+.3f} z={z:+.3f} | yaw={math.degrees(yaw):+.1f}° "
f"spd={speed:.1f}/{actual_spd:.2f} steer={steer_cmd:+.2f}", flush=True)
# ── Snapshot to disk ──
if inp.is_key_just_pressed("space"):
with shared_lock:
jpg = shared["latest_jpg"]
if jpg:
os.makedirs("captures", exist_ok=True)
path = f"captures/front_cam_{capture_index:04d}.jpg"
with open(path, "wb") as f:
f.write(jpg)
print(f"Captured: {path}")
run.render_loop(model.options.timestep, 60, phys_step, render_step)
if __name__ == "__main__":

BIN
map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

BIN
map_full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

View File

@@ -4,4 +4,7 @@ version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.13"
dependencies = []
dependencies = [
"motrixsim-core",
"pillow",
]

148
scene.xml Normal file
View File

@@ -0,0 +1,148 @@
<mujoco model="origincar_scene">
<compiler angle="radian" autolimits="true"/>
<option timestep="0.005" iterations="10" solver="PGS"/>
<statistic center="0 0 0.1" extent="3"/>
<visual>
<headlight diffuse="0.6 0.6 0.6" ambient="0.3 0.3 0.3" specular="0 0 0"/>
<rgba haze="0.15 0.25 0.35 1"/>
<global azimuth="120" elevation="-20"/>
<quality shadowsize="4096"/>
</visual>
<asset>
<texture name="checker_tex" type="2d" builtin="checker" width="512" height="512" mark="edge"
rgb1="0.35 0.45 0.55" rgb2="0.55 0.65 0.75" markrgb="0.15 0.15 0.15"/>
<material name="checker_mat" texture="checker_tex" texrepeat="5 5" texuniform="true" reflectance="0.05"/>
<mesh name="cone_mesh" file="cone.obj"/>
<mesh name="cone_white_mesh" file="cone_white.obj"/>
<texture name="map_tex" type="2d" file="map_full.png"/>
<material name="map_mat" texture="map_tex" texrepeat="0.2 0.2" texuniform="true" reflectance="0.0"/>
<texture type="skybox" builtin="gradient" rgb1="0.4 0.5 0.6" rgb2="0 0 0" width="512" height="512"/>
</asset>
<default>
<geom density="500"/>
<joint damping="0.5"/>
<default class="wheel">
<geom type="cylinder" size="0.03 0.0125" density="1000" condim="3"/>
<joint damping="0.2"/>
</default>
<default class="steering">
<joint axis="0 0 1" damping="2" limited="true" range="-0.6 0.6"/>
<geom type="sphere" size="0.015" density="500" contype="0" conaffinity="0" rgba="0 0 0 0"/>
</default>
<default class="cone_blue">
<geom density="300" rgba="0.0 0.0 0.5 1"/>
</default>
</default>
<worldbody>
<light name="sun" pos="0 0 5" dir="0 0 -1" directional="true" castshadow="true"
diffuse="0.8 0.8 0.8" specular="0.2 0.2 0.2"/>
<!-- Checkerboard ground (infinite) -->
<geom name="floor" type="plane" size="0 0 0.01" material="checker_mat"
friction="0.6 0.1 0.1" condim="3"/>
<!-- 5m x 5m map tile, just above the ground -->
<geom name="map_tile" type="box" size="2.5 2.5 0.002" pos="0 0 0.002"
material="map_mat" friction="0.6 0.1 0.1" condim="3"/>
<!-- 40cm white fence around the map -->
<!-- ===== Blue cone obstacles ===== -->
<!-- Square base 20x20x3cm + cone taper 20cm→2cm over 27cm, total H=30cm -->
<!-- White reflective strip at 2/3 height (z=20cm) -->
<body name="cone_1" pos=" 1.0 1.0 0.0">
<geom type="box" size="0.10 0.10 0.015" pos="0 0 0.015" class="cone_blue"/>
<geom type="mesh" mesh="cone_mesh" pos="0 0 0.03" class="cone_blue"/>
<geom type="mesh" mesh="cone_white_mesh" pos="0 0 0.15" rgba="1 1 1 1"/>
</body>
<body name="cone_2" pos="-1.0 0.5 0.0">
<geom type="box" size="0.10 0.10 0.015" pos="0 0 0.015" class="cone_blue"/>
<geom type="mesh" mesh="cone_mesh" pos="0 0 0.03" class="cone_blue"/>
<geom type="mesh" mesh="cone_white_mesh" pos="0 0 0.15" rgba="1 1 1 1"/>
</body>
<body name="cone_3" pos=" 0.0 -1.5 0.0">
<geom type="box" size="0.10 0.10 0.015" pos="0 0 0.015" class="cone_blue"/>
<geom type="mesh" mesh="cone_mesh" pos="0 0 0.03" class="cone_blue"/>
<geom type="mesh" mesh="cone_white_mesh" pos="0 0 0.15" rgba="1 1 1 1"/>
</body>
<body name="cone_4" pos=" 1.5 -1.0 0.0">
<geom type="box" size="0.10 0.10 0.015" pos="0 0 0.015" class="cone_blue"/>
<geom type="mesh" mesh="cone_mesh" pos="0 0 0.03" class="cone_blue"/>
<geom type="mesh" mesh="cone_white_mesh" pos="0 0 0.15" rgba="1 1 1 1"/>
</body>
<geom name="fence_n" type="box" size="2.5 0.02 0.2" pos="0 2.5 0.2" rgba="1 1 1 1"/>
<geom name="fence_s" type="box" size="2.5 0.02 0.2" pos="0 -2.5 0.2" rgba="1 1 1 1"/>
<geom name="fence_e" type="box" size="0.02 2.5 0.2" pos=" 2.5 0 0.2" rgba="1 1 1 1"/>
<geom name="fence_w" type="box" size="0.02 2.5 0.2" pos="-2.5 0 0.2" rgba="1 1 1 1"/>
<body name="base_link" pos="0 0 0.08">
<freejoint/>
<geom name="base_geom" type="box" size="0.138 0.082 0.04" pos="0 0 0.07"
rgba="0.4 0.039 0.039 1" contype="0" conaffinity="0"/>
<!-- Camera: look along +X, right=-Y, up=+Z -->
<camera name="front_cam" pos="0.1205 0 0.11" xyaxes="0 -1 0 0 0 1" fovy="60"/>
<body name="camera" pos="0.1205 0 0.11">
<geom name="camera_geom" type="box" size="0.0075 0.0225 0.015" pos="0 0 0.015"
rgba="0.365 0.306 0.306 1"/>
</body>
<body name="board_link" pos="-0.038 0 0.11">
<geom name="board_geom" type="box" size="0.100 0.082 0.035" pos="0 0 0.035"
rgba="0.965 0.596 0.596 1"/>
</body>
<!-- ===== Rear wheels (drive only, no steering) ===== -->
<body name="down_left_Link" pos="-0.0841 0.0945 0.03">
<joint name="down_left_joint" axis="0 1 0"/>
<geom class="wheel" zaxis="0 1 0" rgba="0.745 0.129 0.129 1"/>
</body>
<body name="down_right_Link" pos="-0.0841 -0.0945 0.03">
<joint name="down_right_joint" axis="0 -1 0"/>
<geom class="wheel" zaxis="0 -1 0" rgba="0.745 0.129 0.129 1"/>
</body>
<!-- ===== Front wheels (steer + drive) ===== -->
<body name="up_left_steer" pos="0.0841 0.0945 0.03">
<joint name="up_left_steer_joint" axis="0 0 1" limited="true" range="-0.6 0.6"/>
<geom class="steering"/>
<body name="up_left_Link">
<joint name="up_left_drive_joint" axis="0 1 0"/>
<geom class="wheel" zaxis="0 1 0" rgba="0.745 0.129 0.129 1"/>
</body>
</body>
<body name="up_right_steer" pos="0.0841 -0.0945 0.03">
<joint name="up_right_steer_joint" axis="0 0 1" limited="true" range="-0.6 0.6"/>
<geom class="steering"/>
<body name="up_right_Link">
<joint name="up_right_drive_joint" axis="0 -1 0"/>
<geom class="wheel" zaxis="0 -1 0" rgba="0.745 0.129 0.129 1"/>
</body>
</body>
</body>
</worldbody>
<actuator>
<!-- Drive motors (torque) for all 4 wheels -->
<motor name="down_left_drive" joint="down_left_joint" ctrlrange="-20 20"/>
<motor name="down_right_drive" joint="down_right_joint" ctrlrange="-20 20"/>
<motor name="up_left_drive" joint="up_left_drive_joint" ctrlrange="-20 20"/>
<motor name="up_right_drive" joint="up_right_drive_joint" ctrlrange="-20 20"/>
<!-- Steering position servos (kp/kv matched to car.py pattern) -->
<position name="up_left_steer" joint="up_left_steer_joint" ctrlrange="-0.6 0.6" kp="3000" kv="300"/>
<position name="up_right_steer" joint="up_right_steer_joint" ctrlrange="-0.6 0.6" kp="3000" kv="300"/>
</actuator>
</mujoco>

150
uv.lock generated Normal file
View File

@@ -0,0 +1,150 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "absl-py"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7a/8f/fc001b92ecc467cc32ab38398bd0bfb45df46e7523bf33c2ad22a505f06e/absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff", size = 118055, upload-time = "2024-01-16T22:14:26.154Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/ad/e0d3c824784ff121c03cc031f944bc7e139a8f1870ffd2845cc2dd76f6c4/absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308", size = 133706, upload-time = "2024-01-16T22:14:24.055Z" },
]
[[package]]
name = "motrixsim-core"
version = "0.8.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "absl-py" },
{ name = "numpy" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/fc/811c71d6053006ed6afb2a46fed61a6ca294cb9dc5d93bc99c55f05ceab9/motrixsim_core-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:57f37d36b6399b82cd06e88285529c056e487f063edbb4f697aaeb052456dabd", size = 47082098, upload-time = "2026-06-08T14:30:54.736Z" },
{ url = "https://files.pythonhosted.org/packages/18/b6/3f0f9b85ea90d70f1ac0c82640c6ed59d83d11409723d909ba1c5a95934f/motrixsim_core-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0504de2c260380d40ed9393d699034a2cdaf7aff9cf6fe97d55f05b080f4bd0e", size = 54718509, upload-time = "2026-06-08T14:31:33.642Z" },
{ url = "https://files.pythonhosted.org/packages/61/12/d8476ee4da47b5c54ac1625b7645830b9116dbc436a2650a8e727a5aa0d7/motrixsim_core-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf76189cbf1db814a66694a32653d8f7423ee072122f8f8f57da23243f2f695b", size = 52207612, upload-time = "2026-06-08T14:31:43.047Z" },
{ url = "https://files.pythonhosted.org/packages/c7/3e/f62c0c46cf89da0f174e39fb058ff9e71fa3dcd9ffb42b6d4c3923660700/motrixsim_core-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:e91340f961c71d989b34d6717b06a17c3f6a0b8930bc5d47e12a7e485cbd6903", size = 40402629, upload-time = "2026-06-08T14:30:26.842Z" },
]
[[package]]
name = "motrixsim-examples"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "motrixsim-core" },
{ name = "pillow" },
]
[package.metadata]
requires-dist = [
{ name = "motrixsim-core" },
{ name = "pillow" },
]
[[package]]
name = "numpy"
version = "2.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" },
{ url = "https://files.pythonhosted.org/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" },
{ url = "https://files.pythonhosted.org/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" },
{ url = "https://files.pythonhosted.org/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" },
{ url = "https://files.pythonhosted.org/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" },
{ url = "https://files.pythonhosted.org/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" },
{ url = "https://files.pythonhosted.org/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" },
{ url = "https://files.pythonhosted.org/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" },
{ url = "https://files.pythonhosted.org/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" },
{ url = "https://files.pythonhosted.org/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" },
{ url = "https://files.pythonhosted.org/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" },
{ url = "https://files.pythonhosted.org/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" },
{ url = "https://files.pythonhosted.org/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" },
{ url = "https://files.pythonhosted.org/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" },
{ url = "https://files.pythonhosted.org/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" },
{ url = "https://files.pythonhosted.org/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" },
{ url = "https://files.pythonhosted.org/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" },
{ url = "https://files.pythonhosted.org/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" },
{ url = "https://files.pythonhosted.org/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" },
{ url = "https://files.pythonhosted.org/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" },
{ url = "https://files.pythonhosted.org/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" },
{ url = "https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" },
{ url = "https://files.pythonhosted.org/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" },
{ url = "https://files.pythonhosted.org/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" },
{ url = "https://files.pythonhosted.org/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" },
{ url = "https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" },
{ url = "https://files.pythonhosted.org/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" },
{ url = "https://files.pythonhosted.org/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" },
{ url = "https://files.pythonhosted.org/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" },
{ url = "https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" },
{ url = "https://files.pythonhosted.org/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" },
{ url = "https://files.pythonhosted.org/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" },
{ url = "https://files.pythonhosted.org/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" },
{ url = "https://files.pythonhosted.org/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" },
{ url = "https://files.pythonhosted.org/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" },
{ url = "https://files.pythonhosted.org/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" },
{ url = "https://files.pythonhosted.org/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" },
{ url = "https://files.pythonhosted.org/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" },
{ url = "https://files.pythonhosted.org/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" },
{ url = "https://files.pythonhosted.org/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" },
{ url = "https://files.pythonhosted.org/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" },
]
[[package]]
name = "pillow"
version = "12.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" },
{ url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" },
{ url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" },
{ url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" },
{ url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" },
{ url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" },
{ url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" },
{ url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" },
{ url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" },
{ url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" },
{ url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" },
{ url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" },
{ url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" },
{ url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" },
{ url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" },
{ url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" },
{ url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" },
{ url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" },
{ url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" },
{ url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" },
{ url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" },
{ url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" },
{ url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" },
{ url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" },
{ url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" },
{ url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" },
{ url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" },
{ url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" },
{ url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" },
{ url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" },
{ url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" },
{ url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" },
{ url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" },
{ url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" },
{ url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" },
{ url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" },
{ url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" },
{ url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" },
{ url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" },
{ url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" },
{ url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" },
{ url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" },
{ url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" },
{ url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" },
{ url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" },
{ url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" },
{ url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" },
{ url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" },
{ url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" },
{ url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" },
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB