plexus-python 0.5.2__tar.gz → 0.6.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.gitignore +6 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/API.md +1 -1
- {plexus_python-0.5.2 → plexus_python-0.6.1}/CHANGELOG.md +15 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/PKG-INFO +6 -1
- {plexus_python-0.5.2 → plexus_python-0.6.1}/SECURITY.md +2 -2
- plexus_python-0.6.1/examples/thermal_camera.py +88 -0
- plexus_python-0.6.1/plexus/cameras/__init__.py +25 -0
- plexus_python-0.6.1/plexus/cameras/thermal.py +388 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/client.py +45 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/ws.py +7 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/pyproject.toml +7 -2
- plexus_python-0.6.1/tests/test_thermal.py +153 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/uv.lock +185 -3
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.github/workflows/ci.yml +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/.github/workflows/publish.yml +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/AGENTS.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/CODE_OF_CONDUCT.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/CONTRIBUTING.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/LICENSE +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/README.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/.python-version +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/README.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/basic.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/can.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/i2c_bme280.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/mac_metrics.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/mavlink.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/mqtt.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/pyproject.toml +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/examples/uv.lock +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/__init__.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/_log.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/buffer.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/cli.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/plexus/config.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/scripts/plexus.service +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/scripts/release.sh +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/scripts/scan_buses.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/scripts/setup.sh +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/skills/plexus/SKILL.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/skills/plexus/references/api.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/skills/plexus/references/sdk.md +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_basic.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_buffer.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_config.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_retry.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_video.py +0 -0
- {plexus_python-0.5.2 → plexus_python-0.6.1}/tests/test_ws.py +0 -0
|
@@ -145,7 +145,7 @@ For real-time UI-controlled streaming, devices connect via WebSocket.
|
|
|
145
145
|
|
|
146
146
|
### Connection Flow
|
|
147
147
|
|
|
148
|
-
1. Device connects to
|
|
148
|
+
1. Device connects to the gateway
|
|
149
149
|
2. Device authenticates with API key
|
|
150
150
|
3. Device reports available sensors
|
|
151
151
|
4. Dashboard controls streaming via messages
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1] - 2026-05-28 - Thermal camera streaming
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `plexus.cameras.thermal` module with hardware-agnostic drivers for I2C sensors
|
|
8
|
+
(MLX90640, MLX90641) and USB cameras (Y16 pixel format). All drivers return a
|
|
9
|
+
unified `ThermalFrame` containing a colorized JPEG plus temperature metadata
|
|
10
|
+
(min, max, mean °C).
|
|
11
|
+
- `ThermalSource.open()` auto-detects connected hardware; pass `"sim"` for a
|
|
12
|
+
simulated source with no physical device required.
|
|
13
|
+
- `client.send_thermal_frame()` sends thermal frames over the existing WebSocket
|
|
14
|
+
connection. Small sensors (≤4096 pixels, e.g. 32×24 MLX90640) include the full
|
|
15
|
+
temperature array inline; larger USB sensors omit it to keep frame sizes manageable.
|
|
16
|
+
- `examples/thermal_camera.py` — end-to-end example for streaming a thermal camera.
|
|
17
|
+
|
|
3
18
|
## [0.5.2] - 2026-05-21 - DX hardening for hardware engineers
|
|
4
19
|
|
|
5
20
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plexus-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Thin Python SDK for Plexus — send telemetry in one line
|
|
5
5
|
Project-URL: Homepage, https://plexus.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.plexus.dev
|
|
@@ -23,10 +23,15 @@ Classifier: Topic :: System :: Hardware
|
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: websocket-client>=1.7
|
|
25
25
|
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: numpy>=1.24; extra == 'dev'
|
|
27
|
+
Requires-Dist: opencv-python>=4.8; extra == 'dev'
|
|
26
28
|
Requires-Dist: pytest-cov; extra == 'dev'
|
|
27
29
|
Requires-Dist: pytest>=9.0.3; extra == 'dev'
|
|
28
30
|
Requires-Dist: ruff; extra == 'dev'
|
|
29
31
|
Requires-Dist: websockets>=12; extra == 'dev'
|
|
32
|
+
Provides-Extra: thermal
|
|
33
|
+
Requires-Dist: numpy>=1.24; extra == 'thermal'
|
|
34
|
+
Requires-Dist: opencv-python>=4.8; extra == 'thermal'
|
|
30
35
|
Provides-Extra: video
|
|
31
36
|
Requires-Dist: pillow>=12.2.0; extra == 'video'
|
|
32
37
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thermal camera streaming to Plexus.
|
|
3
|
+
|
|
4
|
+
Reads from any supported thermal camera and sends frames to Plexus using
|
|
5
|
+
the SDK's send_thermal_frame() method. The gateway relays colorized JPEG
|
|
6
|
+
frames to the app, along with temperature range and (for small sensors)
|
|
7
|
+
per-pixel temperature data.
|
|
8
|
+
|
|
9
|
+
Supported hardware (--camera argument):
|
|
10
|
+
sim Simulated camera — no hardware needed (default)
|
|
11
|
+
mlx90640 MLX90640 32×24 I2C sensor (pip install adafruit-circuitpython-mlx90640)
|
|
12
|
+
mlx90641 MLX90641 16×12 I2C sensor (pip install adafruit-circuitpython-mlx90641)
|
|
13
|
+
usb USB thermal camera in Y16 format (InfiRay, Topdon, Seek, etc.)
|
|
14
|
+
|
|
15
|
+
Run:
|
|
16
|
+
export PLEXUS_API_KEY=plx_xxx
|
|
17
|
+
python thermal_camera.py
|
|
18
|
+
python thermal_camera.py --camera mlx90640
|
|
19
|
+
python thermal_camera.py --camera usb --device 2
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import sys
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
from plexus import Plexus
|
|
27
|
+
from plexus.cameras.thermal import NoCameraFound, ThermalSource
|
|
28
|
+
|
|
29
|
+
CAMERA_ID = "thermal"
|
|
30
|
+
FPS = 5
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def main() -> None:
|
|
34
|
+
parser = argparse.ArgumentParser(description="Stream thermal camera to Plexus.")
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--camera",
|
|
37
|
+
choices=["sim", "mlx90640", "mlx90641", "usb"],
|
|
38
|
+
default=None,
|
|
39
|
+
help="Camera driver (default: auto-detect)",
|
|
40
|
+
)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--device",
|
|
43
|
+
type=int,
|
|
44
|
+
default=0,
|
|
45
|
+
help="USB video device index (default: 0)",
|
|
46
|
+
)
|
|
47
|
+
args = parser.parse_args()
|
|
48
|
+
|
|
49
|
+
hint = args.device if args.camera == "usb" else args.camera
|
|
50
|
+
try:
|
|
51
|
+
cam = ThermalSource.open(hint)
|
|
52
|
+
except NoCameraFound as e:
|
|
53
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
px = Plexus(transport="ws")
|
|
57
|
+
px.wait_connected()
|
|
58
|
+
|
|
59
|
+
interval = 1.0 / FPS
|
|
60
|
+
frame_count = 0
|
|
61
|
+
print(f"Streaming {cam.width}×{cam.height} thermal at {FPS} fps — Ctrl-C to stop")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
while True:
|
|
65
|
+
t0 = time.time()
|
|
66
|
+
|
|
67
|
+
temps = cam.read_frame()
|
|
68
|
+
px.send_thermal_frame(temps, camera_id=CAMERA_ID)
|
|
69
|
+
|
|
70
|
+
frame_count += 1
|
|
71
|
+
if frame_count % 50 == 0:
|
|
72
|
+
print(f" {frame_count} frames sent")
|
|
73
|
+
|
|
74
|
+
elapsed = time.time() - t0
|
|
75
|
+
wait = interval - elapsed
|
|
76
|
+
if wait > 0:
|
|
77
|
+
time.sleep(wait)
|
|
78
|
+
|
|
79
|
+
except KeyboardInterrupt:
|
|
80
|
+
pass
|
|
81
|
+
finally:
|
|
82
|
+
cam.close()
|
|
83
|
+
px.stop()
|
|
84
|
+
print(f"Done. {frame_count} frames sent.")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from plexus.cameras.thermal import (
|
|
2
|
+
NoCameraFound,
|
|
3
|
+
MLX90640Camera,
|
|
4
|
+
MLX90641Camera,
|
|
5
|
+
SimulatedThermalCamera,
|
|
6
|
+
ThermalCamera,
|
|
7
|
+
ThermalFrame,
|
|
8
|
+
ThermalSource,
|
|
9
|
+
USBThermalCamera,
|
|
10
|
+
build_thermal_frame,
|
|
11
|
+
encode_frame,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"NoCameraFound",
|
|
16
|
+
"MLX90640Camera",
|
|
17
|
+
"MLX90641Camera",
|
|
18
|
+
"SimulatedThermalCamera",
|
|
19
|
+
"ThermalCamera",
|
|
20
|
+
"ThermalFrame",
|
|
21
|
+
"ThermalSource",
|
|
22
|
+
"USBThermalCamera",
|
|
23
|
+
"build_thermal_frame",
|
|
24
|
+
"encode_frame",
|
|
25
|
+
]
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Thermal camera drivers for Plexus.
|
|
3
|
+
|
|
4
|
+
Provides a hardware-agnostic interface over I2C sensors (MLX90640, MLX90641)
|
|
5
|
+
and USB thermal cameras (Y16 pixel format). All drivers return a unified
|
|
6
|
+
ThermalFrame containing a colorized JPEG image plus temperature metadata.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
from plexus.cameras.thermal import ThermalSource
|
|
10
|
+
|
|
11
|
+
cam = ThermalSource.open() # auto-detect
|
|
12
|
+
cam = ThermalSource.open("sim") # simulated, no hardware
|
|
13
|
+
|
|
14
|
+
while True:
|
|
15
|
+
px.send_thermal_frame(cam.read_frame(), camera_id="thermal")
|
|
16
|
+
time.sleep(1 / 5)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import base64
|
|
22
|
+
import time
|
|
23
|
+
from abc import ABC, abstractmethod
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
26
|
+
|
|
27
|
+
import cv2
|
|
28
|
+
import numpy as np
|
|
29
|
+
|
|
30
|
+
# Inferno is perceptually uniform and well-suited for thermal imaging.
|
|
31
|
+
# Swap for cv2.COLORMAP_HOT or cv2.COLORMAP_JET if preferred.
|
|
32
|
+
_COLORMAP = cv2.COLORMAP_INFERNO
|
|
33
|
+
|
|
34
|
+
# Sensors at or below this pixel count include the full temps array in the
|
|
35
|
+
# wire message. I2C sensors (32×24 = 768, 16×12 = 192) always qualify.
|
|
36
|
+
# USB thermal cameras (256×192 = 49 152) do not.
|
|
37
|
+
_TEMPS_INLINE_THRESHOLD = 4096
|
|
38
|
+
|
|
39
|
+
# Upscale sensors whose shortest side is below this threshold.
|
|
40
|
+
_MIN_DISPLAY_SIDE = 160
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class NoCameraFound(RuntimeError):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Abstract interface
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ThermalCamera(ABC):
|
|
53
|
+
"""Hardware-agnostic thermal camera interface.
|
|
54
|
+
|
|
55
|
+
Subclass and implement read_frame() to support any sensor.
|
|
56
|
+
read_frame() must return a 2-D float32 array of temperatures in Celsius,
|
|
57
|
+
shaped (height, width).
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def width(self) -> int: ...
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
@abstractmethod
|
|
66
|
+
def height(self) -> int: ...
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def read_frame(self) -> np.ndarray: ...
|
|
70
|
+
|
|
71
|
+
def close(self) -> None:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Unified output
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class ThermalFrame:
|
|
82
|
+
"""Encoded output from a ThermalCamera, ready to send to the gateway."""
|
|
83
|
+
|
|
84
|
+
image: np.ndarray # uint8 BGR colorized image (display size)
|
|
85
|
+
width: int # display width (may differ from sensor if upscaled)
|
|
86
|
+
height: int # display height
|
|
87
|
+
sensor_width: int # native sensor width
|
|
88
|
+
sensor_height: int # native sensor height
|
|
89
|
+
temp_min: float
|
|
90
|
+
temp_max: float
|
|
91
|
+
temps: Optional[np.ndarray] # native res; None when sensor > threshold
|
|
92
|
+
timestamp_ms: int
|
|
93
|
+
|
|
94
|
+
def to_message(
|
|
95
|
+
self, camera_id: str, source_id: Optional[str] = None, quality: int = 85
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
"""Build the gateway `video_frame` wire message for this frame."""
|
|
98
|
+
_, buf = cv2.imencode(".jpg", self.image, [cv2.IMWRITE_JPEG_QUALITY, quality])
|
|
99
|
+
b64 = base64.b64encode(buf.tobytes()).decode("ascii")
|
|
100
|
+
|
|
101
|
+
msg: Dict[str, Any] = {
|
|
102
|
+
"type": "video_frame",
|
|
103
|
+
"camera_id": camera_id,
|
|
104
|
+
"frame": b64,
|
|
105
|
+
"width": self.width,
|
|
106
|
+
"height": self.height,
|
|
107
|
+
"timestamp": self.timestamp_ms,
|
|
108
|
+
"video_type": "thermal",
|
|
109
|
+
"sensor_width": self.sensor_width,
|
|
110
|
+
"sensor_height": self.sensor_height,
|
|
111
|
+
"temp_min": self.temp_min,
|
|
112
|
+
"temp_max": self.temp_max,
|
|
113
|
+
}
|
|
114
|
+
if source_id is not None:
|
|
115
|
+
msg["source_id"] = source_id
|
|
116
|
+
if self.temps is not None:
|
|
117
|
+
msg["temps"] = self.temps.flatten().tolist()
|
|
118
|
+
return msg
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
# Encoding — single shared path used by both encode_frame() and the SDK's
|
|
123
|
+
# Plexus.send_thermal_frame().
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _upscale_size(sw: int, sh: int) -> tuple[int, int]:
|
|
128
|
+
if sw >= _MIN_DISPLAY_SIDE and sh >= _MIN_DISPLAY_SIDE:
|
|
129
|
+
return sw, sh
|
|
130
|
+
scale = max(_MIN_DISPLAY_SIDE / sw, _MIN_DISPLAY_SIDE / sh)
|
|
131
|
+
return round(sw * scale), round(sh * scale)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def build_thermal_frame(
|
|
135
|
+
temps: np.ndarray, timestamp_ms: Optional[int] = None
|
|
136
|
+
) -> ThermalFrame:
|
|
137
|
+
"""Colorize a temperature array into a ThermalFrame.
|
|
138
|
+
|
|
139
|
+
temps: 2-D float32 Celsius array, shape (height, width).
|
|
140
|
+
timestamp_ms: wire timestamp; defaults to current time.
|
|
141
|
+
"""
|
|
142
|
+
sh, sw = temps.shape[:2]
|
|
143
|
+
|
|
144
|
+
temp_min = float(np.min(temps))
|
|
145
|
+
temp_max = float(np.max(temps))
|
|
146
|
+
span = temp_max - temp_min
|
|
147
|
+
|
|
148
|
+
normalized = (
|
|
149
|
+
np.zeros((sh, sw), dtype=np.uint8)
|
|
150
|
+
if span < 1e-6
|
|
151
|
+
else ((temps - temp_min) / span * 255).astype(np.uint8)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
colored = cv2.applyColorMap(normalized, _COLORMAP)
|
|
155
|
+
|
|
156
|
+
dw, dh = _upscale_size(sw, sh)
|
|
157
|
+
if (dw, dh) != (sw, sh):
|
|
158
|
+
colored = cv2.resize(colored, (dw, dh), interpolation=cv2.INTER_CUBIC)
|
|
159
|
+
|
|
160
|
+
return ThermalFrame(
|
|
161
|
+
image=colored,
|
|
162
|
+
width=dw,
|
|
163
|
+
height=dh,
|
|
164
|
+
sensor_width=sw,
|
|
165
|
+
sensor_height=sh,
|
|
166
|
+
temp_min=round(temp_min, 2),
|
|
167
|
+
temp_max=round(temp_max, 2),
|
|
168
|
+
temps=temps if sw * sh <= _TEMPS_INLINE_THRESHOLD else None,
|
|
169
|
+
timestamp_ms=timestamp_ms if timestamp_ms is not None else int(time.time() * 1000),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def encode_frame(cam: ThermalCamera) -> ThermalFrame:
|
|
174
|
+
"""Read one frame from a camera and colorize it."""
|
|
175
|
+
return build_thermal_frame(cam.read_frame())
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# Drivers
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class SimulatedThermalCamera(ThermalCamera):
|
|
184
|
+
"""Simulated thermal camera for testing without hardware.
|
|
185
|
+
|
|
186
|
+
Produces a moving warm blob over a noisy background at 32×24.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(self, width: int = 32, height: int = 24) -> None:
|
|
190
|
+
self._width = width
|
|
191
|
+
self._height = height
|
|
192
|
+
self._t = 0.0
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def width(self) -> int:
|
|
196
|
+
return self._width
|
|
197
|
+
|
|
198
|
+
@property
|
|
199
|
+
def height(self) -> int:
|
|
200
|
+
return self._height
|
|
201
|
+
|
|
202
|
+
def read_frame(self) -> np.ndarray:
|
|
203
|
+
self._t += 0.1
|
|
204
|
+
x = np.linspace(0, 2 * np.pi, self._width)
|
|
205
|
+
y = np.linspace(0, 2 * np.pi, self._height)
|
|
206
|
+
xx, yy = np.meshgrid(x, y)
|
|
207
|
+
base = 22.0 + 3.0 * np.sin(xx + self._t) * np.cos(yy + self._t * 0.7)
|
|
208
|
+
cx = int((np.sin(self._t * 0.5) * 0.4 + 0.5) * self._width)
|
|
209
|
+
cy = int((np.cos(self._t * 0.3) * 0.4 + 0.5) * self._height)
|
|
210
|
+
yg, xg = np.ogrid[: self._height, : self._width]
|
|
211
|
+
hotspot = 15.0 * np.exp(-((xg - cx) ** 2 + (yg - cy) ** 2) / 20.0)
|
|
212
|
+
return (base + hotspot).astype(np.float32)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class USBThermalCamera(ThermalCamera):
|
|
216
|
+
"""USB thermal camera via V4L2/UVC in Y16 pixel format.
|
|
217
|
+
|
|
218
|
+
Most USB thermal cameras (InfiRay, Topdon, Seek, some FLIR) present as
|
|
219
|
+
standard UVC video devices outputting Y16 frames where each uint16 pixel
|
|
220
|
+
encodes temperature as: celsius = (value / 100.0) - 273.15
|
|
221
|
+
|
|
222
|
+
Requires: pip install opencv-python
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
_KELVIN_SCALE = 100.0
|
|
226
|
+
_KELVIN_OFFSET = 273.15
|
|
227
|
+
|
|
228
|
+
def __init__(self, device_index: int = 0) -> None:
|
|
229
|
+
self._cap = cv2.VideoCapture(device_index)
|
|
230
|
+
if not self._cap.isOpened():
|
|
231
|
+
raise NoCameraFound(f"Cannot open video device {device_index}")
|
|
232
|
+
self._cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"Y16 "))
|
|
233
|
+
ret, frame = self._cap.read()
|
|
234
|
+
if not ret or frame is None:
|
|
235
|
+
self._cap.release()
|
|
236
|
+
raise NoCameraFound(
|
|
237
|
+
f"Device {device_index} opened but could not read a frame. "
|
|
238
|
+
"Check that it supports Y16 format."
|
|
239
|
+
)
|
|
240
|
+
self._width = int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
241
|
+
self._height = int(self._cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
242
|
+
|
|
243
|
+
@property
|
|
244
|
+
def width(self) -> int:
|
|
245
|
+
return self._width
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def height(self) -> int:
|
|
249
|
+
return self._height
|
|
250
|
+
|
|
251
|
+
def read_frame(self) -> np.ndarray:
|
|
252
|
+
ret, raw = self._cap.read()
|
|
253
|
+
if not ret or raw is None:
|
|
254
|
+
raise RuntimeError("Failed to read frame from USB thermal camera")
|
|
255
|
+
u16 = raw.view(np.uint16).reshape(self._height, self._width)
|
|
256
|
+
return (u16.astype(np.float32) / self._KELVIN_SCALE) - self._KELVIN_OFFSET
|
|
257
|
+
|
|
258
|
+
def close(self) -> None:
|
|
259
|
+
self._cap.release()
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class MLX90640Camera(ThermalCamera):
|
|
263
|
+
"""MLX90640 32×24 thermal array via I2C.
|
|
264
|
+
|
|
265
|
+
Requires: pip install adafruit-circuitpython-mlx90640
|
|
266
|
+
Wiring: SCL → GPIO 3, SDA → GPIO 2, 3.3V power (Raspberry Pi).
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
def __init__(self) -> None:
|
|
270
|
+
import adafruit_mlx90640
|
|
271
|
+
import board
|
|
272
|
+
import busio
|
|
273
|
+
|
|
274
|
+
i2c = busio.I2C(board.SCL, board.SDA, frequency=800_000)
|
|
275
|
+
self._mlx = adafruit_mlx90640.MLX90640(i2c)
|
|
276
|
+
self._mlx.refresh_rate = adafruit_mlx90640.RefreshRate.REFRESH_4_HZ
|
|
277
|
+
self._buf = [0.0] * 768
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def width(self) -> int:
|
|
281
|
+
return 32
|
|
282
|
+
|
|
283
|
+
@property
|
|
284
|
+
def height(self) -> int:
|
|
285
|
+
return 24
|
|
286
|
+
|
|
287
|
+
def read_frame(self) -> np.ndarray:
|
|
288
|
+
self._mlx.getFrame(self._buf)
|
|
289
|
+
return np.array(self._buf, dtype=np.float32).reshape(24, 32)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class MLX90641Camera(ThermalCamera):
|
|
293
|
+
"""MLX90641 16×12 thermal array via I2C.
|
|
294
|
+
|
|
295
|
+
Requires: pip install adafruit-circuitpython-mlx90641
|
|
296
|
+
Wiring: SCL → GPIO 3, SDA → GPIO 2, 3.3V power (Raspberry Pi).
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def __init__(self) -> None:
|
|
300
|
+
import adafruit_mlx90641
|
|
301
|
+
import board
|
|
302
|
+
import busio
|
|
303
|
+
|
|
304
|
+
i2c = busio.I2C(board.SCL, board.SDA, frequency=400_000)
|
|
305
|
+
self._mlx = adafruit_mlx90641.MLX90641(i2c)
|
|
306
|
+
self._mlx.refresh_rate = adafruit_mlx90641.RefreshRate.REFRESH_4_HZ
|
|
307
|
+
self._buf = [0.0] * 192
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def width(self) -> int:
|
|
311
|
+
return 16
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def height(self) -> int:
|
|
315
|
+
return 12
|
|
316
|
+
|
|
317
|
+
def read_frame(self) -> np.ndarray:
|
|
318
|
+
self._mlx.getFrame(self._buf)
|
|
319
|
+
return np.array(self._buf, dtype=np.float32).reshape(12, 16)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# ---------------------------------------------------------------------------
|
|
323
|
+
# Auto-detection factory
|
|
324
|
+
# ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class ThermalSource:
|
|
328
|
+
"""Factory that opens the first available thermal camera.
|
|
329
|
+
|
|
330
|
+
Examples:
|
|
331
|
+
ThermalSource.open() # auto-detect
|
|
332
|
+
ThermalSource.open("sim") # simulated
|
|
333
|
+
ThermalSource.open("mlx90640") # force I2C MLX90640
|
|
334
|
+
ThermalSource.open("mlx90641") # force I2C MLX90641
|
|
335
|
+
ThermalSource.open("usb") # USB thermal at index 0
|
|
336
|
+
ThermalSource.open(2) # USB thermal at index 2
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
@staticmethod
|
|
340
|
+
def open(hint: str | int | None = None) -> ThermalCamera:
|
|
341
|
+
if hint == "sim":
|
|
342
|
+
return SimulatedThermalCamera()
|
|
343
|
+
if hint == "mlx90640":
|
|
344
|
+
return MLX90640Camera()
|
|
345
|
+
if hint == "mlx90641":
|
|
346
|
+
return MLX90641Camera()
|
|
347
|
+
if hint == "usb" or isinstance(hint, int):
|
|
348
|
+
return USBThermalCamera(0 if hint == "usb" else hint)
|
|
349
|
+
return ThermalSource._autodetect()
|
|
350
|
+
|
|
351
|
+
@staticmethod
|
|
352
|
+
def _autodetect() -> ThermalCamera:
|
|
353
|
+
cam = ThermalSource._try_i2c()
|
|
354
|
+
if cam:
|
|
355
|
+
return cam
|
|
356
|
+
cam = ThermalSource._try_usb()
|
|
357
|
+
if cam:
|
|
358
|
+
return cam
|
|
359
|
+
raise NoCameraFound(
|
|
360
|
+
"No thermal camera detected. Use ThermalSource.open('sim') to run without hardware."
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
@staticmethod
|
|
364
|
+
def _try_i2c() -> ThermalCamera | None:
|
|
365
|
+
try:
|
|
366
|
+
import board
|
|
367
|
+
import busio
|
|
368
|
+
|
|
369
|
+
i2c = busio.I2C(board.SCL, board.SDA)
|
|
370
|
+
i2c.try_lock()
|
|
371
|
+
addrs = i2c.scan()
|
|
372
|
+
i2c.unlock()
|
|
373
|
+
if 0x33 in addrs:
|
|
374
|
+
return MLX90640Camera()
|
|
375
|
+
if 0x60 in addrs:
|
|
376
|
+
return MLX90641Camera()
|
|
377
|
+
except Exception:
|
|
378
|
+
pass
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def _try_usb() -> ThermalCamera | None:
|
|
383
|
+
for idx in range(10):
|
|
384
|
+
try:
|
|
385
|
+
return USBThermalCamera(idx)
|
|
386
|
+
except NoCameraFound:
|
|
387
|
+
continue
|
|
388
|
+
return None
|
|
@@ -590,6 +590,51 @@ class Plexus:
|
|
|
590
590
|
self._normalize_ts_ms(timestamp),
|
|
591
591
|
)
|
|
592
592
|
|
|
593
|
+
def send_thermal_frame(
|
|
594
|
+
self,
|
|
595
|
+
temps,
|
|
596
|
+
camera_id: str = "thermal:0",
|
|
597
|
+
quality: int = 85,
|
|
598
|
+
timestamp: Optional[float] = None,
|
|
599
|
+
) -> bool:
|
|
600
|
+
"""Send a thermal camera frame to Plexus (WebSocket transport only).
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
temps: 2-D float32 numpy array of temperatures in Celsius,
|
|
604
|
+
shape (height, width). Obtained from ThermalCamera.read_frame().
|
|
605
|
+
camera_id: Logical camera identifier.
|
|
606
|
+
quality: JPEG quality for the colorized image, 1-100.
|
|
607
|
+
timestamp: Unix timestamp in seconds. Defaults to current time.
|
|
608
|
+
|
|
609
|
+
Returns:
|
|
610
|
+
True if the frame was sent successfully.
|
|
611
|
+
|
|
612
|
+
Raises:
|
|
613
|
+
PlexusError: If transport is not 'ws'.
|
|
614
|
+
ImportError: If opencv-python is not installed.
|
|
615
|
+
"""
|
|
616
|
+
if self.transport != "ws":
|
|
617
|
+
raise PlexusError("send_thermal_frame requires transport='ws'")
|
|
618
|
+
|
|
619
|
+
try:
|
|
620
|
+
from plexus.cameras.thermal import build_thermal_frame
|
|
621
|
+
except ImportError as e:
|
|
622
|
+
raise ImportError(
|
|
623
|
+
"send_thermal_frame requires opencv-python. "
|
|
624
|
+
"Install with: pip install opencv-python"
|
|
625
|
+
) from e
|
|
626
|
+
|
|
627
|
+
frame = build_thermal_frame(temps, timestamp_ms=self._normalize_ts_ms(timestamp))
|
|
628
|
+
msg = frame.to_message(
|
|
629
|
+
camera_id=camera_id, source_id=self.source_id, quality=quality
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
ws = self._ensure_ws()
|
|
633
|
+
if not ws.is_authenticated:
|
|
634
|
+
ws.wait_authenticated(timeout=min(self.timeout, 5.0))
|
|
635
|
+
|
|
636
|
+
return ws.send_json_video_frame(msg)
|
|
637
|
+
|
|
593
638
|
def stream_camera(
|
|
594
639
|
self,
|
|
595
640
|
url: str,
|
|
@@ -209,6 +209,13 @@ class WebSocketTransport:
|
|
|
209
209
|
except queue.Full:
|
|
210
210
|
return False
|
|
211
211
|
|
|
212
|
+
def send_json_video_frame(self, msg: Dict[str, Any]) -> bool:
|
|
213
|
+
"""Send a JSON video_frame message. Used for frames that carry extra
|
|
214
|
+
metadata (e.g. thermal cameras) that the binary format cannot express."""
|
|
215
|
+
if not self._authenticated.is_set():
|
|
216
|
+
return False
|
|
217
|
+
return self._send_frame(msg)
|
|
218
|
+
|
|
212
219
|
# ------------------------------------------------------------------ thread
|
|
213
220
|
|
|
214
221
|
def _run(self) -> None:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "plexus-python"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.6.1"
|
|
8
8
|
description = "Thin Python SDK for Plexus — send telemetry in one line"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -31,7 +31,12 @@ dependencies = [
|
|
|
31
31
|
|
|
32
32
|
[project.optional-dependencies]
|
|
33
33
|
video = ["Pillow>=12.2.0"]
|
|
34
|
-
|
|
34
|
+
# Thermal cameras: colorization + Y16/USB capture. I2C sensors additionally
|
|
35
|
+
# need a board-specific driver, installed separately on the device, e.g.
|
|
36
|
+
# adafruit-circuitpython-mlx90640 (these require Raspberry Pi hardware libs
|
|
37
|
+
# and don't install on other platforms, so they're not listed here).
|
|
38
|
+
thermal = ["opencv-python>=4.8", "numpy>=1.24"]
|
|
39
|
+
dev = ["pytest>=9.0.3", "pytest-cov", "ruff", "websockets>=12", "opencv-python>=4.8", "numpy>=1.24"]
|
|
35
40
|
|
|
36
41
|
[project.scripts]
|
|
37
42
|
plexus = "plexus.cli:main"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Tests for thermal camera encoding and the send_thermal_frame wire message."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
# Thermal support requires opencv + numpy (the `thermal`/`dev` extras). Skip the
|
|
8
|
+
# whole module cleanly if they're not installed rather than erroring on import.
|
|
9
|
+
np = pytest.importorskip("numpy")
|
|
10
|
+
pytest.importorskip("cv2")
|
|
11
|
+
|
|
12
|
+
from plexus.cameras.thermal import ( # noqa: E402
|
|
13
|
+
SimulatedThermalCamera,
|
|
14
|
+
ThermalFrame,
|
|
15
|
+
_upscale_size,
|
|
16
|
+
build_thermal_frame,
|
|
17
|
+
encode_frame,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# _upscale_size — pure geometry, no encoding
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestUpscaleSize:
|
|
26
|
+
def test_large_sensor_unchanged(self):
|
|
27
|
+
assert _upscale_size(256, 192) == (256, 192)
|
|
28
|
+
|
|
29
|
+
def test_small_sensor_upscaled_to_min_side(self):
|
|
30
|
+
# MLX90640 32x24: shortest side (24) scaled up to 160.
|
|
31
|
+
dw, dh = _upscale_size(32, 24)
|
|
32
|
+
assert min(dw, dh) == 160
|
|
33
|
+
# Aspect ratio preserved (4:3).
|
|
34
|
+
assert dw == round(dh * 32 / 24)
|
|
35
|
+
|
|
36
|
+
def test_tiny_sensor_upscaled(self):
|
|
37
|
+
# MLX90641 16x12.
|
|
38
|
+
dw, dh = _upscale_size(16, 12)
|
|
39
|
+
assert min(dw, dh) == 160
|
|
40
|
+
|
|
41
|
+
def test_square_below_threshold(self):
|
|
42
|
+
assert _upscale_size(100, 100) == (160, 160)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# SimulatedThermalCamera
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class TestSimulatedCamera:
|
|
51
|
+
def test_frame_shape_and_dtype(self):
|
|
52
|
+
cam = SimulatedThermalCamera()
|
|
53
|
+
frame = cam.read_frame()
|
|
54
|
+
assert frame.shape == (24, 32) # (height, width)
|
|
55
|
+
assert frame.dtype == np.float32
|
|
56
|
+
|
|
57
|
+
def test_dimensions(self):
|
|
58
|
+
cam = SimulatedThermalCamera()
|
|
59
|
+
assert cam.width == 32
|
|
60
|
+
assert cam.height == 24
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# build_thermal_frame + ThermalFrame.to_message
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestBuildThermalFrame:
|
|
69
|
+
def test_small_sensor_includes_temps(self):
|
|
70
|
+
temps = np.arange(768, dtype=np.float32).reshape(24, 32)
|
|
71
|
+
frame = build_thermal_frame(temps)
|
|
72
|
+
assert isinstance(frame, ThermalFrame)
|
|
73
|
+
assert frame.sensor_width == 32
|
|
74
|
+
assert frame.sensor_height == 24
|
|
75
|
+
assert frame.temp_min == 0.0
|
|
76
|
+
assert frame.temp_max == 767.0
|
|
77
|
+
# 768 px <= 4096 threshold → temps retained.
|
|
78
|
+
assert frame.temps is not None
|
|
79
|
+
|
|
80
|
+
def test_large_sensor_drops_temps(self):
|
|
81
|
+
# 100x100 = 10_000 px > 4096 threshold → temps omitted.
|
|
82
|
+
temps = np.zeros((100, 100), dtype=np.float32)
|
|
83
|
+
frame = build_thermal_frame(temps)
|
|
84
|
+
assert frame.temps is None
|
|
85
|
+
|
|
86
|
+
def test_image_upscaled_for_small_sensor(self):
|
|
87
|
+
temps = np.zeros((24, 32), dtype=np.float32)
|
|
88
|
+
frame = build_thermal_frame(temps)
|
|
89
|
+
assert min(frame.width, frame.height) == 160
|
|
90
|
+
|
|
91
|
+
def test_timestamp_passthrough(self):
|
|
92
|
+
temps = np.zeros((24, 32), dtype=np.float32)
|
|
93
|
+
frame = build_thermal_frame(temps, timestamp_ms=1_234_567_890)
|
|
94
|
+
assert frame.timestamp_ms == 1_234_567_890
|
|
95
|
+
|
|
96
|
+
def test_uniform_frame_does_not_crash(self):
|
|
97
|
+
# Zero span (all pixels equal) must not divide-by-zero.
|
|
98
|
+
temps = np.full((24, 32), 25.0, dtype=np.float32)
|
|
99
|
+
frame = build_thermal_frame(temps)
|
|
100
|
+
assert frame.temp_min == 25.0
|
|
101
|
+
assert frame.temp_max == 25.0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class TestToMessage:
|
|
105
|
+
def _msg(self, **kwargs):
|
|
106
|
+
temps = np.arange(768, dtype=np.float32).reshape(24, 32)
|
|
107
|
+
frame = build_thermal_frame(temps, timestamp_ms=1000)
|
|
108
|
+
return frame, frame.to_message(**kwargs)
|
|
109
|
+
|
|
110
|
+
def test_message_shape(self):
|
|
111
|
+
_, msg = self._msg(camera_id="thermal", source_id="dev1")
|
|
112
|
+
assert msg["type"] == "video_frame"
|
|
113
|
+
assert msg["camera_id"] == "thermal"
|
|
114
|
+
assert msg["source_id"] == "dev1"
|
|
115
|
+
assert msg["video_type"] == "thermal"
|
|
116
|
+
assert msg["sensor_width"] == 32
|
|
117
|
+
assert msg["sensor_height"] == 24
|
|
118
|
+
assert msg["timestamp"] == 1000
|
|
119
|
+
assert isinstance(msg["frame"], str) and msg["frame"] # base64 JPEG
|
|
120
|
+
|
|
121
|
+
def test_temps_flattened_and_preserved(self):
|
|
122
|
+
_, msg = self._msg(camera_id="thermal")
|
|
123
|
+
assert "temps" in msg
|
|
124
|
+
assert len(msg["temps"]) == 768
|
|
125
|
+
assert msg["temps"] == list(range(768))
|
|
126
|
+
|
|
127
|
+
def test_source_id_omitted_when_none(self):
|
|
128
|
+
_, msg = self._msg(camera_id="thermal")
|
|
129
|
+
assert "source_id" not in msg
|
|
130
|
+
|
|
131
|
+
def test_display_dims_match_upscaled_image(self):
|
|
132
|
+
frame, msg = self._msg(camera_id="thermal")
|
|
133
|
+
assert msg["width"] == frame.width
|
|
134
|
+
assert msg["height"] == frame.height
|
|
135
|
+
assert min(msg["width"], msg["height"]) == 160
|
|
136
|
+
|
|
137
|
+
def test_large_sensor_message_has_no_temps(self):
|
|
138
|
+
temps = np.zeros((100, 100), dtype=np.float32)
|
|
139
|
+
msg = build_thermal_frame(temps).to_message(camera_id="t")
|
|
140
|
+
assert "temps" not in msg
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# encode_frame — camera → ThermalFrame
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_encode_frame_reads_camera():
|
|
149
|
+
cam = SimulatedThermalCamera()
|
|
150
|
+
frame = encode_frame(cam)
|
|
151
|
+
assert frame.sensor_width == 32
|
|
152
|
+
assert frame.sensor_height == 24
|
|
153
|
+
assert frame.temps is not None
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
version = 1
|
|
2
2
|
revision = 2
|
|
3
3
|
requires-python = ">=3.10"
|
|
4
|
+
resolution-markers = [
|
|
5
|
+
"python_full_version >= '3.11'",
|
|
6
|
+
"python_full_version < '3.11'",
|
|
7
|
+
]
|
|
4
8
|
|
|
5
9
|
[[package]]
|
|
6
10
|
name = "colorama"
|
|
@@ -134,7 +138,7 @@ name = "exceptiongroup"
|
|
|
134
138
|
version = "1.3.1"
|
|
135
139
|
source = { registry = "https://pypi.org/simple" }
|
|
136
140
|
dependencies = [
|
|
137
|
-
{ name = "typing-extensions", marker = "python_full_version < '3.
|
|
141
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
|
138
142
|
]
|
|
139
143
|
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
140
144
|
wheels = [
|
|
@@ -150,6 +154,172 @@ wheels = [
|
|
|
150
154
|
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
|
|
151
155
|
]
|
|
152
156
|
|
|
157
|
+
[[package]]
|
|
158
|
+
name = "numpy"
|
|
159
|
+
version = "2.2.6"
|
|
160
|
+
source = { registry = "https://pypi.org/simple" }
|
|
161
|
+
resolution-markers = [
|
|
162
|
+
"python_full_version < '3.11'",
|
|
163
|
+
]
|
|
164
|
+
sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" }
|
|
165
|
+
wheels = [
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" },
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" },
|
|
183
|
+
{ url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" },
|
|
184
|
+
{ url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" },
|
|
185
|
+
{ url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" },
|
|
186
|
+
{ url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" },
|
|
187
|
+
{ url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" },
|
|
188
|
+
{ url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" },
|
|
189
|
+
{ url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" },
|
|
190
|
+
{ url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" },
|
|
191
|
+
{ url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" },
|
|
192
|
+
{ url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" },
|
|
193
|
+
{ url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" },
|
|
194
|
+
{ url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" },
|
|
195
|
+
{ url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" },
|
|
196
|
+
{ url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" },
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" },
|
|
198
|
+
{ url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" },
|
|
199
|
+
{ url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" },
|
|
200
|
+
{ url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" },
|
|
201
|
+
{ url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" },
|
|
202
|
+
{ url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" },
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" },
|
|
204
|
+
{ url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" },
|
|
205
|
+
{ url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" },
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" },
|
|
207
|
+
{ url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" },
|
|
208
|
+
{ url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" },
|
|
209
|
+
{ url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" },
|
|
210
|
+
{ url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" },
|
|
211
|
+
{ url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" },
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" },
|
|
213
|
+
{ url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" },
|
|
214
|
+
{ url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" },
|
|
215
|
+
{ url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" },
|
|
216
|
+
{ url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" },
|
|
217
|
+
{ url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" },
|
|
218
|
+
{ url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" },
|
|
219
|
+
{ url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" },
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
[[package]]
|
|
223
|
+
name = "numpy"
|
|
224
|
+
version = "2.4.6"
|
|
225
|
+
source = { registry = "https://pypi.org/simple" }
|
|
226
|
+
resolution-markers = [
|
|
227
|
+
"python_full_version >= '3.11'",
|
|
228
|
+
]
|
|
229
|
+
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" }
|
|
230
|
+
wheels = [
|
|
231
|
+
{ url = "https://files.pythonhosted.org/packages/b3/49/ec46835a70be8fa6446c495126ac84fdb28cb2558e1620ffb87a10c8b64c/numpy-2.4.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", size = 16969194, upload-time = "2026-05-18T23:33:13.503Z" },
|
|
232
|
+
{ url = "https://files.pythonhosted.org/packages/0e/0d/f5957185c0ee2f3e12f78715aa9e3b353fd83633316c8532b38faa37e3f6/numpy-2.4.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", size = 14964111, upload-time = "2026-05-18T23:33:17.795Z" },
|
|
233
|
+
{ url = "https://files.pythonhosted.org/packages/ad/40/40a40ee0ddf7ceb782c49af278894b686e586d65d8c1889c8b5da01a3d7d/numpy-2.4.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", size = 5469159, upload-time = "2026-05-18T23:33:20.654Z" },
|
|
234
|
+
{ url = "https://files.pythonhosted.org/packages/63/13/f9a8046535cb21deae82f8d03de9617e08882d274fad2539630761888228/numpy-2.4.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", size = 6798936, upload-time = "2026-05-18T23:33:22.987Z" },
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/33/a8/6fa8c1a345a8c85dbb21932c447bee07c30a2c2a3f31e369c0a84b300147/numpy-2.4.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", size = 15966692, upload-time = "2026-05-18T23:33:26.62Z" },
|
|
236
|
+
{ url = "https://files.pythonhosted.org/packages/02/03/74fe2a4cb3817d94d86402f2506554130a2f01414e299b5a843e5a8a957f/numpy-2.4.6-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", size = 16918164, upload-time = "2026-05-18T23:33:29.955Z" },
|
|
237
|
+
{ url = "https://files.pythonhosted.org/packages/c5/80/3615be3313f7e7696609bc194b9f0101da809df79e859bdb84e0cd043f46/numpy-2.4.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", size = 17322877, upload-time = "2026-05-18T23:33:34.724Z" },
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/ca/ac/a691e0fe2675e370d0e08ff905adc49a1c8830e8cae03efe4477e92cd55d/numpy-2.4.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6", size = 18651487, upload-time = "2026-05-18T23:33:38.217Z" },
|
|
239
|
+
{ url = "https://files.pythonhosted.org/packages/15/a7/9bc1cd626d7bf6869bfedf27b91b6ab5dd607758bf8e959d6fa80c6a59cb/numpy-2.4.6-cp311-cp311-win32.whl", hash = "sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8", size = 6233945, upload-time = "2026-05-18T23:33:41.331Z" },
|
|
240
|
+
{ url = "https://files.pythonhosted.org/packages/c5/31/7fc6239c12bce7e931463251cca4426c465e1876ba3cc785402ef4dd8f4e/numpy-2.4.6-cp311-cp311-win_amd64.whl", hash = "sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", size = 12608406, upload-time = "2026-05-18T23:33:44.131Z" },
|
|
241
|
+
{ url = "https://files.pythonhosted.org/packages/27/83/140f85a466595a16382996a1bf06b2b54bcd597488921b0c9daaeeda72af/numpy-2.4.6-cp311-cp311-win_arm64.whl", hash = "sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577", size = 10479528, upload-time = "2026-05-18T23:33:50.725Z" },
|
|
242
|
+
{ url = "https://files.pythonhosted.org/packages/95/2a/3d7b5ac8aac24feaf9ad7ed58f45b0bbc06d37e4338ae84c9f2298b570f9/numpy-2.4.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", size = 16689119, upload-time = "2026-05-18T23:33:54.065Z" },
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/ea/12/92c4c131527599e8288d6918e888d88726f84d805d784b771f32408aeaef/numpy-2.4.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb", size = 14699246, upload-time = "2026-05-18T23:33:57.621Z" },
|
|
244
|
+
{ url = "https://files.pythonhosted.org/packages/ad/fe/c0a6b7b2ca128a8fb228575147073b660656734b8ebe4d76c8fd748dcc79/numpy-2.4.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", size = 5204410, upload-time = "2026-05-18T23:34:00.302Z" },
|
|
245
|
+
{ url = "https://files.pythonhosted.org/packages/f3/d4/9770d14ba719432bb90a421bfd443872ed0f70f7264b64bec12ea363d5fd/numpy-2.4.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", size = 6551240, upload-time = "2026-05-18T23:34:02.852Z" },
|
|
246
|
+
{ url = "https://files.pythonhosted.org/packages/c9/c6/50a46a6205feba2343f1d6d17438107c5dc491ed1c736e6ea68689fd906b/numpy-2.4.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", size = 15671012, upload-time = "2026-05-18T23:34:05.485Z" },
|
|
247
|
+
{ url = "https://files.pythonhosted.org/packages/99/60/14115e6364fa676c5397c2ad3004e527e9aa487abf5d0706ec81bbd08529/numpy-2.4.6-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", size = 16645538, upload-time = "2026-05-18T23:34:09.265Z" },
|
|
248
|
+
{ url = "https://files.pythonhosted.org/packages/ae/c5/693cbe59e57db94d2231fa519ca3978dc9e19da5a8f088588f5c6e947ff2/numpy-2.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", size = 17020706, upload-time = "2026-05-18T23:34:13.053Z" },
|
|
249
|
+
{ url = "https://files.pythonhosted.org/packages/ef/fc/85b7c4eff9b4966ade25c2273cf7e7012e92366c032058653934b37de044/numpy-2.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2", size = 18368541, upload-time = "2026-05-18T23:34:17.024Z" },
|
|
250
|
+
{ url = "https://files.pythonhosted.org/packages/f6/81/e1b27545deedce7f4a0b348618c6b62d74e36a4dc9ccd42f3eb2f85eee32/numpy-2.4.6-cp312-cp312-win32.whl", hash = "sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45", size = 5962825, upload-time = "2026-05-18T23:34:20.3Z" },
|
|
251
|
+
{ url = "https://files.pythonhosted.org/packages/ab/ca/feab00bd44aa5fe1ad2c18f08b4d3bb92e26484b0b1d1443897809ed528c/numpy-2.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751", size = 12321687, upload-time = "2026-05-18T23:34:23.095Z" },
|
|
252
|
+
{ url = "https://files.pythonhosted.org/packages/63/cf/5a6d34850a39d1093558564f77ee8e8e0bee5061151b8f05a55711001ec7/numpy-2.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", size = 10221482, upload-time = "2026-05-18T23:34:25.876Z" },
|
|
253
|
+
{ 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" },
|
|
254
|
+
{ 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" },
|
|
255
|
+
{ 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" },
|
|
256
|
+
{ 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" },
|
|
257
|
+
{ 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" },
|
|
258
|
+
{ 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" },
|
|
259
|
+
{ 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" },
|
|
260
|
+
{ 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" },
|
|
261
|
+
{ 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" },
|
|
262
|
+
{ 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" },
|
|
263
|
+
{ 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" },
|
|
264
|
+
{ 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" },
|
|
265
|
+
{ 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" },
|
|
266
|
+
{ 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" },
|
|
267
|
+
{ 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" },
|
|
268
|
+
{ 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" },
|
|
269
|
+
{ 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" },
|
|
270
|
+
{ 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" },
|
|
271
|
+
{ 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" },
|
|
272
|
+
{ 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" },
|
|
273
|
+
{ 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" },
|
|
274
|
+
{ 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" },
|
|
275
|
+
{ 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" },
|
|
276
|
+
{ 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" },
|
|
277
|
+
{ 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" },
|
|
278
|
+
{ 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" },
|
|
279
|
+
{ 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" },
|
|
280
|
+
{ 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" },
|
|
281
|
+
{ 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" },
|
|
282
|
+
{ 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" },
|
|
283
|
+
{ 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" },
|
|
284
|
+
{ 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" },
|
|
285
|
+
{ 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" },
|
|
286
|
+
{ 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" },
|
|
287
|
+
{ 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" },
|
|
288
|
+
{ 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" },
|
|
289
|
+
{ 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" },
|
|
290
|
+
{ 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" },
|
|
291
|
+
{ 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" },
|
|
292
|
+
{ 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" },
|
|
293
|
+
{ 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" },
|
|
294
|
+
{ 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" },
|
|
295
|
+
{ url = "https://files.pythonhosted.org/packages/de/12/b422cc84439adc0d00de605bf4a308890ae5c26f2c71fbd73e5d08fbb0dd/numpy-2.4.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", size = 16847511, upload-time = "2026-05-18T23:36:50.673Z" },
|
|
296
|
+
{ url = "https://files.pythonhosted.org/packages/44/53/f481bef68011740f8849418d82db07230e825013f31f4eef5ba5b805316a/numpy-2.4.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7", size = 14889064, upload-time = "2026-05-18T23:36:53.879Z" },
|
|
297
|
+
{ url = "https://files.pythonhosted.org/packages/7f/57/42ed575c10ced8af951d426bc4e1f8aff16fd851db33f067036215a7f860/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", size = 5394157, upload-time = "2026-05-18T23:36:57.194Z" },
|
|
298
|
+
{ url = "https://files.pythonhosted.org/packages/6a/ef/f66cc724fcc36c1e364c67f51ae9146090b8b584f27d58b97fdae3edd737/numpy-2.4.6-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", size = 6708728, upload-time = "2026-05-18T23:36:59.575Z" },
|
|
299
|
+
{ url = "https://files.pythonhosted.org/packages/1a/9c/c531f2293b91265d8b48e9b329f54fdd7ffae73cb4134ea10cca4237e9cc/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", size = 15798374, upload-time = "2026-05-18T23:37:02.674Z" },
|
|
300
|
+
{ url = "https://files.pythonhosted.org/packages/1a/b0/413077f6b1153ed3cba361401c6783bbad6114804a000cc22eb71c13e190/numpy-2.4.6-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", size = 16747286, upload-time = "2026-05-18T23:37:06.327Z" },
|
|
301
|
+
{ url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" },
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
[[package]]
|
|
305
|
+
name = "opencv-python"
|
|
306
|
+
version = "4.13.0.92"
|
|
307
|
+
source = { registry = "https://pypi.org/simple" }
|
|
308
|
+
dependencies = [
|
|
309
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
|
310
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
|
311
|
+
]
|
|
312
|
+
wheels = [
|
|
313
|
+
{ url = "https://files.pythonhosted.org/packages/fc/6f/5a28fef4c4a382be06afe3938c64cc168223016fa520c5abaf37e8862aa5/opencv_python-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:caf60c071ec391ba51ed00a4a920f996d0b64e3e46068aac1f646b5de0326a19", size = 46247052, upload-time = "2026-02-05T07:01:25.046Z" },
|
|
314
|
+
{ url = "https://files.pythonhosted.org/packages/08/ac/6c98c44c650b8114a0fb901691351cfb3956d502e8e9b5cd27f4ee7fbf2f/opencv_python-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:5868a8c028a0b37561579bfb8ac1875babdc69546d236249fff296a8c010ccf9", size = 32568781, upload-time = "2026-02-05T07:01:41.379Z" },
|
|
315
|
+
{ url = "https://files.pythonhosted.org/packages/3e/51/82fed528b45173bf629fa44effb76dff8bc9f4eeaee759038362dfa60237/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bc2596e68f972ca452d80f444bc404e08807d021fbba40df26b61b18e01838a", size = 47685527, upload-time = "2026-02-05T06:59:11.24Z" },
|
|
316
|
+
{ url = "https://files.pythonhosted.org/packages/db/07/90b34a8e2cf9c50fe8ed25cac9011cde0676b4d9d9c973751ac7616223a2/opencv_python-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:402033cddf9d294693094de5ef532339f14ce821da3ad7df7c9f6e8316da32cf", size = 70460872, upload-time = "2026-02-05T06:59:19.162Z" },
|
|
317
|
+
{ url = "https://files.pythonhosted.org/packages/02/6d/7a9cc719b3eaf4377b9c2e3edeb7ed3a81de41f96421510c0a169ca3cfd4/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:bccaabf9eb7f897ca61880ce2869dcd9b25b72129c28478e7f2a5e8dee945616", size = 46708208, upload-time = "2026-02-05T06:59:15.419Z" },
|
|
318
|
+
{ url = "https://files.pythonhosted.org/packages/fd/55/b3b49a1b97aabcfbbd6c7326df9cb0b6fa0c0aefa8e89d500939e04aa229/opencv_python-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:620d602b8f7d8b8dab5f4b99c6eb353e78d3fb8b0f53db1bd258bb1aa001c1d5", size = 72927042, upload-time = "2026-02-05T06:59:23.389Z" },
|
|
319
|
+
{ url = "https://files.pythonhosted.org/packages/fb/17/de5458312bcb07ddf434d7bfcb24bb52c59635ad58c6e7c751b48949b009/opencv_python-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:372fe164a3148ac1ca51e5f3ad0541a4a276452273f503441d718fab9c5e5f59", size = 30932638, upload-time = "2026-02-05T07:02:14.98Z" },
|
|
320
|
+
{ url = "https://files.pythonhosted.org/packages/e9/a5/1be1516390333ff9be3a9cb648c9f33df79d5096e5884b5df71a588af463/opencv_python-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:423d934c9fafb91aad38edf26efb46da91ffbc05f3f59c4b0c72e699720706f5", size = 40212062, upload-time = "2026-02-05T07:02:12.724Z" },
|
|
321
|
+
]
|
|
322
|
+
|
|
153
323
|
[[package]]
|
|
154
324
|
name = "packaging"
|
|
155
325
|
version = "26.2"
|
|
@@ -259,7 +429,7 @@ wheels = [
|
|
|
259
429
|
|
|
260
430
|
[[package]]
|
|
261
431
|
name = "plexus-python"
|
|
262
|
-
version = "0.
|
|
432
|
+
version = "0.5.2"
|
|
263
433
|
source = { editable = "." }
|
|
264
434
|
dependencies = [
|
|
265
435
|
{ name = "websocket-client" },
|
|
@@ -267,17 +437,29 @@ dependencies = [
|
|
|
267
437
|
|
|
268
438
|
[package.optional-dependencies]
|
|
269
439
|
dev = [
|
|
440
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
|
441
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
|
442
|
+
{ name = "opencv-python" },
|
|
270
443
|
{ name = "pytest" },
|
|
271
444
|
{ name = "pytest-cov" },
|
|
272
445
|
{ name = "ruff" },
|
|
273
446
|
{ name = "websockets" },
|
|
274
447
|
]
|
|
448
|
+
thermal = [
|
|
449
|
+
{ name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
|
450
|
+
{ name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
|
451
|
+
{ name = "opencv-python" },
|
|
452
|
+
]
|
|
275
453
|
video = [
|
|
276
454
|
{ name = "pillow" },
|
|
277
455
|
]
|
|
278
456
|
|
|
279
457
|
[package.metadata]
|
|
280
458
|
requires-dist = [
|
|
459
|
+
{ name = "numpy", marker = "extra == 'dev'", specifier = ">=1.24" },
|
|
460
|
+
{ name = "numpy", marker = "extra == 'thermal'", specifier = ">=1.24" },
|
|
461
|
+
{ name = "opencv-python", marker = "extra == 'dev'", specifier = ">=4.8" },
|
|
462
|
+
{ name = "opencv-python", marker = "extra == 'thermal'", specifier = ">=4.8" },
|
|
281
463
|
{ name = "pillow", marker = "extra == 'video'", specifier = ">=12.2.0" },
|
|
282
464
|
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.3" },
|
|
283
465
|
{ name = "pytest-cov", marker = "extra == 'dev'" },
|
|
@@ -285,7 +467,7 @@ requires-dist = [
|
|
|
285
467
|
{ name = "websocket-client", specifier = ">=1.7" },
|
|
286
468
|
{ name = "websockets", marker = "extra == 'dev'", specifier = ">=12" },
|
|
287
469
|
]
|
|
288
|
-
provides-extras = ["video", "dev"]
|
|
470
|
+
provides-extras = ["video", "thermal", "dev"]
|
|
289
471
|
|
|
290
472
|
[[package]]
|
|
291
473
|
name = "pluggy"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|