From 6804a37636e8fb4fe39330b4258739dddf0ccde9 Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 19:05:04 +0900 Subject: [PATCH 1/7] scaffold robot vision platform --- .github/workflows/robot-vision-ci.yml | 36 ++++++ .../workflows/robot-vision-release-device.yml | 25 ++++ RobotVisionPlatform/.gitignore | 11 ++ RobotVisionPlatform/README.md | 108 ++++++++++++++++++ .../deploy/compose/docker-compose.yml | 10 ++ .../systemd/robot-vision-device.service | 24 ++++ RobotVisionPlatform/device/CMakeLists.txt | 44 +++++++ RobotVisionPlatform/device/README.md | 20 ++++ .../device/config/device.example.toml | 30 +++++ .../device/include/rv/bounded_queue.hpp | 61 ++++++++++ .../device/include/rv/interfaces.hpp | 41 +++++++ .../device/include/rv/pipeline.hpp | 50 ++++++++ .../device/include/rv/types.hpp | 45 ++++++++ .../device/src/http_event_sink.cpp | 84 ++++++++++++++ RobotVisionPlatform/device/src/main.cpp | 54 +++++++++ RobotVisionPlatform/device/src/pipeline.cpp | 70 ++++++++++++ .../device/src/synthetic_adapters.cpp | 69 +++++++++++ .../device/tests/core_tests.cpp | 28 +++++ RobotVisionPlatform/docs/README.md | 14 +++ .../docs/adr/0001-platform-architecture.md | 13 +++ RobotVisionPlatform/docs/architecture.md | 45 ++++++++ .../docs/initial-release-guide.md | 63 ++++++++++ RobotVisionPlatform/docs/jetson-deployment.md | 49 ++++++++ RobotVisionPlatform/docs/model-lifecycle.md | 30 +++++ .../docs/security-and-operations.md | 13 +++ .../docs/streaming-and-protocols.md | 24 ++++ RobotVisionPlatform/scripts/build.ps1 | 14 +++ RobotVisionPlatform/scripts/build.sh | 7 ++ RobotVisionPlatform/scripts/test.ps1 | 10 ++ RobotVisionPlatform/scripts/test.sh | 6 + .../server/Directory.Build.props | 10 ++ RobotVisionPlatform/server/README.md | 26 +++++ .../server/RobotVision.Server.slnx | 6 + .../src/RobotVision.Dashboard.Maui/App.xaml | 7 ++ .../RobotVision.Dashboard.Maui/App.xaml.cs | 11 ++ .../src/RobotVision.Dashboard.Maui/Main.razor | 4 + .../RobotVision.Dashboard.Maui/MainPage.xaml | 12 ++ .../MainPage.xaml.cs | 3 + .../RobotVision.Dashboard.Maui/MauiProgram.cs | 18 +++ .../RobotVision.Dashboard.Maui.csproj | 17 +++ .../RobotVision.Dashboard.Maui/_Imports.razor | 2 + .../wwwroot/index.html | 3 + .../DetectionFanoutWorker.cs | 29 +++++ .../RobotVision.Server.Api/DeviceRegistry.cs | 28 +++++ .../src/RobotVision.Server.Api/Dockerfile | 16 +++ .../RobotVision.Server.Api/MonitoringHub.cs | 15 +++ .../src/RobotVision.Server.Api/Program.cs | 49 ++++++++ .../RobotVision.Server.Api.csproj | 6 + .../RobotVision.Server.Api/appsettings.json | 18 +++ .../RobotVision.Server.Api/wwwroot/index.html | 38 ++++++ .../DeviceContracts.cs | 29 +++++ .../RobotVision.Server.Contracts.csproj | 2 + .../tests/RobotVision.Server.Tests/Program.cs | 10 ++ .../RobotVision.Server.Tests.csproj | 8 ++ .../shared/proto/vision/v1/device.proto | 71 ++++++++++++ RobotVisionPlatform/tips/README.md | 13 +++ .../tips/data/dataset-engineering.md | 11 ++ .../tips/deployment/jetson-optimization.md | 12 ++ .../tips/evaluation/edge-evaluation.md | 6 + .../fundamentals/vision-training-basics.md | 10 ++ .../tips/resources/official-resources.md | 17 +++ .../tips/training/fine-tuning-playbook.md | 13 +++ 62 files changed, 1618 insertions(+) create mode 100644 .github/workflows/robot-vision-ci.yml create mode 100644 .github/workflows/robot-vision-release-device.yml create mode 100644 RobotVisionPlatform/.gitignore create mode 100644 RobotVisionPlatform/README.md create mode 100644 RobotVisionPlatform/deploy/compose/docker-compose.yml create mode 100644 RobotVisionPlatform/deploy/systemd/robot-vision-device.service create mode 100644 RobotVisionPlatform/device/CMakeLists.txt create mode 100644 RobotVisionPlatform/device/README.md create mode 100644 RobotVisionPlatform/device/config/device.example.toml create mode 100644 RobotVisionPlatform/device/include/rv/bounded_queue.hpp create mode 100644 RobotVisionPlatform/device/include/rv/interfaces.hpp create mode 100644 RobotVisionPlatform/device/include/rv/pipeline.hpp create mode 100644 RobotVisionPlatform/device/include/rv/types.hpp create mode 100644 RobotVisionPlatform/device/src/http_event_sink.cpp create mode 100644 RobotVisionPlatform/device/src/main.cpp create mode 100644 RobotVisionPlatform/device/src/pipeline.cpp create mode 100644 RobotVisionPlatform/device/src/synthetic_adapters.cpp create mode 100644 RobotVisionPlatform/device/tests/core_tests.cpp create mode 100644 RobotVisionPlatform/docs/README.md create mode 100644 RobotVisionPlatform/docs/adr/0001-platform-architecture.md create mode 100644 RobotVisionPlatform/docs/architecture.md create mode 100644 RobotVisionPlatform/docs/initial-release-guide.md create mode 100644 RobotVisionPlatform/docs/jetson-deployment.md create mode 100644 RobotVisionPlatform/docs/model-lifecycle.md create mode 100644 RobotVisionPlatform/docs/security-and-operations.md create mode 100644 RobotVisionPlatform/docs/streaming-and-protocols.md create mode 100644 RobotVisionPlatform/scripts/build.ps1 create mode 100644 RobotVisionPlatform/scripts/build.sh create mode 100644 RobotVisionPlatform/scripts/test.ps1 create mode 100644 RobotVisionPlatform/scripts/test.sh create mode 100644 RobotVisionPlatform/server/Directory.Build.props create mode 100644 RobotVisionPlatform/server/README.md create mode 100644 RobotVisionPlatform/server/RobotVision.Server.slnx create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/RobotVision.Dashboard.Maui.csproj create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/_Imports.razor create mode 100644 RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/wwwroot/index.html create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/Dockerfile create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/appsettings.json create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs create mode 100644 RobotVisionPlatform/server/src/RobotVision.Server.Contracts/RobotVision.Server.Contracts.csproj create mode 100644 RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs create mode 100644 RobotVisionPlatform/server/tests/RobotVision.Server.Tests/RobotVision.Server.Tests.csproj create mode 100644 RobotVisionPlatform/shared/proto/vision/v1/device.proto create mode 100644 RobotVisionPlatform/tips/README.md create mode 100644 RobotVisionPlatform/tips/data/dataset-engineering.md create mode 100644 RobotVisionPlatform/tips/deployment/jetson-optimization.md create mode 100644 RobotVisionPlatform/tips/evaluation/edge-evaluation.md create mode 100644 RobotVisionPlatform/tips/fundamentals/vision-training-basics.md create mode 100644 RobotVisionPlatform/tips/resources/official-resources.md create mode 100644 RobotVisionPlatform/tips/training/fine-tuning-playbook.md diff --git a/.github/workflows/robot-vision-ci.yml b/.github/workflows/robot-vision-ci.yml new file mode 100644 index 0000000..0a0f927 --- /dev/null +++ b/.github/workflows/robot-vision-ci.yml @@ -0,0 +1,36 @@ +name: robot-vision-ci + +on: + push: + paths: ["RobotVisionPlatform/**", ".github/workflows/robot-vision-*.yml"] + pull_request: + paths: ["RobotVisionPlatform/**", ".github/workflows/robot-vision-*.yml"] + +jobs: + device: + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: RobotVisionPlatform + steps: + - uses: actions/checkout@v4 + - name: Configure + run: cmake -S device -B device/build -G Ninja -DCMAKE_BUILD_TYPE=Release -DRV_ENABLE_BOOST_HTTP=OFF + - name: Build + run: cmake --build device/build --parallel + - name: Test + run: ctest --test-dir device/build --output-on-failure + + server: + runs-on: ubuntu-24.04 + defaults: + run: + working-directory: RobotVisionPlatform + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - run: dotnet build server/RobotVision.Server.slnx --configuration Release + - run: dotnet run --project server/tests/RobotVision.Server.Tests --configuration Release + diff --git a/.github/workflows/robot-vision-release-device.yml b/.github/workflows/robot-vision-release-device.yml new file mode 100644 index 0000000..6568fd5 --- /dev/null +++ b/.github/workflows/robot-vision-release-device.yml @@ -0,0 +1,25 @@ +name: robot-vision-release-device + +on: + workflow_dispatch: + push: + tags: ["robot-vision-v*"] + +jobs: + jetson-arm64: + # Native Jetson build avoids shipping an incompatible TensorRT engine/runtime. + runs-on: [self-hosted, linux, arm64, jetson] + defaults: + run: + working-directory: RobotVisionPlatform + steps: + - uses: actions/checkout@v4 + - run: cmake -S device -B device/build -G Ninja -DCMAKE_BUILD_TYPE=Release + - run: cmake --build device/build --parallel + - run: ctest --test-dir device/build --output-on-failure + - run: cmake --install device/build --prefix device/stage + - uses: actions/upload-artifact@v4 + with: + name: robot-vision-device-linux-arm64 + path: RobotVisionPlatform/device/stage + diff --git a/RobotVisionPlatform/.gitignore b/RobotVisionPlatform/.gitignore new file mode 100644 index 0000000..b81ade5 --- /dev/null +++ b/RobotVisionPlatform/.gitignore @@ -0,0 +1,11 @@ +device/build*/ +device/stage/ +**/bin/ +**/obj/ +.vs/ +.idea/ +*.user +*.log +.env +secrets/ +models/*.engine diff --git a/RobotVisionPlatform/README.md b/RobotVisionPlatform/README.md new file mode 100644 index 0000000..e4dc4c1 --- /dev/null +++ b/RobotVisionPlatform/README.md @@ -0,0 +1,108 @@ +# Robot Vision Platform + +Jetson Orin Nano Super에서 비전 추론을 수행하고, 서버에서 장치·영상·이벤트를 관제하며, 모델을 학습·배포·롤백하는 단일 저장소(monorepo)입니다. + +> 현재 단계는 **MVP 기반 골격**입니다. 합성 카메라/탐지기로 전체 파이프라인과 서버 수집을 먼저 검증하고, 실제 CSI/USB 카메라·TensorRT·WebRTC 구현을 어댑터로 교체합니다. + +## 목표 아키텍처 + +```text +Camera -> bounded queue -> TensorRT/DeepStream -> overlay/encoder -> WebRTC (video) + | | + +-> gRPC events/status --+--> ASP.NET Core -> SignalR -> MAUI/Web UI + ^ | + +------ commands --------+ + +Training -> ONNX -> TensorRT engine build on target -> signed model registry -> staged OTA/rollback +``` + +영상과 제어 경로를 분리합니다. H.264/H.265 하드웨어 인코딩 영상은 WebRTC(저지연) 또는 RTSP/SRT(불안정한 WAN)로 보내고, 작은 구조화 데이터와 명령은 protobuf/gRPC로 보냅니다. 서버는 ASP.NET Core, 관제 앱은 UI 공유가 쉬운 .NET MAUI Blazor Hybrid를 기준으로 합니다. + +## 저장소 구조 + +```text +RobotVisionPlatform/ +├── docs/ # 통합 설계·운영·초기 릴리스 가이드 +├── tips/ # 학습 기초와 실전 가이드 +├── device/ # Jetson에서 실행되는 C++20 프로젝트 +├── server/ # ASP.NET Core 수집 서버와 MAUI 관제 앱 +├── shared/proto/ # 장치/서버 공용 protobuf 계약 +├── deploy/ # Compose, systemd, 배포 설정 +├── scripts/ # 개발·검증 스크립트 +└── ../.github/workflows/ # GitHub가 인식하는 저장소 루트 CI/릴리스 자동화 +``` + +## 단계별 TODO + +### Phase 0 — 기반과 계약 + +- [x] 모노레포 폴더와 문서 체계 생성 +- [x] 장치 등록·상태·탐지 이벤트용 protobuf 초안 정의 +- [x] C++20 파이프라인 코어와 서버 수집 API 골격 생성 +- [x] 기본 CI, Docker Compose, systemd 배포 파일 생성 +- [ ] 장치 PKI(mTLS), 인증서 발급/회전 정책 확정 +- [ ] 카메라별 해상도·FPS·지연·보존 기간 SLO 확정 + +### Phase 1 — 단일 장치 MVP + +- [ ] JetPack/DeepStream 호환표를 실제 장치 이미지에 고정 +- [ ] CSI 또는 USB 카메라 GStreamer source 어댑터 구현 +- [ ] 기준 모델을 ONNX로 내보내고 장치에서 TensorRT engine 생성 +- [ ] 실제 탐지 메타데이터를 gRPC `Connect` 스트림으로 전송 +- [ ] NVENC H.264 + WebRTC 송출 및 서버 signaling 구현 +- [ ] 오프라인 store-and-forward와 재접속 검증 + +### Phase 2 — 관제와 운영 + +- [ ] MAUI 대시보드에 WebRTC 플레이어와 탐지 오버레이 연결 +- [ ] PostgreSQL/TimescaleDB 이벤트 저장소와 S3 호환 클립 저장소 연결 +- [ ] OpenTelemetry metrics/logs/traces 및 장치 health alert 추가 +- [ ] 원격 설정, 재시작, 로그 묶음 수집 명령 추가 +- [ ] 네트워크 손실·재부팅·전원 차단 chaos test 자동화 + +### Phase 3 — 모델 수명주기 + +- [ ] 데이터셋 버전 관리(DVC 또는 lakeFS)와 라벨 검수 흐름 확정 +- [ ] 학습/평가 파이프라인과 모델 승인 게이트 구축 +- [ ] 모델 manifest, SHA-256, 서명, 호환성 검증 추가 +- [ ] canary → cohort → fleet 단계 배포와 자동 롤백 구현 +- [ ] drift/오탐/미탐 샘플링 및 active-learning 루프 구현 + +### Phase 4 — VLM/VLA 확장 + +- [ ] 이벤트 기반 keyframe/clip 추출(상시 VLM 실행 금지) +- [ ] 서버 GPU의 VLM batch 분석과 구조화 결과 스키마 정의 +- [ ] VLA 명령은 정책 엔진·허용 목록·human-in-the-loop를 통과하도록 구현 +- [ ] 안전 정지 및 명령 감사 로그 검증 + +## 빠른 시작 + +필수 도구는 CMake 3.22+, C++20 컴파일러, .NET 10 SDK, Docker입니다. + +```powershell +./scripts/build.ps1 +./scripts/test.ps1 +dotnet run --project server/src/RobotVision.Server.Api +``` + +서버 실행 후 `http://localhost:5080`, health check는 `/health`, 장치 목록은 `/api/devices`입니다. 장치 데모는 별도 터미널에서 `device/build/robot_vision_device --device-id jetson-dev-001`로 실행합니다. Boost HTTP adapter를 빌드했다면 `--server-host 127.0.0.1 --server-port 5080`을 더해 MVP end-to-end 전송을 확인할 수 있습니다. + +## 설계 문서 + +- [시스템 아키텍처](docs/architecture.md) +- [초기 릴리스 가이드](docs/initial-release-guide.md) +- [Jetson 준비 및 배포](docs/jetson-deployment.md) +- [통신과 영상 전송](docs/streaming-and-protocols.md) +- [모델 수명주기](docs/model-lifecycle.md) +- [보안 및 운영](docs/security-and-operations.md) +- [학습 팁 인덱스](tips/README.md) + +## 기술 기준 + +- Edge: C++20, CMake, `std::jthread`/`std::stop_token`, 선택형 Boost, GStreamer/DeepStream/TensorRT +- Control plane: protobuf + gRPC bidirectional streaming, TLS/mTLS +- Media plane: WebRTC + H.264 우선, RTSP/SRT 대안, keyframe JPEG는 디버그용 +- Server: .NET 10, ASP.NET Core, SignalR, MAUI Blazor Hybrid +- Observability: OpenTelemetry, Prometheus 호환 metrics, 구조화 로그 + +버전 숫자는 예시보다 실제 JetPack–DeepStream 호환표를 우선합니다. 자세한 결정 근거와 공식 링크는 각 문서에 있습니다. diff --git a/RobotVisionPlatform/deploy/compose/docker-compose.yml b/RobotVisionPlatform/deploy/compose/docker-compose.yml new file mode 100644 index 0000000..40ce015 --- /dev/null +++ b/RobotVisionPlatform/deploy/compose/docker-compose.yml @@ -0,0 +1,10 @@ +services: + api: + build: + context: ../.. + dockerfile: server/src/RobotVision.Server.Api/Dockerfile + ports: + - "5080:5080" + environment: + ASPNETCORE_ENVIRONMENT: Production + restart: unless-stopped diff --git a/RobotVisionPlatform/deploy/systemd/robot-vision-device.service b/RobotVisionPlatform/deploy/systemd/robot-vision-device.service new file mode 100644 index 0000000..35a3a6b --- /dev/null +++ b/RobotVisionPlatform/deploy/systemd/robot-vision-device.service @@ -0,0 +1,24 @@ +[Unit] +Description=Robot Vision Edge Agent +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=robotvision +Group=robotvision +SupplementaryGroups=video render +ExecStart=/opt/robot-vision/bin/robot_vision_device --device-id jetson-dev-001 +Restart=on-failure +RestartSec=3 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +StateDirectory=robot-vision +LogsDirectory=robot-vision +DeviceAllow=/dev/video0 rw +EnvironmentFile=-/etc/robot-vision/environment + +[Install] +WantedBy=multi-user.target diff --git a/RobotVisionPlatform/device/CMakeLists.txt b/RobotVisionPlatform/device/CMakeLists.txt new file mode 100644 index 0000000..c0249ef --- /dev/null +++ b/RobotVisionPlatform/device/CMakeLists.txt @@ -0,0 +1,44 @@ +cmake_minimum_required(VERSION 3.22) +project(robot_vision_device VERSION 0.1.0 LANGUAGES CXX) + +option(RV_BUILD_TESTS "Build device unit tests" ON) +option(RV_ENABLE_BOOST_HTTP "Enable Boost.Beast HTTP event sink when Boost is available" ON) + +add_library(rv_core + src/pipeline.cpp + src/synthetic_adapters.cpp +) +target_include_directories(rv_core PUBLIC include) +target_compile_features(rv_core PUBLIC cxx_std_20) +set_target_properties(rv_core PROPERTIES CXX_EXTENSIONS OFF) + +if(MSVC) + target_compile_options(rv_core PRIVATE /W4 /permissive-) +else() + target_compile_options(rv_core PRIVATE -Wall -Wextra -Wpedantic) +endif() + +if(RV_ENABLE_BOOST_HTTP) + find_package(Boost 1.74 QUIET COMPONENTS system) + if(Boost_FOUND) + target_sources(rv_core PRIVATE src/http_event_sink.cpp) + target_link_libraries(rv_core PUBLIC Boost::system) + target_compile_definitions(rv_core PUBLIC RV_HAS_BOOST_HTTP=1) + else() + message(STATUS "Boost.System not found; HTTP sink disabled") + endif() +endif() + +add_executable(robot_vision_device src/main.cpp) +target_link_libraries(robot_vision_device PRIVATE rv_core) + +include(GNUInstallDirs) +install(TARGETS robot_vision_device RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES config/device.example.toml DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/robot-vision) + +if(RV_BUILD_TESTS) + enable_testing() + add_executable(rv_core_tests tests/core_tests.cpp) + target_link_libraries(rv_core_tests PRIVATE rv_core) + add_test(NAME rv_core_tests COMMAND rv_core_tests) +endif() diff --git a/RobotVisionPlatform/device/README.md b/RobotVisionPlatform/device/README.md new file mode 100644 index 0000000..c0bdee3 --- /dev/null +++ b/RobotVisionPlatform/device/README.md @@ -0,0 +1,20 @@ +# Device Agent + +C++20 기반 edge 파이프라인입니다. 기본 빌드는 외부 SDK 없이 합성 입력으로 실행됩니다. + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build --parallel +ctest --test-dir build --output-on-failure +./build/robot_vision_device --device-id jetson-dev-001 +``` + +Boost.System을 포함해 빌드했다면 실행 중인 MVP 서버로 바로 전송할 수 있습니다. + +```bash +./build/robot_vision_device --device-id jetson-dev-001 --server-host 127.0.0.1 --server-port 5080 +``` + +실제 장치 구현 순서는 `ICamera`의 GStreamer/NvArgus adapter, `IDetector`의 TensorRT/DeepStream adapter, `IEventSink`의 gRPC/mTLS adapter, `IVideoPublisher`의 WebRTC adapter입니다. 코어가 CPU `pixels`를 정의하지만 Jetson adapter에서는 NVMM/CUDA handle을 별도 frame payload로 확장해 zero-copy를 유지합니다. + +Boost.System이 발견되면 Boost.Asio/Beast HTTP sink가 함께 컴파일됩니다. 이 sink는 서버 계약 smoke test용이며 장기 운영 경로는 공용 proto의 bidi gRPC입니다. diff --git a/RobotVisionPlatform/device/config/device.example.toml b/RobotVisionPlatform/device/config/device.example.toml new file mode 100644 index 0000000..cc694c5 --- /dev/null +++ b/RobotVisionPlatform/device/config/device.example.toml @@ -0,0 +1,30 @@ +[device] +id = "jetson-dev-001" +site = "lab-a" + +[camera] +kind = "synthetic" # synthetic | v4l2 | nvargus +uri = "/dev/video0" +width = 1280 +height = 720 +fps = 30 + +[inference] +backend = "demo" # demo | tensorrt | deepstream +model_manifest = "/opt/robot-vision/models/current/manifest.json" +confidence = 0.50 +queue_capacity = 2 + +[server] +control_uri = "https://vision.example.internal:7443" +webrtc_signaling_uri = "wss://vision.example.internal/webrtc" +ca_certificate = "/etc/robot-vision/pki/ca.pem" +client_certificate = "/etc/robot-vision/pki/device.pem" +client_key = "/etc/robot-vision/pki/device-key.pem" + +[video] +enabled = false +codec = "h264" +bitrate_kbps = 2500 +keyframe_interval = 30 + diff --git a/RobotVisionPlatform/device/include/rv/bounded_queue.hpp b/RobotVisionPlatform/device/include/rv/bounded_queue.hpp new file mode 100644 index 0000000..cd14ecb --- /dev/null +++ b/RobotVisionPlatform/device/include/rv/bounded_queue.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rv { + +template +class LatestQueue { + public: + explicit LatestQueue(std::size_t capacity) : capacity_(capacity == 0 ? 1 : capacity) {} + + // Real-time policy: discard the oldest work rather than accumulate latency. + bool Push(T value) { + std::scoped_lock lock(mutex_); + if (closed_) return false; + if (items_.size() == capacity_) { + items_.pop_front(); + ++dropped_; + } + items_.push_back(std::move(value)); + ready_.notify_one(); + return true; + } + + std::optional Pop(std::stop_token stop) { + std::unique_lock lock(mutex_); + ready_.wait(lock, stop, [this] { return closed_ || !items_.empty(); }); + if (items_.empty()) return std::nullopt; + T value = std::move(items_.front()); + items_.pop_front(); + return value; + } + + void Close() { + std::scoped_lock lock(mutex_); + closed_ = true; + ready_.notify_all(); + } + + [[nodiscard]] std::uint64_t dropped() const { + std::scoped_lock lock(mutex_); + return dropped_; + } + + private: + const std::size_t capacity_; + mutable std::mutex mutex_; + std::condition_variable_any ready_; + std::deque items_; + std::uint64_t dropped_{}; + bool closed_{}; +}; + +} // namespace rv diff --git a/RobotVisionPlatform/device/include/rv/interfaces.hpp b/RobotVisionPlatform/device/include/rv/interfaces.hpp new file mode 100644 index 0000000..2243a45 --- /dev/null +++ b/RobotVisionPlatform/device/include/rv/interfaces.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "rv/types.hpp" + +#include +#include +#include +#include + +namespace rv { + +class ICamera { + public: + virtual ~ICamera() = default; + virtual Frame Read(std::stop_token stop) = 0; +}; + +class IDetector { + public: + virtual ~IDetector() = default; + virtual std::vector Infer(const Frame& frame) = 0; + [[nodiscard]] virtual std::string Version() const = 0; +}; + +class IEventSink { + public: + virtual ~IEventSink() = default; + virtual bool Publish(const DetectionEvent& event) = 0; +}; + +std::unique_ptr MakeSyntheticCamera(int width, int height, int fps); +std::unique_ptr MakeDemoDetector(); +std::unique_ptr MakeConsoleEventSink(); + +#ifdef RV_HAS_BOOST_HTTP +std::unique_ptr MakeHttpEventSink(std::string host, std::string port, + std::string target); +#endif + +} // namespace rv + diff --git a/RobotVisionPlatform/device/include/rv/pipeline.hpp b/RobotVisionPlatform/device/include/rv/pipeline.hpp new file mode 100644 index 0000000..c83f3b3 --- /dev/null +++ b/RobotVisionPlatform/device/include/rv/pipeline.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "rv/bounded_queue.hpp" +#include "rv/interfaces.hpp" + +#include +#include +#include +#include +#include + +namespace rv { + +struct PipelineOptions { + std::string device_id{"jetson-dev-001"}; + std::size_t frame_queue_capacity{2}; + std::chrono::milliseconds heartbeat_interval{5000}; +}; + +class Pipeline { + public: + Pipeline(PipelineOptions options, std::unique_ptr camera, + std::unique_ptr detector, std::unique_ptr sink); + ~Pipeline(); + + Pipeline(const Pipeline&) = delete; + Pipeline& operator=(const Pipeline&) = delete; + + void Start(); + void Stop(); + [[nodiscard]] PipelineStats Stats() const; + + private: + void CaptureLoop(std::stop_token stop); + void InferenceLoop(std::stop_token stop); + + PipelineOptions options_; + std::unique_ptr camera_; + std::unique_ptr detector_; + std::unique_ptr sink_; + LatestQueue frames_; + std::jthread capture_thread_; + std::jthread inference_thread_; + std::atomic_uint64_t captured_{}; + std::atomic_uint64_t inferred_{}; + std::atomic_uint64_t published_{}; +}; + +} // namespace rv + diff --git a/RobotVisionPlatform/device/include/rv/types.hpp b/RobotVisionPlatform/device/include/rv/types.hpp new file mode 100644 index 0000000..4d0ac67 --- /dev/null +++ b/RobotVisionPlatform/device/include/rv/types.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +namespace rv { + +using Clock = std::chrono::system_clock; + +struct Frame { + std::uint64_t sequence{}; + Clock::time_point captured_at{}; + int width{}; + int height{}; + std::vector pixels; // MVP: CPU BGR. Jetson adapter uses an NVMM handle. +}; + +struct Detection { + std::string label; + float confidence{}; + float x{}; + float y{}; + float width{}; + float height{}; +}; + +struct DetectionEvent { + std::string device_id; + std::uint64_t sequence{}; + Clock::time_point captured_at{}; + std::string model_version; + std::vector detections; +}; + +struct PipelineStats { + std::uint64_t captured{}; + std::uint64_t inferred{}; + std::uint64_t published{}; + std::uint64_t dropped{}; +}; + +} // namespace rv + diff --git a/RobotVisionPlatform/device/src/http_event_sink.cpp b/RobotVisionPlatform/device/src/http_event_sink.cpp new file mode 100644 index 0000000..1be058c --- /dev/null +++ b/RobotVisionPlatform/device/src/http_event_sink.cpp @@ -0,0 +1,84 @@ +#include "rv/interfaces.hpp" + +#include +#include +#include +#include + +#include +#include +#include + +namespace rv { +namespace { +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; +using tcp = net::ip::tcp; + +std::string Escape(std::string_view value) { + std::string result; + for (char ch : value) { + if (ch == '"' || ch == '\\') result.push_back('\\'); + result.push_back(ch); + } + return result; +} + +class HttpEventSink final : public IEventSink { + public: + HttpEventSink(std::string host, std::string port, std::string target) + : host_(std::move(host)), port_(std::move(port)), target_(std::move(target)) {} + + bool Publish(const DetectionEvent& event) override { + try { + net::io_context context; + tcp::resolver resolver(context); + beast::tcp_stream stream(context); + stream.expires_after(std::chrono::seconds(3)); + stream.connect(resolver.resolve(host_, port_)); + + std::ostringstream body; + body << "{\"deviceId\":\"" << Escape(event.device_id) << "\",\"sequence\":" + << event.sequence << ",\"modelVersion\":\"" << Escape(event.model_version) + << "\",\"detections\":["; + for (std::size_t index = 0; index < event.detections.size(); ++index) { + const auto& item = event.detections[index]; + if (index) body << ','; + body << "{\"label\":\"" << Escape(item.label) << "\",\"confidence\":" + << item.confidence << ",\"x\":" << item.x << ",\"y\":" << item.y + << ",\"width\":" << item.width << ",\"height\":" << item.height << '}'; + } + body << "]}"; + + http::request request{http::verb::post, target_, 11}; + request.set(http::field::host, host_); + request.set(http::field::user_agent, "robot-vision-device/0.1"); + request.set(http::field::content_type, "application/json"); + request.body() = body.str(); + request.prepare_payload(); + http::write(stream, request); + beast::flat_buffer buffer; + http::response response; + http::read(stream, buffer, response); + beast::error_code ignored; + stream.socket().shutdown(tcp::socket::shutdown_both, ignored); + return response.result_int() >= 200 && response.result_int() < 300; + } catch (...) { + return false; // Production adapter adds bounded disk spool + retry/backoff. + } + } + + private: + std::string host_; + std::string port_; + std::string target_; +}; +} // namespace + +std::unique_ptr MakeHttpEventSink(std::string host, std::string port, + std::string target) { + return std::make_unique(std::move(host), std::move(port), std::move(target)); +} +} // namespace rv + diff --git a/RobotVisionPlatform/device/src/main.cpp b/RobotVisionPlatform/device/src/main.cpp new file mode 100644 index 0000000..64a1c42 --- /dev/null +++ b/RobotVisionPlatform/device/src/main.cpp @@ -0,0 +1,54 @@ +#include "rv/pipeline.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace { +volatile std::sig_atomic_t running = 1; +void HandleSignal(int) { running = 0; } +} // namespace + +int main(int argc, char** argv) { + std::signal(SIGINT, HandleSignal); + std::signal(SIGTERM, HandleSignal); + + std::string device_id = "jetson-dev-001"; + std::string server_host; + std::string server_port = "5080"; + for (int index = 1; index + 1 < argc; index += 2) { + const std::string_view option = argv[index]; + if (option == "--device-id") device_id = argv[index + 1]; + if (option == "--server-host") server_host = argv[index + 1]; + if (option == "--server-port") server_port = argv[index + 1]; + } + + std::unique_ptr sink; +#ifdef RV_HAS_BOOST_HTTP + if (!server_host.empty()) { + sink = rv::MakeHttpEventSink(server_host, server_port, "/api/events/detections"); + } +#endif + if (!sink) { + if (!server_host.empty()) { + std::cerr << "HTTP sink unavailable; rebuild with Boost.System installed\n"; + } + sink = rv::MakeConsoleEventSink(); + } + + rv::Pipeline pipeline({.device_id = device_id, .frame_queue_capacity = 2}, + rv::MakeSyntheticCamera(1280, 720, 15), rv::MakeDemoDetector(), + std::move(sink)); + pipeline.Start(); + std::cout << "robot-vision device started; Ctrl+C to stop\n"; + while (running) std::this_thread::sleep_for(std::chrono::milliseconds(200)); + pipeline.Stop(); + const auto stats = pipeline.Stats(); + std::cout << "captured=" << stats.captured << " inferred=" << stats.inferred + << " published=" << stats.published << " dropped=" << stats.dropped << '\n'; + return 0; +} diff --git a/RobotVisionPlatform/device/src/pipeline.cpp b/RobotVisionPlatform/device/src/pipeline.cpp new file mode 100644 index 0000000..e1082af --- /dev/null +++ b/RobotVisionPlatform/device/src/pipeline.cpp @@ -0,0 +1,70 @@ +#include "rv/pipeline.hpp" + +#include +#include +#include + +namespace rv { + +Pipeline::Pipeline(PipelineOptions options, std::unique_ptr camera, + std::unique_ptr detector, std::unique_ptr sink) + : options_(std::move(options)), + camera_(std::move(camera)), + detector_(std::move(detector)), + sink_(std::move(sink)), + frames_(options_.frame_queue_capacity) {} + +Pipeline::~Pipeline() { Stop(); } + +void Pipeline::Start() { + if (capture_thread_.joinable() || inference_thread_.joinable()) return; + capture_thread_ = std::jthread([this](std::stop_token stop) { CaptureLoop(stop); }); + inference_thread_ = std::jthread([this](std::stop_token stop) { InferenceLoop(stop); }); +} + +void Pipeline::Stop() { + capture_thread_.request_stop(); + inference_thread_.request_stop(); + frames_.Close(); + if (capture_thread_.joinable()) capture_thread_.join(); + if (inference_thread_.joinable()) inference_thread_.join(); +} + +PipelineStats Pipeline::Stats() const { + return {.captured = captured_.load(), + .inferred = inferred_.load(), + .published = published_.load(), + .dropped = frames_.dropped()}; +} + +void Pipeline::CaptureLoop(std::stop_token stop) { + try { + while (!stop.stop_requested()) { + auto frame = camera_->Read(stop); + if (stop.stop_requested()) break; + ++captured_; + if (!frames_.Push(std::move(frame))) break; + } + } catch (const std::exception& error) { + std::cerr << "capture_error=\"" << error.what() << "\"\n"; + } + frames_.Close(); +} + +void Pipeline::InferenceLoop(std::stop_token stop) { + while (!stop.stop_requested()) { + auto frame = frames_.Pop(stop); + if (!frame) break; + auto detections = detector_->Infer(*frame); + ++inferred_; + DetectionEvent event{.device_id = options_.device_id, + .sequence = frame->sequence, + .captured_at = frame->captured_at, + .model_version = detector_->Version(), + .detections = std::move(detections)}; + if (sink_->Publish(event)) ++published_; + } +} + +} // namespace rv + diff --git a/RobotVisionPlatform/device/src/synthetic_adapters.cpp b/RobotVisionPlatform/device/src/synthetic_adapters.cpp new file mode 100644 index 0000000..e3d5ef1 --- /dev/null +++ b/RobotVisionPlatform/device/src/synthetic_adapters.cpp @@ -0,0 +1,69 @@ +#include "rv/interfaces.hpp" + +#include +#include +#include + +namespace rv { +namespace { + +class SyntheticCamera final : public ICamera { + public: + SyntheticCamera(int width, int height, int fps) + : width_(width), height_(height), interval_(1000 / (fps <= 0 ? 1 : fps)) {} + + Frame Read(std::stop_token stop) override { + std::this_thread::sleep_for(interval_); + if (stop.stop_requested()) return {}; + Frame frame{.sequence = ++sequence_, + .captured_at = Clock::now(), + .width = width_, + .height = height_, + .pixels = {}}; + // Synthetic mode avoids allocating full images; real adapters retain NVMM buffers. + return frame; + } + + private: + int width_; + int height_; + std::chrono::milliseconds interval_; + std::uint64_t sequence_{}; +}; + +class DemoDetector final : public IDetector { + public: + std::vector Infer(const Frame& frame) override { + if (frame.sequence % 3 != 0) return {}; + const auto phase = static_cast(frame.sequence % 100) / 100.0F; + return {{.label = "demo-object", + .confidence = 0.91F, + .x = phase, + .y = 0.2F, + .width = 0.2F, + .height = 0.3F}}; + } + std::string Version() const override { return "demo-detector/0.1.0"; } +}; + +class ConsoleEventSink final : public IEventSink { + public: + bool Publish(const DetectionEvent& event) override { + std::cout << "device=" << event.device_id << " sequence=" << event.sequence + << " model=" << event.model_version + << " detections=" << event.detections.size() << '\n'; + return true; + } +}; + +} // namespace + +std::unique_ptr MakeSyntheticCamera(int width, int height, int fps) { + return std::make_unique(width, height, fps); +} +std::unique_ptr MakeDemoDetector() { return std::make_unique(); } +std::unique_ptr MakeConsoleEventSink() { + return std::make_unique(); +} + +} // namespace rv diff --git a/RobotVisionPlatform/device/tests/core_tests.cpp b/RobotVisionPlatform/device/tests/core_tests.cpp new file mode 100644 index 0000000..8d3e537 --- /dev/null +++ b/RobotVisionPlatform/device/tests/core_tests.cpp @@ -0,0 +1,28 @@ +#include "rv/bounded_queue.hpp" +#include "rv/pipeline.hpp" + +#include +#include +#include + +int main() { + rv::LatestQueue queue(2); + assert(queue.Push(1)); + assert(queue.Push(2)); + assert(queue.Push(3)); + assert(queue.dropped() == 1); + std::stop_source stop; + assert(queue.Pop(stop.get_token()).value() == 2); + + rv::Pipeline pipeline({.device_id = "test", .frame_queue_capacity = 2}, + rv::MakeSyntheticCamera(32, 32, 100), rv::MakeDemoDetector(), + rv::MakeConsoleEventSink()); + pipeline.Start(); + std::this_thread::sleep_for(std::chrono::milliseconds(80)); + pipeline.Stop(); + const auto stats = pipeline.Stats(); + assert(stats.captured > 0); + assert(stats.inferred > 0); + return 0; +} + diff --git a/RobotVisionPlatform/docs/README.md b/RobotVisionPlatform/docs/README.md new file mode 100644 index 0000000..b01a36b --- /dev/null +++ b/RobotVisionPlatform/docs/README.md @@ -0,0 +1,14 @@ +# 문서 인덱스 + +| 문서 | 목적 | +|---|---| +| [architecture.md](architecture.md) | 전체 구성, 경계, 데이터 흐름 | +| [initial-release-guide.md](initial-release-guide.md) | v0.1.0 빌드·설치·검증·롤백 | +| [jetson-deployment.md](jetson-deployment.md) | Orin Nano Super 준비와 서비스 운영 | +| [streaming-and-protocols.md](streaming-and-protocols.md) | gRPC/WebRTC/압축 선택 기준 | +| [model-lifecycle.md](model-lifecycle.md) | 학습부터 OTA/롤백까지 | +| [security-and-operations.md](security-and-operations.md) | 보안, 관측성, 장애 대응 | +| [adr/0001-platform-architecture.md](adr/0001-platform-architecture.md) | 주요 기술 선택 기록 | + +문서는 구현과 같은 PR에서 갱신합니다. 호환성 표와 외부 링크는 릴리스마다 재검증합니다. + diff --git a/RobotVisionPlatform/docs/adr/0001-platform-architecture.md b/RobotVisionPlatform/docs/adr/0001-platform-architecture.md new file mode 100644 index 0000000..8052213 --- /dev/null +++ b/RobotVisionPlatform/docs/adr/0001-platform-architecture.md @@ -0,0 +1,13 @@ +# ADR 0001: Edge C++20 + ASP.NET Core + MAUI Hybrid + +- 상태: Accepted +- 날짜: 2026-08-17 + +## 결정 + +Jetson 실행부는 C++20과 NVIDIA 가속 스택을 사용합니다. 서버는 ASP.NET Core, 운영 UI는 웹과 네이티브 배포가 모두 가능한 MAUI Blazor Hybrid/shared Razor components를 사용합니다. gRPC는 control plane, WebRTC는 media plane, SignalR는 UI fan-out에 사용합니다. + +## 결과 + +GPU zero-copy와 하드웨어 codec을 활용하면서 서버 생산성을 유지할 수 있습니다. 반면 프로토콜과 미디어 세션이 분리되므로 연결 수명주기와 correlation을 명시적으로 관리해야 합니다. MAUI는 서버 역할을 맡지 않고 관제 클라이언트로 한정합니다. + diff --git a/RobotVisionPlatform/docs/architecture.md b/RobotVisionPlatform/docs/architecture.md new file mode 100644 index 0000000..e27e9a7 --- /dev/null +++ b/RobotVisionPlatform/docs/architecture.md @@ -0,0 +1,45 @@ +# 시스템 아키텍처 + +## 원칙 + +1. 장치는 네트워크가 끊겨도 추론과 안전 관련 작업을 계속한다. +2. 영상(media plane)과 이벤트/제어(control plane)를 분리해 장애와 대역폭을 격리한다. +3. 모든 큐는 bounded queue로 만들고, 실시간 경로에서는 오래된 프레임을 버려 지연 누적을 막는다. +4. 카메라·추론기·전송기는 인터페이스 뒤에 두어 USB/CSI, TensorRT/DeepStream, WebRTC/SRT를 교체한다. +5. 서버가 보내는 동작 명령은 정책 검사, 만료 시간, idempotency key, 감사 로그를 갖는다. + +## 구성 요소 + +- `device`: 프레임 획득, 전처리, 추론, 로컬 규칙, 인코딩, 이벤트/상태 전송, 오프라인 버퍼 +- `server/Api`: 장치 세션, ingest, 최신 상태, SignalR fan-out, 향후 저장소/VLM 작업 큐 +- `server/Dashboard`: 웹/Windows/Android 관제 UI의 공유 Razor 컴포넌트 +- `shared/proto`: wire contract의 단일 원본 +- Object storage: 이벤트 클립/스냅샷. 원본 상시 업로드는 기본값이 아님 +- PostgreSQL: 장치·모델·배포·이벤트 메타데이터 + +## 장치 파이프라인 + +```text +Capture (NVMM zero-copy) + -> latest-frame queue + -> batching/preprocess + -> TensorRT inference + -> local rule engine + +-> protobuf event queue -> retrying gRPC transport + +-> OSD -> NVENC -> WebRTC/SRT +``` + +코어는 합성 구현으로 테스트할 수 있습니다. 실제 Jetson adapter는 DeepStream 9.x의 C/C++ 플러그인 또는 GStreamer 앱 파이프라인으로 연결합니다. CUDA/NVMM 버퍼를 CPU로 복사하지 않는 것이 핵심입니다. + +## 서버 처리 + +수집 handler는 무거운 VLM을 직접 실행하지 않습니다. 이벤트를 검증·저장하고 bounded channel/메시지 브로커에 넣은 뒤 즉시 응답합니다. 별도 worker가 keyframe/짧은 clip만 VLM에 batch로 전달합니다. 실시간 객체 추적은 edge에서, 의미 해석은 server에서 수행하는 계층형 추론입니다. + +## 확장 지점 + +- `ICamera`: Synthetic → V4L2/GStreamer/NvArgus +- `IDetector`: Noop → ONNX Runtime → TensorRT/DeepStream +- `IEventSink`: stdout → gRPC/mTLS → broker gateway +- `IVideoPublisher`: disabled → WebRTC → SRT/RTSP +- `IAction`: log only → GPIO/ROS 2/PLC adapter + diff --git a/RobotVisionPlatform/docs/initial-release-guide.md b/RobotVisionPlatform/docs/initial-release-guide.md new file mode 100644 index 0000000..df2308b --- /dev/null +++ b/RobotVisionPlatform/docs/initial-release-guide.md @@ -0,0 +1,63 @@ +# 초기 릴리스 가이드 (v0.1.0) + +v0.1.0의 목적은 **합성 장치 → 이벤트 수집 서버 → 웹 관제 화면**의 end-to-end 계약 검증입니다. 실제 카메라 영상/WebRTC/TensorRT는 다음 마일스톤입니다. + +## 사전 조건 + +- 개발 PC: CMake 3.22+, C++20 compiler, .NET 10 SDK, Docker(선택) +- Jetson: JetPack/펌웨어 확인, 19V 정격 전원, 충분한 냉각, 유선 LAN 우선 +- 배포 환경: 장치 ID, 서버 주소, 시간 동기화, 인증서 발급 계획 + +## 1. 검증 빌드 + +```bash +./scripts/build.sh +./scripts/test.sh +``` + +Windows PowerShell에서는 `./scripts/build.ps1`, `./scripts/test.ps1`을 사용합니다. C++ compiler가 없는 PC에서는 .NET만 검증되고 CI Linux job이 C++ 빌드를 보완합니다. + +## 2. 서버 기동 + +```bash +docker compose -f deploy/compose/docker-compose.yml up --build +curl http://localhost:5080/health +``` + +또는 `dotnet run --project server/src/RobotVision.Server.Api`로 실행합니다. + +## 3. 장치 smoke test + +현재 기본 agent는 stdout sink를 사용합니다. + +```bash +./device/build/robot_vision_device --device-id jetson-001 +``` + +서버 계약은 아래 이벤트로 검증합니다. + +```bash +curl -X POST http://localhost:5080/api/events/detections \ + -H 'content-type: application/json' \ + -d '{"deviceId":"jetson-001","sequence":1,"modelVersion":"demo/0.1.0","detections":[]}' +curl http://localhost:5080/api/devices/jetson-001 +``` + +## 4. 릴리스 산출물 + +- `robot_vision_device` arm64 binary 또는 `.deb` +- server container image(digest로 고정) +- model bundle + manifest + signature +- `device.toml` schema/version과 migration note +- SBOM, checksum, release notes, JetPack/DeepStream 호환표 + +tag는 SemVer를 사용합니다. 장치 binary, 서버, 모델은 서로 다른 버전을 가지며 release manifest에서 검증된 조합을 선언합니다. + +## 5. 단계 배포와 롤백 + +1. lab device 1대에 설치하고 health/FPS/온도/이벤트를 30분 확인 +2. canary cohort에서 24시간 관찰 +3. cohort를 점진 확대 +4. crash loop, latency/temperature/drop guardrail 위반 시 이전 symlink와 systemd service로 즉시 rollback + +현재 골격은 자동 OTA를 구현하지 않았습니다. 인증·서명·atomic switch가 완료되기 전 원격 덮어쓰기는 금지합니다. diff --git a/RobotVisionPlatform/docs/jetson-deployment.md b/RobotVisionPlatform/docs/jetson-deployment.md new file mode 100644 index 0000000..ff43e9b --- /dev/null +++ b/RobotVisionPlatform/docs/jetson-deployment.md @@ -0,0 +1,49 @@ +# Jetson Orin Nano Super 준비와 배포 + +대상은 **NVIDIA Jetson Orin Nano Super Developer Kit**입니다. Developer Kit은 개발/검증용이며 양산 제품은 production module과 carrier/thermal/power 설계를 별도로 검토해야 합니다. + +## 1. OS와 펌웨어 + +1. [공식 시작 가이드](https://developer.nvidia.com/embedded/learn/get-started-jetson-orin-nano-devkit)에서 JetPack 6.x 호환 펌웨어 여부를 먼저 확인합니다. 구형 출하 펌웨어는 6.x SD 이미지와 바로 호환되지 않을 수 있습니다. +2. SDK Manager 또는 공식 SD 이미지를 사용합니다. NVMe를 운영 저장소로 권장합니다. +3. `cat /etc/nv_tegra_release`, `dpkg-query -W nvidia-jetpack`, `nvpmodel -q` 결과를 릴리스 기록에 첨부합니다. +4. Super Mode는 충분한 전원과 냉각을 전제로 설정하고 30분 이상 thermal soak test를 수행합니다. + +JetPack, CUDA, cuDNN, TensorRT, DeepStream은 독립적으로 최신 버전을 섞지 않습니다. [DeepStream release notes](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_Release_notes.html)의 platform compatibility를 기준으로 한 세트를 고정합니다. + +## 2. 빌드 + +```bash +sudo apt-get update +sudo apt-get install -y build-essential cmake ninja-build libboost-system-dev +cmake -S device -B device/build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build device/build +ctest --test-dir device/build --output-on-failure +cmake --install device/build --prefix device/stage +``` + +실제 adapter를 추가할 때 JetPack이 제공하는 GStreamer, CUDA, TensorRT 개발 패키지를 사용합니다. 컨테이너를 쓴다면 Jetson용 L4T/DeepStream base image와 NVIDIA Container Runtime의 호환성을 고정합니다. + +## 3. 설치 + +```bash +sudo install -d -o robotvision -g robotvision /opt/robot-vision/bin /etc/robot-vision +sudo install -m 0755 device/stage/bin/robot_vision_device /opt/robot-vision/bin/ +sudo install -m 0640 device/config/device.example.toml /etc/robot-vision/device.toml +sudo install -m 0644 deploy/systemd/robot-vision-device.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now robot-vision-device +``` + +운영에서는 `DynamicUser`보다 카메라/video 그룹과 인증서 권한을 가진 고정 전용 사용자를 생성합니다. unit의 `ExecStart`는 현재 CLI와 일치하며 TOML parser가 연결되기 전까지 `--device-id`만 사용합니다. + +## 4. 확인 + +```bash +systemctl status robot-vision-device +journalctl -u robot-vision-device -f +tegrastats +``` + +카메라 분리, 서버 차단, Wi-Fi 전환, 재부팅, 온도 상승 상태를 검증합니다. OTA 전에는 이전 binary/model symlink를 유지해 한 명령으로 rollback할 수 있어야 합니다. + diff --git a/RobotVisionPlatform/docs/model-lifecycle.md b/RobotVisionPlatform/docs/model-lifecycle.md new file mode 100644 index 0000000..6297845 --- /dev/null +++ b/RobotVisionPlatform/docs/model-lifecycle.md @@ -0,0 +1,30 @@ +# 모델 수명주기 + +## 흐름 + +1. 수집: 개인정보 마스킹, scene/device metadata, 중복 제거 후 샘플만 보존 +2. 라벨: 클래스 정의서와 edge-case bucket을 먼저 고정 +3. 분할: 같은 영상/장소가 train과 validation에 섞이지 않도록 group split +4. 학습: pretrained detector fine-tuning, augmentation ablation, 실험 추적 +5. 평가: mAP 외에 class별 precision/recall, latency, memory, thermal throttling 측정 +6. export: ONNX 검증 후 target Jetson에서 TensorRT engine 생성 +7. 승인: manifest와 artifact hash/signature 검증 +8. 배포: canary 1대 → cohort → fleet, health/accuracy guardrail 위반 시 rollback +9. 개선: uncertainty/오탐/미탐 후보를 active-learning queue로 회수 + +TensorRT engine은 GPU/JetPack/TensorRT 버전에 민감할 수 있으므로 범용 ONNX와 target별 engine을 구분하고 manifest에 호환 조건을 기록합니다. 모델 파일만 교체하지 말고 labels, preprocessing, postprocessing, calibration cache를 하나의 불변 bundle로 취급합니다. + +예시 manifest: + +```json +{ + "modelId": "forklift-detector", + "version": "0.1.0", + "format": "onnx", + "sha256": "replace-me", + "input": { "width": 640, "height": 640, "color": "RGB" }, + "labels": ["person", "forklift"], + "minimumRuntime": { "jetpack": "6.x", "tensorrt": "verify-on-target" } +} +``` + diff --git a/RobotVisionPlatform/docs/security-and-operations.md b/RobotVisionPlatform/docs/security-and-operations.md new file mode 100644 index 0000000..946590f --- /dev/null +++ b/RobotVisionPlatform/docs/security-and-operations.md @@ -0,0 +1,13 @@ +# 보안 및 운영 + +- 장치별 인증서와 mTLS를 사용하고 bootstrap token은 1회용으로 제한합니다. +- private key와 Wi-Fi 비밀번호는 이미지·Git·평문 TOML에 넣지 않습니다. +- 모델/설정 bundle은 서명과 SHA-256을 확인한 뒤 staging 경로에 내려받고 atomic switch합니다. +- 명령은 allow-list, TTL, idempotency key, issuer, audit event를 요구합니다. +- 카메라 영상은 최소 수집·최소 보존·접근 감사 원칙을 적용하고 얼굴/번호판 정책을 별도로 정합니다. +- root가 아닌 systemd 사용자, read-only root filesystem, 필요한 device/capability만 허용합니다. + +관측 항목은 capture/inference/publish FPS, end-to-end latency p50/p95/p99, queue drops, reconnect count, temperature, power mode, disk, active model hash입니다. 로그·metric·trace에는 같은 `device_id`와 `correlation_id`를 넣습니다. + +장애 시 장치는 지수 backoff+jitter로 재접속하고 최근 이벤트를 용량 제한 spool에 보관합니다. 디스크가 차면 원본 프레임보다 오래된 비중요 이벤트를 먼저 제거합니다. 서버 장애가 장치 로컬 동작을 중단시키면 안 됩니다. + diff --git a/RobotVisionPlatform/docs/streaming-and-protocols.md b/RobotVisionPlatform/docs/streaming-and-protocols.md new file mode 100644 index 0000000..a94f6c4 --- /dev/null +++ b/RobotVisionPlatform/docs/streaming-and-protocols.md @@ -0,0 +1,24 @@ +# 통신과 영상 전송 + +## 권장 조합 + +| 데이터 | 기본 | 대안 | 이유 | +|---|---|---|---| +| live video | WebRTC + H.264 | SRT/RTSP | 브라우저/MAUI 저지연 재생, congestion control | +| detection/health | gRPC bidirectional stream | MQTT | typed contract, 장치 명령을 같은 세션에서 전달 | +| dashboard update | SignalR | SSE | .NET 클라이언트와 웹 fan-out | +| clip upload | HTTPS object upload | gRPC chunk | 재시도와 대용량 분리 | + +protobuf 자체가 작은 수치 이벤트에 효율적이므로 이미 압축된 JPEG/H.264를 gzip으로 다시 압축하지 않습니다. gRPC message compression은 큰 반복 텍스트에만 측정 후 적용합니다. 영상은 NVENC에서 H.264 low-latency preset을 사용하고, 해상도/FPS/bitrate를 네트워크 상태에 맞춰 조절합니다. + +`shared/proto/vision/v1/device.proto`의 `Connect`는 장치가 시작하는 장기 bidi stream입니다. heartbeat, detection batch, model state를 보내고 서버는 config/model/action 명령을 돌려줍니다. 메시지에 `device_id`, `sequence`, UTC timestamp, schema version을 두어 재연결과 중복을 처리합니다. + +WebRTC signaling은 인증된 HTTPS/SignalR endpoint로 추가하고, NAT 환경에서는 STUN/TURN을 둡니다. LAN 전용 MVP라도 TLS와 장치 신원은 생략하지 않습니다. + +## 공식 참고 + +- [NVIDIA Jetson WebRTC hardware acceleration](https://docs.nvidia.com/jetson/archives/r36.3/DeveloperGuide/SD/HardwareAccelerationInTheWebrtcFramework.html) +- [gRPC C++ basics](https://grpc.io/docs/languages/cpp/basics/) +- [ASP.NET Core gRPC services](https://learn.microsoft.com/aspnet/core/grpc/services?view=aspnetcore-10.0) +- [ASP.NET Core SignalR](https://learn.microsoft.com/aspnet/core/signalr/introduction) + diff --git a/RobotVisionPlatform/scripts/build.ps1 b/RobotVisionPlatform/scripts/build.ps1 new file mode 100644 index 0000000..efbae10 --- /dev/null +++ b/RobotVisionPlatform/scripts/build.ps1 @@ -0,0 +1,14 @@ +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $PSScriptRoot + +dotnet build "$root/server/RobotVision.Server.slnx" --configuration Release + +$compiler = Get-Command cl, g++, clang++ -ErrorAction SilentlyContinue | Select-Object -First 1 +if ($null -eq $compiler) { + Write-Warning 'C++ compiler was not found. Run from a Visual Studio Developer PowerShell or install LLVM/GCC.' + exit 0 +} + +cmake -S "$root/device" -B "$root/device/build" -DRV_ENABLE_BOOST_HTTP=OFF +cmake --build "$root/device/build" --config Release --parallel + diff --git a/RobotVisionPlatform/scripts/build.sh b/RobotVisionPlatform/scripts/build.sh new file mode 100644 index 0000000..05e9663 --- /dev/null +++ b/RobotVisionPlatform/scripts/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +dotnet build "$repo_root/server/RobotVision.Server.slnx" --configuration Release +cmake -S "$repo_root/device" -B "$repo_root/device/build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DRV_ENABLE_BOOST_HTTP=OFF +cmake --build "$repo_root/device/build" --parallel + diff --git a/RobotVisionPlatform/scripts/test.ps1 b/RobotVisionPlatform/scripts/test.ps1 new file mode 100644 index 0000000..f3aff20 --- /dev/null +++ b/RobotVisionPlatform/scripts/test.ps1 @@ -0,0 +1,10 @@ +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $PSScriptRoot + +dotnet run --project "$root/server/tests/RobotVision.Server.Tests" --configuration Release +if (Test-Path "$root/device/build/CTestTestfile.cmake") { + ctest --test-dir "$root/device/build" -C Release --output-on-failure +} else { + Write-Warning 'C++ build directory is unavailable; device tests were skipped.' +} + diff --git a/RobotVisionPlatform/scripts/test.sh b/RobotVisionPlatform/scripts/test.sh new file mode 100644 index 0000000..efd8e72 --- /dev/null +++ b/RobotVisionPlatform/scripts/test.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +dotnet run --project "$repo_root/server/tests/RobotVision.Server.Tests" --configuration Release +ctest --test-dir "$repo_root/device/build" --output-on-failure + diff --git a/RobotVisionPlatform/server/Directory.Build.props b/RobotVisionPlatform/server/Directory.Build.props new file mode 100644 index 0000000..6b7a99a --- /dev/null +++ b/RobotVisionPlatform/server/Directory.Build.props @@ -0,0 +1,10 @@ + + + net10.0 + enable + enable + true + true + + + diff --git a/RobotVisionPlatform/server/README.md b/RobotVisionPlatform/server/README.md new file mode 100644 index 0000000..e216de1 --- /dev/null +++ b/RobotVisionPlatform/server/README.md @@ -0,0 +1,26 @@ +# Server and Dashboard + +서버는 ASP.NET Core 수집 API와 SignalR fan-out을 제공하고, 운영 UI는 웹 MVP와 선택형 MAUI Blazor Hybrid 앱으로 구성합니다. + +```bash +dotnet build RobotVision.Server.slnx +dotnet run --project src/RobotVision.Server.Api +``` + +테스트 이벤트: + +```bash +curl -X POST http://localhost:5080/api/events/detections \ + -H 'content-type: application/json' \ + -d '{"deviceId":"jetson-001","sequence":1,"modelVersion":"demo/1","detections":[{"label":"person","confidence":0.94,"x":0.1,"y":0.2,"width":0.3,"height":0.5}]}' +``` + +브라우저에서 `http://localhost:5080`을 열면 최신 장치 상태를 볼 수 있습니다. 데이터는 현재 in-memory이므로 production 단계에서 PostgreSQL과 object storage를 연결합니다. + +MAUI 프로젝트는 기본 solution에서 제외되어 CI에 MAUI workload를 강제하지 않습니다. 설치 후 별도로 빌드합니다. + +```bash +dotnet workload install maui +dotnet build src/RobotVision.Dashboard.Maui/RobotVision.Dashboard.Maui.csproj -f net10.0-windows10.0.19041.0 +``` + diff --git a/RobotVisionPlatform/server/RobotVision.Server.slnx b/RobotVisionPlatform/server/RobotVision.Server.slnx new file mode 100644 index 0000000..a3daf5f --- /dev/null +++ b/RobotVisionPlatform/server/RobotVision.Server.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml new file mode 100644 index 0000000..459f3d3 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs new file mode 100644 index 0000000..21cd909 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs @@ -0,0 +1,11 @@ +namespace RobotVision.Dashboard.Maui; + +public partial class App : Application +{ + public App() + { + InitializeComponent(); + MainPage = new MainPage(); + } +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor new file mode 100644 index 0000000..747ec1e --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor @@ -0,0 +1,4 @@ +

Robot Vision

+

MAUI Blazor Hybrid 관제 클라이언트 골격입니다.

+

다음 단계에서 SignalR client와 WebRTC player를 이 공유 Razor UI에 연결합니다.

+ diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml new file mode 100644 index 0000000..5e529b7 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs new file mode 100644 index 0000000..2a09766 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs @@ -0,0 +1,3 @@ +namespace RobotVision.Dashboard.Maui; +public partial class MainPage : ContentPage { public MainPage() => InitializeComponent(); } + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs new file mode 100644 index 0000000..97bd48b --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs @@ -0,0 +1,18 @@ +using Microsoft.Extensions.Logging; + +namespace RobotVision.Dashboard.Maui; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder().UseMauiApp(); + builder.Services.AddMauiBlazorWebView(); +#if DEBUG + builder.Services.AddBlazorWebViewDeveloperTools(); + builder.Logging.AddDebug(); +#endif + return builder.Build(); + } +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/RobotVision.Dashboard.Maui.csproj b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/RobotVision.Dashboard.Maui.csproj new file mode 100644 index 0000000..446b77f --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/RobotVision.Dashboard.Maui.csproj @@ -0,0 +1,17 @@ + + + net10.0-android;net10.0-windows10.0.19041.0 + Exe + RobotVision.Dashboard.Maui + true + true + Robot Vision + io.robotvision.dashboard + 0.1.0 + 1 + + + + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/_Imports.razor b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/_Imports.razor new file mode 100644 index 0000000..dbd903a --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/_Imports.razor @@ -0,0 +1,2 @@ +@using Microsoft.AspNetCore.Components.Web + diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/wwwroot/index.html b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/wwwroot/index.html new file mode 100644 index 0000000..6b427dd --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/wwwroot/index.html @@ -0,0 +1,3 @@ + +
Loading…
+ diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs new file mode 100644 index 0000000..35348e0 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs @@ -0,0 +1,29 @@ +using System.Threading.Channels; +using Microsoft.AspNetCore.SignalR; +using RobotVision.Server.Contracts; + +namespace RobotVision.Server.Api; + +public sealed class DetectionFanoutWorker( + Channel channel, + IHubContext hub, + ILogger logger) : BackgroundService +{ + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await foreach (var item in channel.Reader.ReadAllAsync(stoppingToken)) + { + try + { + await hub.Clients.Group(MonitoringHub.GroupName(item.DeviceId)) + .SendAsync("detection", item, stoppingToken); + await hub.Clients.Group("fleet").SendAsync("detection", item, stoppingToken); + } + catch (Exception error) when (error is not OperationCanceledException) + { + logger.LogError(error, "Failed to fan out event for {DeviceId}", item.DeviceId); + } + } + } +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs new file mode 100644 index 0000000..cec3407 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs @@ -0,0 +1,28 @@ +using System.Collections.Concurrent; +using RobotVision.Server.Contracts; + +namespace RobotVision.Server.Api; + +public sealed class DeviceRegistry +{ + private readonly ConcurrentDictionary _devices = new(StringComparer.Ordinal); + + public void Upsert(DetectionEventDto item) + { + var snapshot = new DeviceSnapshot( + item.DeviceId, + DateTimeOffset.UtcNow, + item.Sequence, + item.ModelVersion, + item.Detections); + _devices.AddOrUpdate(item.DeviceId, snapshot, (_, current) => + item.Sequence >= current.LastSequence ? snapshot : current); + } + + public IReadOnlyCollection List() => + _devices.Values.OrderBy(x => x.DeviceId, StringComparer.Ordinal).ToArray(); + + public bool TryGet(string deviceId, out DeviceSnapshot? snapshot) => + _devices.TryGetValue(deviceId, out snapshot); +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Dockerfile b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Dockerfile new file mode 100644 index 0000000..b0e54fe --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Dockerfile @@ -0,0 +1,16 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY server/Directory.Build.props server/ +COPY server/src/RobotVision.Server.Contracts/RobotVision.Server.Contracts.csproj server/src/RobotVision.Server.Contracts/ +COPY server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj server/src/RobotVision.Server.Api/ +RUN dotnet restore server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj +COPY server/src server/src +RUN dotnet publish server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj -c Release -o /app --no-restore + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +WORKDIR /app +COPY --from=build /app . +USER $APP_UID +EXPOSE 5080 +ENTRYPOINT ["dotnet", "RobotVision.Server.Api.dll"] + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs new file mode 100644 index 0000000..611e5d2 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.SignalR; + +namespace RobotVision.Server.Api; + +public sealed class MonitoringHub : Hub +{ + public Task WatchDevice(string deviceId) => + Groups.AddToGroupAsync(Context.ConnectionId, GroupName(deviceId)); + + public Task StopWatchingDevice(string deviceId) => + Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName(deviceId)); + + public static string GroupName(string deviceId) => $"device:{deviceId}"; +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs new file mode 100644 index 0000000..2020f32 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs @@ -0,0 +1,49 @@ +using System.Threading.Channels; +using Microsoft.AspNetCore.Http.Json; +using RobotVision.Server.Api; +using RobotVision.Server.Contracts; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.Configure(options => + options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase); +builder.Services.AddSignalR(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(Channel.CreateBounded(new BoundedChannelOptions(1024) +{ + FullMode = BoundedChannelFullMode.DropOldest, + SingleReader = true, + SingleWriter = false +})); +builder.Services.AddHostedService(); +builder.Services.AddHealthChecks(); + +var app = builder.Build(); +app.UseDefaultFiles(); +app.UseStaticFiles(); +app.MapHealthChecks("/health"); +app.MapHub("/hubs/monitoring"); + +app.MapGet("/api/devices", (DeviceRegistry registry) => Results.Ok(registry.List())); +app.MapGet("/api/devices/{deviceId}", (string deviceId, DeviceRegistry registry) => + registry.TryGet(deviceId, out var device) ? Results.Ok(device) : Results.NotFound()); + +app.MapPost("/api/events/detections", async ( + DetectionEventDto item, + DeviceRegistry registry, + Channel channel, + CancellationToken cancellationToken) => +{ + if (string.IsNullOrWhiteSpace(item.DeviceId) || item.Detections.Count > 1_000) + return Results.BadRequest(new { error = "deviceId is required; max 1000 detections" }); + + registry.Upsert(item); + if (!channel.Writer.TryWrite(item)) + return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); + await Task.CompletedTask; + return Results.Accepted($"/api/devices/{Uri.EscapeDataString(item.DeviceId)}"); +}); + +app.Run(); + +public partial class Program; + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj b/RobotVisionPlatform/server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj new file mode 100644 index 0000000..26a4c16 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/RobotVision.Server.Api.csproj @@ -0,0 +1,6 @@ + + + + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/appsettings.json b/RobotVisionPlatform/server/src/RobotVision.Server.Api/appsettings.json new file mode 100644 index 0000000..679d9d0 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/appsettings.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "Endpoints": { + "Http": { + "Url": "http://0.0.0.0:5080", + "Protocols": "Http1AndHttp2" + } + } + } +} + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html b/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html new file mode 100644 index 0000000..5b9846d --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html @@ -0,0 +1,38 @@ + + + + + + Robot Vision Monitor + + + +

Robot Vision Monitor

MVP device registry · refreshes every 2 seconds
+
장치 이벤트를 기다리는 중…
+ + + + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs new file mode 100644 index 0000000..f38691e --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs @@ -0,0 +1,29 @@ +namespace RobotVision.Server.Contracts; + +public sealed record BoundingBox(float X, float Y, float Width, float Height); + +public sealed record DetectionDto( + string Label, + float Confidence, + float X, + float Y, + float Width, + float Height) +{ + public BoundingBox Box => new(X, Y, Width, Height); +} + +public sealed record DetectionEventDto( + string DeviceId, + ulong Sequence, + string ModelVersion, + IReadOnlyList Detections, + DateTimeOffset? CapturedAt = null); + +public sealed record DeviceSnapshot( + string DeviceId, + DateTimeOffset LastSeenAt, + ulong LastSequence, + string ModelVersion, + IReadOnlyList Detections); + diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/RobotVision.Server.Contracts.csproj b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/RobotVision.Server.Contracts.csproj new file mode 100644 index 0000000..9609c61 --- /dev/null +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/RobotVision.Server.Contracts.csproj @@ -0,0 +1,2 @@ + + diff --git a/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs new file mode 100644 index 0000000..6579c45 --- /dev/null +++ b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs @@ -0,0 +1,10 @@ +using RobotVision.Server.Api; +using RobotVision.Server.Contracts; + +var registry = new DeviceRegistry(); +registry.Upsert(new DetectionEventDto("test-device", 2, "model/1", [])); +registry.Upsert(new DetectionEventDto("test-device", 1, "stale", [])); +if (!registry.TryGet("test-device", out var item) || item?.LastSequence != 2) + throw new InvalidOperationException("Registry must reject out-of-order snapshots."); +Console.WriteLine("Server core tests passed."); + diff --git a/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/RobotVision.Server.Tests.csproj b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/RobotVision.Server.Tests.csproj new file mode 100644 index 0000000..45fb238 --- /dev/null +++ b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/RobotVision.Server.Tests.csproj @@ -0,0 +1,8 @@ + + Exe + + + + + + diff --git a/RobotVisionPlatform/shared/proto/vision/v1/device.proto b/RobotVisionPlatform/shared/proto/vision/v1/device.proto new file mode 100644 index 0000000..d1431ea --- /dev/null +++ b/RobotVisionPlatform/shared/proto/vision/v1/device.proto @@ -0,0 +1,71 @@ +syntax = "proto3"; + +package robotvision.vision.v1; +option csharp_namespace = "RobotVision.Contracts.Grpc.V1"; + +import "google/protobuf/timestamp.proto"; + +service DeviceControl { + rpc Connect(stream DeviceEnvelope) returns (stream ServerCommand); +} + +message DeviceEnvelope { + string device_id = 1; + uint64 sequence = 2; + google.protobuf.Timestamp sent_at = 3; + oneof payload { + Heartbeat heartbeat = 10; + DetectionBatch detections = 11; + ModelState model_state = 12; + } +} + +message Heartbeat { + double capture_fps = 1; + double inference_fps = 2; + double temperature_c = 3; + uint64 dropped_frames = 4; + string software_version = 5; +} + +message DetectionBatch { + google.protobuf.Timestamp captured_at = 1; + string model_version = 2; + repeated Detection items = 3; +} + +message Detection { + string label = 1; + float confidence = 2; + float x = 3; + float y = 4; + float width = 5; + float height = 6; + uint64 track_id = 7; +} + +message ModelState { + string model_id = 1; + string version = 2; + string sha256 = 3; + string status = 4; +} + +message ServerCommand { + string command_id = 1; + google.protobuf.Timestamp expires_at = 2; + oneof payload { + UpdateConfiguration update_configuration = 10; + DeployModel deploy_model = 11; + RequestKeyframe request_keyframe = 12; + } +} + +message UpdateConfiguration { string json_merge_patch = 1; } +message DeployModel { + string manifest_uri = 1; + string manifest_sha256 = 2; + string signature = 3; +} +message RequestKeyframe { string reason = 1; } + diff --git a/RobotVisionPlatform/tips/README.md b/RobotVisionPlatform/tips/README.md new file mode 100644 index 0000000..7980f92 --- /dev/null +++ b/RobotVisionPlatform/tips/README.md @@ -0,0 +1,13 @@ +# 모델 학습 Tips + +처음에는 [학습 기초](fundamentals/vision-training-basics.md) → [데이터셋](data/dataset-engineering.md) → [fine-tuning](training/fine-tuning-playbook.md) → [평가](evaluation/edge-evaluation.md) → [배포 최적화](deployment/jetson-optimization.md) 순서로 진행합니다. + +## 카테고리 + +- `fundamentals`: 학습·과적합·손실·transfer learning 기초 +- `data`: 수집, 라벨링, split, 증강, 개인정보 +- `training`: 실험 설계와 fine-tuning 실행 절차 +- `evaluation`: 정확도뿐 아니라 장치 지연/열/전력 평가 +- `deployment`: ONNX/TensorRT/DeepStream 최적화 +- `resources`: 공식 문서와 학습 사이트 + diff --git a/RobotVisionPlatform/tips/data/dataset-engineering.md b/RobotVisionPlatform/tips/data/dataset-engineering.md new file mode 100644 index 0000000..f45ef21 --- /dev/null +++ b/RobotVisionPlatform/tips/data/dataset-engineering.md @@ -0,0 +1,11 @@ +# 데이터셋 엔지니어링 + +- 영상의 인접 프레임을 무작위 split하지 않습니다. clip/site/device 단위 group split을 사용합니다. +- 클래스마다 정상, 가림, 역광, 야간, motion blur, 작은 물체, 빈 장면 bucket을 관리합니다. +- 라벨 정의에는 포함/제외 예시와 최소 크기, 가림 처리 규칙을 둡니다. +- 중복 프레임을 줄이고 hard negative를 의도적으로 포함합니다. +- 증강은 실제 카메라 현상을 모사할 때만 사용하고 validation에는 적용하지 않습니다. +- dataset version에 원본 checksum, 라벨 schema, split seed, license/consent를 기록합니다. + +초기에는 전체 영상을 올리지 말고 edge에서 낮은 confidence, 규칙 위반, 주기적 unbiased sample을 선정하는 것이 비용과 개인정보 측면에서 유리합니다. + diff --git a/RobotVisionPlatform/tips/deployment/jetson-optimization.md b/RobotVisionPlatform/tips/deployment/jetson-optimization.md new file mode 100644 index 0000000..ebe8388 --- /dev/null +++ b/RobotVisionPlatform/tips/deployment/jetson-optimization.md @@ -0,0 +1,12 @@ +# Jetson 최적화 + +1. PyTorch 모델을 ONNX로 export하고 ONNX Runtime 또는 Polygraphy로 출력 parity를 확인합니다. +2. TensorRT engine은 배포 대상과 동일한 JetPack/TensorRT 환경에서 생성합니다. +3. FP16 baseline을 먼저 만들고 정확도와 메모리가 필요할 때 INT8을 검토합니다. +4. GStreamer/DeepStream의 NVMM surface를 유지해 CPU 복사를 피합니다. +5. capture, preprocess, inference, encode의 latency를 각각 측정합니다. +6. queue depth를 제한하고 실시간 영상은 backlog보다 최신 프레임을 우선합니다. +7. 고정 max clock 결과만 보고 용량을 산정하지 말고 운영 power mode에서 soak test합니다. + +DeepStream은 다중 스트림, tracker, OSD, inference plugin을 결합할 때 우선 검토하고, 단일 모델과 특수 후처리가 중심이면 직접 TensorRT adapter가 더 단순할 수 있습니다. + diff --git a/RobotVisionPlatform/tips/evaluation/edge-evaluation.md b/RobotVisionPlatform/tips/evaluation/edge-evaluation.md new file mode 100644 index 0000000..18f6e9f --- /dev/null +++ b/RobotVisionPlatform/tips/evaluation/edge-evaluation.md @@ -0,0 +1,6 @@ +# Edge 평가 + +정확도 표에는 mAP50-95, class별 precision/recall, confidence threshold를 포함합니다. 시스템 표에는 capture-to-action latency p50/p95/p99, steady-state FPS, peak memory, power mode, 온도, throttling, 네트워크 사용량, drop rate를 포함합니다. + +평가 시 cold start와 warm state를 분리하고 최소 30분 이상 열 평형 상태를 관찰합니다. 랜/Wi-Fi 정상, packet loss, 서버 단절, 재접속, 디스크 부족, 카메라 분리 시나리오를 반복합니다. 모델 후보는 정확도 하나가 아니라 이 제약의 Pareto frontier에서 선택합니다. + diff --git a/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md b/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md new file mode 100644 index 0000000..cd6dbe9 --- /dev/null +++ b/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md @@ -0,0 +1,10 @@ +# 비전 모델 학습 기초 + +## 먼저 정할 것 + +문제 유형(분류/탐지/분할/추적), 실제 행동으로 이어질 metric, 오탐과 미탐의 비용, 목표 FPS·전력·메모리를 먼저 적습니다. 장치 제약을 마지막에 고려하면 정확하지만 배포할 수 없는 모델이 나옵니다. + +transfer learning은 대규모 사전학습 weight에서 시작해 작은 learning rate로 task head와 backbone을 조정합니다. 초반에는 backbone freeze로 baseline을 만들고, 데이터가 충분하면 점진적으로 unfreeze합니다. train loss가 낮다는 이유만으로 채택하지 말고 촬영 장소/날짜/카메라가 겹치지 않는 holdout에서 비교합니다. + +과적합 신호는 train metric 개선과 validation metric 악화, 특정 배경에 대한 의존, confidence 과대입니다. 더 다양한 실제 데이터, weight decay, 적절한 augmentation, early stopping으로 대응하되 validation leakage를 먼저 의심합니다. + diff --git a/RobotVisionPlatform/tips/resources/official-resources.md b/RobotVisionPlatform/tips/resources/official-resources.md new file mode 100644 index 0000000..2d8cc36 --- /dev/null +++ b/RobotVisionPlatform/tips/resources/official-resources.md @@ -0,0 +1,17 @@ +# 공식 참고 자료 + +- [Jetson Orin Nano Developer Kit 시작 가이드](https://developer.nvidia.com/embedded/learn/get-started-jetson-orin-nano-devkit) +- [JetPack SDK](https://developer.nvidia.com/embedded/jetpack) +- [DeepStream 개발자 가이드](https://docs.nvidia.com/metropolis/deepstream/dev-guide/index.html) +- [TensorRT 문서](https://docs.nvidia.com/deeplearning/tensorrt/latest/) +- [TAO Toolkit 문서](https://docs.nvidia.com/tao/tao-toolkit/) +- [ONNX](https://onnx.ai/onnx/intro/) +- [PyTorch transfer learning tutorial](https://docs.pytorch.org/tutorials/beginner/transfer_learning_tutorial.html) +- [Ultralytics 모델 학습 문서](https://docs.ultralytics.com/modes/train/) +- [gRPC C++ 문서](https://grpc.io/docs/languages/cpp/) +- [ASP.NET Core gRPC](https://learn.microsoft.com/aspnet/core/grpc/) +- [.NET MAUI Blazor Hybrid](https://learn.microsoft.com/aspnet/core/blazor/hybrid/) +- [OpenTelemetry C++](https://opentelemetry.io/docs/languages/cpp/) + +블로그의 명령을 그대로 복사하기보다 JetPack–DeepStream 호환표와 각 릴리스 노트를 먼저 확인합니다. + diff --git a/RobotVisionPlatform/tips/training/fine-tuning-playbook.md b/RobotVisionPlatform/tips/training/fine-tuning-playbook.md new file mode 100644 index 0000000..0555492 --- /dev/null +++ b/RobotVisionPlatform/tips/training/fine-tuning-playbook.md @@ -0,0 +1,13 @@ +# Fine-tuning 실전 플레이북 + +1. pretrained checkpoint로 재현 가능한 baseline을 만든다. +2. seed, code commit, dataset version, hyperparameter, hardware를 기록한다. +3. 20~50개 샘플을 과적합시켜 데이터/라벨/코드 경로가 정상인지 확인한다. +4. input size, batch size, learning rate를 작은 sweep으로 찾는다. +5. augmentation은 하나씩 추가해 ablation한다. +6. class별 PR curve와 운영 threshold를 선택한다. +7. ONNX export 전후 output을 동일 입력으로 비교한다. +8. 실제 Jetson에서 warm-up 후 latency/전력/온도/메모리를 측정한다. + +mixed precision은 성능에 유리하지만 작은 물체나 후처리에서 수치 오차를 확인합니다. INT8은 대표 calibration set이 필요하며 정확도 회귀를 별도 승인 조건으로 둡니다. + From a9d38b2c464d3b5a1fdf5817c0f2a56a934772bd Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 19:09:29 +0900 Subject: [PATCH 2/7] make robot vision docs beginner friendly --- RobotVisionPlatform/README.md | 33 +++++- RobotVisionPlatform/docs/README.md | 4 +- .../docs/getting-started-for-beginners.md | 102 ++++++++++++++++++ RobotVisionPlatform/docs/glossary.md | 32 ++++++ RobotVisionPlatform/docs/troubleshooting.md | 50 +++++++++ .../scripts/send-demo-event.ps1 | 23 ++++ .../fundamentals/vision-training-basics.md | 11 +- 7 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 RobotVisionPlatform/docs/getting-started-for-beginners.md create mode 100644 RobotVisionPlatform/docs/glossary.md create mode 100644 RobotVisionPlatform/docs/troubleshooting.md create mode 100644 RobotVisionPlatform/scripts/send-demo-event.ps1 diff --git a/RobotVisionPlatform/README.md b/RobotVisionPlatform/README.md index e4dc4c1..ff45ad3 100644 --- a/RobotVisionPlatform/README.md +++ b/RobotVisionPlatform/README.md @@ -4,6 +4,32 @@ Jetson Orin Nano Super에서 비전 추론을 수행하고, 서버에서 장치 > 현재 단계는 **MVP 기반 골격**입니다. 합성 카메라/탐지기로 전체 파이프라인과 서버 수집을 먼저 검증하고, 실제 CSI/USB 카메라·TensorRT·WebRTC 구현을 어댑터로 교체합니다. +## 처음 보는 분은 여기부터 + +로봇 비전이나 Jetson이 처음이라면 전체 문서를 한 번에 이해할 필요가 없습니다. + +1. [초보자 시작 가이드](docs/getting-started-for-beginners.md)를 따라 서버를 실행합니다. +2. 브라우저에서 장치가 표시되는 것을 확인합니다. +3. [용어집](docs/glossary.md)에서 낯선 단어만 찾아봅니다. +4. 이후 관심 분야에 따라 `tips`, `device`, `server` 문서로 이동합니다. + +### 지금 바로 할 수 있는 것 + +- .NET 서버 실행과 health check +- 예제 탐지 이벤트 전송 +- 웹 화면에서 장치 ID, 모델 버전, 탐지 결과 확인 +- 합성 카메라를 사용하는 C++20 파이프라인 빌드와 테스트 + +### 아직 구현되지 않은 것 + +- Jetson CSI/USB 카메라의 실제 프레임 입력 +- TensorRT 모델의 실제 객체 탐지 +- WebRTC 실시간 영상 재생 +- gRPC/mTLS 장치 연결과 자동 모델 업데이트 +- VLM/VLA 분석과 로봇 동작 명령 + +이 기능들은 오류가 아니라 아래 TODO에 따라 구현할 다음 단계입니다. + ## 목표 아키텍처 ```text @@ -85,7 +111,9 @@ RobotVisionPlatform/ dotnet run --project server/src/RobotVision.Server.Api ``` -서버 실행 후 `http://localhost:5080`, health check는 `/health`, 장치 목록은 `/api/devices`입니다. 장치 데모는 별도 터미널에서 `device/build/robot_vision_device --device-id jetson-dev-001`로 실행합니다. Boost HTTP adapter를 빌드했다면 `--server-host 127.0.0.1 --server-port 5080`을 더해 MVP end-to-end 전송을 확인할 수 있습니다. +명령은 `RobotVisionPlatform` 폴더에서 실행합니다. 자세한 준비물과 예상 결과는 [초보자 시작 가이드](docs/getting-started-for-beginners.md)에 있습니다. + +서버 실행 후 `http://localhost:5080`, health check는 `/health`, 장치 목록은 `/api/devices`입니다. 장치 데모는 별도 터미널에서 실행합니다. Linux/Ninja는 `device/build/robot_vision_device`, Visual Studio generator는 보통 `device/build/Release/robot_vision_device.exe`에 생성됩니다. Boost HTTP adapter를 빌드했다면 `--server-host 127.0.0.1 --server-port 5080`을 더해 MVP end-to-end 전송을 확인할 수 있습니다. ## 설계 문서 @@ -96,6 +124,9 @@ dotnet run --project server/src/RobotVision.Server.Api - [모델 수명주기](docs/model-lifecycle.md) - [보안 및 운영](docs/security-and-operations.md) - [학습 팁 인덱스](tips/README.md) +- [초보자 시작 가이드](docs/getting-started-for-beginners.md) +- [용어집](docs/glossary.md) +- [문제 해결](docs/troubleshooting.md) ## 기술 기준 diff --git a/RobotVisionPlatform/docs/README.md b/RobotVisionPlatform/docs/README.md index b01a36b..395f23a 100644 --- a/RobotVisionPlatform/docs/README.md +++ b/RobotVisionPlatform/docs/README.md @@ -2,6 +2,9 @@ | 문서 | 목적 | |---|---| +| [getting-started-for-beginners.md](getting-started-for-beginners.md) | 처음 실행하는 사람을 위한 순서별 실습 | +| [glossary.md](glossary.md) | 프로젝트에서 사용하는 용어 설명 | +| [troubleshooting.md](troubleshooting.md) | 자주 발생하는 설치·실행 문제 | | [architecture.md](architecture.md) | 전체 구성, 경계, 데이터 흐름 | | [initial-release-guide.md](initial-release-guide.md) | v0.1.0 빌드·설치·검증·롤백 | | [jetson-deployment.md](jetson-deployment.md) | Orin Nano Super 준비와 서비스 운영 | @@ -11,4 +14,3 @@ | [adr/0001-platform-architecture.md](adr/0001-platform-architecture.md) | 주요 기술 선택 기록 | 문서는 구현과 같은 PR에서 갱신합니다. 호환성 표와 외부 링크는 릴리스마다 재검증합니다. - diff --git a/RobotVisionPlatform/docs/getting-started-for-beginners.md b/RobotVisionPlatform/docs/getting-started-for-beginners.md new file mode 100644 index 0000000..1ac9058 --- /dev/null +++ b/RobotVisionPlatform/docs/getting-started-for-beginners.md @@ -0,0 +1,102 @@ +# 초보자 시작 가이드 + +이 실습의 목표는 실제 카메라 없이도 다음 흐름을 눈으로 확인하는 것입니다. + +```text +예제 탐지 데이터 전송 -> 서버가 수신 -> 브라우저에 장치와 탐지 결과 표시 +``` + +처음에는 Jetson이 없어도 됩니다. Windows 개발 PC만으로 서버 부분을 실행할 수 있습니다. + +## 1. 준비물 + +필수: + +- Git +- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) +- PowerShell 7 또는 Windows PowerShell + +C++ 장치 프로그램까지 빌드하려면 다음 중 하나가 추가로 필요합니다. + +- Windows: Visual Studio 2022 Build Tools의 **Desktop development with C++** workload +- Ubuntu/Jetson: `build-essential`, CMake 3.22 이상, Ninja + +Docker와 MAUI는 첫 실습에는 필요하지 않습니다. + +## 2. 프로젝트 폴더로 이동 + +저장소를 받은 뒤 PowerShell에서 실행합니다. + +```powershell +cd RobotVisionPlatform +dotnet --version +``` + +`10.`으로 시작하는 버전이 보이면 준비가 된 것입니다. + +## 3. 서버 빌드와 테스트 + +```powershell +./scripts/build.ps1 +./scripts/test.ps1 +``` + +`빌드했습니다`, `오류 0개`, `Server core tests passed`가 표시되면 성공입니다. C++ compiler가 없다는 경고는 서버만 실습할 때는 무시해도 됩니다. + +## 4. 서버 실행 + +```powershell +dotnet run --project server/src/RobotVision.Server.Api +``` + +터미널이 실행 상태로 유지되는 것이 정상입니다. 브라우저에서 아래 주소를 엽니다. + +- 관제 화면: +- 상태 확인: + +처음에는 장치가 없다는 메시지가 표시됩니다. + +## 5. 예제 장치 데이터 전송 + +서버 터미널은 그대로 두고 새 PowerShell 창을 열어 같은 프로젝트 폴더로 이동한 뒤 실행합니다. + +```powershell +./scripts/send-demo-event.ps1 +``` + +다시 관제 화면을 보면 `beginner-demo-001` 장치와 `person` 탐지 결과가 나타납니다. 화면은 2초마다 새로고침됩니다. + +서버를 종료하려면 서버가 실행 중인 터미널에서 `Ctrl+C`를 누릅니다. 서버를 재시작하면 현재 장치 목록이 사라지는 것이 정상입니다. 아직 데이터베이스를 연결하지 않은 MVP이기 때문입니다. + +## 6. C++ 장치 프로그램 실행 + +Windows에서는 **Developer PowerShell for Visual Studio**를 열어 다음 명령을 사용합니다. + +```powershell +cmake -S device -B device/build +cmake --build device/build --config Release +ctest --test-dir device/build -C Release --output-on-failure +./device/build/Release/robot_vision_device.exe --device-id my-first-device +``` + +Ubuntu 또는 Jetson에서는 다음과 같습니다. + +```bash +cmake -S device -B device/build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build device/build +ctest --test-dir device/build --output-on-failure +./device/build/robot_vision_device --device-id my-first-device +``` + +현재 C++ 프로그램은 실제 카메라 대신 일정한 속도로 가짜 프레임을 만듭니다. 세 프레임마다 `demo-object` 하나를 탐지한 것처럼 출력합니다. 실제 카메라와 TensorRT 연결은 README의 Phase 1 작업입니다. + +## 다음 학습 순서 + +1. [비전 학습 기초](../tips/fundamentals/vision-training-basics.md) +2. [데이터셋 만들기](../tips/data/dataset-engineering.md) +3. [Fine-tuning 실습 순서](../tips/training/fine-tuning-playbook.md) +4. [전체 아키텍처](architecture.md) +5. [Jetson 배포](jetson-deployment.md) + +문제가 생기면 [문제 해결 문서](troubleshooting.md)를 먼저 확인합니다. + diff --git a/RobotVisionPlatform/docs/glossary.md b/RobotVisionPlatform/docs/glossary.md new file mode 100644 index 0000000..621f0eb --- /dev/null +++ b/RobotVisionPlatform/docs/glossary.md @@ -0,0 +1,32 @@ +# 용어집 + +| 용어 | 쉬운 설명 | +|---|---| +| Edge / Device | 카메라 가까이에 설치되어 영상을 직접 처리하는 Jetson 장치 | +| Server | 여러 장치의 상태와 결과를 모아 저장하고 화면에 보여주는 프로그램 | +| Model | 이미지에서 사람이나 물체를 찾도록 학습된 파일과 계산 규칙 | +| Training | 정답이 표시된 데이터로 모델을 학습하는 과정 | +| Fine-tuning | 이미 학습된 모델을 우리 데이터에 맞게 추가 학습하는 과정 | +| Inference | 학습이 끝난 모델에 새 이미지를 넣어 결과를 얻는 과정 | +| Frame | 영상 한 장면을 구성하는 한 개의 이미지 | +| FPS | 1초에 처리하는 프레임 수. 높을수록 영상이 더 부드러움 | +| Latency | 카메라 촬영부터 결과 표시까지 걸린 시간 | +| Queue | 처리할 프레임이나 이벤트가 잠시 기다리는 줄 | +| Bounded queue | 최대 크기를 제한한 대기 줄. 메모리와 지연이 무한히 늘어나는 것을 방지함 | +| Drop | 실시간성을 유지하기 위해 처리하지 못한 오래된 프레임을 버리는 것 | +| ONNX | 서로 다른 학습·추론 도구 사이에서 모델을 전달하기 위한 공통 형식 | +| TensorRT | NVIDIA GPU에서 모델 추론을 빠르게 실행하도록 최적화하는 SDK | +| DeepStream | NVIDIA GPU 기반 영상 입력·추론·추적·인코딩 파이프라인 SDK | +| NVMM / zero-copy | 영상 데이터를 CPU 메모리로 계속 복사하지 않고 GPU 쪽에서 전달하는 방식 | +| H.264 / H.265 | 영상 크기와 네트워크 사용량을 줄이는 압축 형식 | +| WebRTC | 브라우저와 앱에 낮은 지연으로 영상·음성을 전달하는 기술 | +| gRPC | 장치와 서버가 정해진 메시지 형식으로 빠르게 통신하는 기술 | +| protobuf | gRPC 메시지의 필드와 타입을 정의하는 형식 | +| SignalR | 서버가 웹/앱 화면에 변경 사항을 실시간으로 알려주는 .NET 기술 | +| VLM | 이미지·영상과 문장을 함께 이해하는 Vision-Language Model | +| VLA | 시각·언어 정보로 로봇 행동까지 결정하는 Vision-Language-Action 모델 | +| mTLS | 장치와 서버가 서로 인증서를 확인하는 양방향 암호화 연결 | +| OTA | 현장 장치를 직접 방문하지 않고 네트워크로 업데이트하는 방식 | +| Canary | 전체 장치보다 먼저 소수 장치에 새 버전을 배포해 확인하는 방식 | +| Rollback | 문제가 발생했을 때 이전 정상 버전으로 되돌리는 것 | + diff --git a/RobotVisionPlatform/docs/troubleshooting.md b/RobotVisionPlatform/docs/troubleshooting.md new file mode 100644 index 0000000..8694d07 --- /dev/null +++ b/RobotVisionPlatform/docs/troubleshooting.md @@ -0,0 +1,50 @@ +# 문제 해결 + +## `dotnet` 명령을 찾을 수 없음 + +.NET 10 **SDK**를 설치하고 새 터미널을 엽니다. Runtime만 설치하면 빌드할 수 없습니다. `dotnet --info`로 SDK 목록을 확인합니다. + +## C++ compiler를 찾을 수 없음 + +Windows에서는 Visual Studio Build Tools와 **Desktop development with C++** workload를 설치한 뒤 일반 PowerShell이 아니라 **Developer PowerShell for Visual Studio**에서 실행합니다. + +Ubuntu/Jetson: + +```bash +sudo apt-get update +sudo apt-get install -y build-essential cmake ninja-build +``` + +## CMake가 `nmake`를 찾지 못함 + +일반 PowerShell에서 MSVC 환경 변수가 설정되지 않았을 때 발생합니다. Developer PowerShell을 사용하거나 Ninja를 설치한 뒤 `-G Ninja`를 지정합니다. + +## 5080 포트를 이미 사용 중임 + +기존 서버를 `Ctrl+C`로 종료합니다. 다른 포트를 사용하려면 다음과 같이 실행합니다. + +```powershell +dotnet run --project server/src/RobotVision.Server.Api --urls http://localhost:5090 +``` + +이 경우 데모 스크립트에도 포트를 전달합니다. + +```powershell +./scripts/send-demo-event.ps1 -ServerUrl http://localhost:5090 +``` + +## 관제 화면에 장치가 표시되지 않음 + +1. 가 열리는지 확인합니다. +2. 데모 스크립트 결과가 `202`인지 확인합니다. +3. 관제 화면을 새로고침하고 2초 기다립니다. +4. 서버 재시작 후라면 데모 이벤트를 다시 보냅니다. + +## MAUI 프로젝트가 빌드되지 않음 + +MAUI는 첫 실습과 서버 실행에 필요하지 않습니다. 필요한 경우 Visual Studio의 MAUI workload 또는 `dotnet workload install maui`를 설치합니다. 기본 solution은 MAUI를 제외하므로 일반 서버 CI에는 영향을 주지 않습니다. + +## Jetson에서 Super Mode가 보이지 않음 + +구형 출하 펌웨어가 JetPack 6.x와 호환되지 않을 수 있습니다. 임의 패키지 설치보다 [NVIDIA 공식 시작 가이드](https://developer.nvidia.com/embedded/learn/get-started-jetson-orin-nano-devkit)의 펌웨어 확인 절차를 먼저 따릅니다. + diff --git a/RobotVisionPlatform/scripts/send-demo-event.ps1 b/RobotVisionPlatform/scripts/send-demo-event.ps1 new file mode 100644 index 0000000..acbc0f8 --- /dev/null +++ b/RobotVisionPlatform/scripts/send-demo-event.ps1 @@ -0,0 +1,23 @@ +param( + [string]$ServerUrl = 'http://localhost:5080', + [string]$DeviceId = 'beginner-demo-001' +) + +$ErrorActionPreference = 'Stop' +$body = @{ + deviceId = $DeviceId + sequence = 1 + modelVersion = 'demo/0.1.0' + detections = @( + @{ label = 'person'; confidence = 0.94; x = 0.10; y = 0.20; width = 0.30; height = 0.50 } + ) +} | ConvertTo-Json -Depth 4 + +$response = Invoke-WebRequest ` + -Uri "$($ServerUrl.TrimEnd('/'))/api/events/detections" ` + -Method Post ` + -ContentType 'application/json' ` + -Body $body + +Write-Host "Demo event accepted: HTTP $($response.StatusCode), device=$DeviceId" + diff --git a/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md b/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md index cd6dbe9..00bbada 100644 --- a/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md +++ b/RobotVisionPlatform/tips/fundamentals/vision-training-basics.md @@ -1,10 +1,19 @@ # 비전 모델 학습 기초 +모델 학습은 사진을 반복해서 보여주고 정답과의 차이를 줄이는 과정입니다. 한 번의 전체 데이터 학습을 `epoch`, 한 번에 처리하는 작은 데이터 묶음을 `batch`, 모델을 얼마나 크게 수정할지를 `learning rate`라고 합니다. + ## 먼저 정할 것 문제 유형(분류/탐지/분할/추적), 실제 행동으로 이어질 metric, 오탐과 미탐의 비용, 목표 FPS·전력·메모리를 먼저 적습니다. 장치 제약을 마지막에 고려하면 정확하지만 배포할 수 없는 모델이 나옵니다. transfer learning은 대규모 사전학습 weight에서 시작해 작은 learning rate로 task head와 backbone을 조정합니다. 초반에는 backbone freeze로 baseline을 만들고, 데이터가 충분하면 점진적으로 unfreeze합니다. train loss가 낮다는 이유만으로 채택하지 말고 촬영 장소/날짜/카메라가 겹치지 않는 holdout에서 비교합니다. -과적합 신호는 train metric 개선과 validation metric 악화, 특정 배경에 대한 의존, confidence 과대입니다. 더 다양한 실제 데이터, weight decay, 적절한 augmentation, early stopping으로 대응하되 validation leakage를 먼저 의심합니다. +- `weight`: 모델이 학습한 숫자 값 +- `backbone`: 이미지의 기본 특징을 찾는 앞부분 +- `head`: 특징을 이용해 클래스와 위치를 예측하는 뒷부분 +- `freeze`: 일부 weight를 바꾸지 않고 학습하는 것 +- `baseline`: 이후 실험과 비교할 가장 단순한 첫 결과 +- `loss`: 예측이 정답과 얼마나 다른지 나타내는 학습용 숫자 +- `validation/holdout`: 학습에 사용하지 않고 성능 확인에만 사용하는 데이터 +과적합 신호는 train metric 개선과 validation metric 악화, 특정 배경에 대한 의존, confidence 과대입니다. 더 다양한 실제 데이터, weight decay, 적절한 augmentation, early stopping으로 대응하되 validation leakage를 먼저 의심합니다. From f668944f362cc2db79cea59d369a37467b646b8f Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 19:40:18 +0900 Subject: [PATCH 3/7] add ONNX model deployment toolkit --- .github/workflows/robot-vision-ci.yml | 3 +- RobotVisionPlatform/.gitattributes | 6 + RobotVisionPlatform/.gitignore | 3 + RobotVisionPlatform/README.md | 1 + RobotVisionPlatform/device/README.md | 11 + .../config_infer_primary.example.txt | 27 + RobotVisionPlatform/device/guides/README.md | 32 + .../device/guides/deepstream-custom-model.md | 34 + .../device/guides/model-bundle.md | 31 + .../device/guides/onnx-workflow.md | 99 + .../device/guides/tensorrt-on-jetson.md | 63 + .../device/models/example/labels.txt | 3 + .../device/models/example/manifest.json | 29 + .../device/tools/model/README.md | 20 + .../tools/model/build_tensorrt_engine.sh | 74 + .../device/tools/model/create_manifest.py | 65 + .../device/tools/model/export_ultralytics.py | 40 + .../device/tools/model/inspect_onnx.py | 59 + .../device/tools/model/pyproject.toml | 17 + .../tools/model/tests/test_create_manifest.py | 45 + .../device/tools/model/uv.lock | 1684 +++++++++++++++++ .../device/tools/model/validate_onnx.py | 120 ++ .../docs/getting-started-for-beginners.md | 2 +- RobotVisionPlatform/scripts/build.sh | 0 RobotVisionPlatform/scripts/test.ps1 | 2 +- RobotVisionPlatform/scripts/test.sh | 2 +- .../tips/resources/official-resources.md | 4 +- 27 files changed, 2471 insertions(+), 5 deletions(-) create mode 100644 RobotVisionPlatform/.gitattributes create mode 100644 RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt create mode 100644 RobotVisionPlatform/device/guides/README.md create mode 100644 RobotVisionPlatform/device/guides/deepstream-custom-model.md create mode 100644 RobotVisionPlatform/device/guides/model-bundle.md create mode 100644 RobotVisionPlatform/device/guides/onnx-workflow.md create mode 100644 RobotVisionPlatform/device/guides/tensorrt-on-jetson.md create mode 100644 RobotVisionPlatform/device/models/example/labels.txt create mode 100644 RobotVisionPlatform/device/models/example/manifest.json create mode 100644 RobotVisionPlatform/device/tools/model/README.md create mode 100755 RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh create mode 100644 RobotVisionPlatform/device/tools/model/create_manifest.py create mode 100644 RobotVisionPlatform/device/tools/model/export_ultralytics.py create mode 100644 RobotVisionPlatform/device/tools/model/inspect_onnx.py create mode 100644 RobotVisionPlatform/device/tools/model/pyproject.toml create mode 100644 RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py create mode 100644 RobotVisionPlatform/device/tools/model/uv.lock create mode 100644 RobotVisionPlatform/device/tools/model/validate_onnx.py mode change 100644 => 100755 RobotVisionPlatform/scripts/build.sh mode change 100644 => 100755 RobotVisionPlatform/scripts/test.sh diff --git a/.github/workflows/robot-vision-ci.yml b/.github/workflows/robot-vision-ci.yml index 0a0f927..2d88ba9 100644 --- a/.github/workflows/robot-vision-ci.yml +++ b/.github/workflows/robot-vision-ci.yml @@ -20,6 +20,8 @@ jobs: run: cmake --build device/build --parallel - name: Test run: ctest --test-dir device/build --output-on-failure + - name: Test model tooling + run: python3 -m unittest discover -s device/tools/model/tests -v server: runs-on: ubuntu-24.04 @@ -33,4 +35,3 @@ jobs: dotnet-version: "10.0.x" - run: dotnet build server/RobotVision.Server.slnx --configuration Release - run: dotnet run --project server/tests/RobotVision.Server.Tests --configuration Release - diff --git a/RobotVisionPlatform/.gitattributes b/RobotVisionPlatform/.gitattributes new file mode 100644 index 0000000..86d1962 --- /dev/null +++ b/RobotVisionPlatform/.gitattributes @@ -0,0 +1,6 @@ +*.sh text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.service text eol=lf +*.proto text eol=lf + diff --git a/RobotVisionPlatform/.gitignore b/RobotVisionPlatform/.gitignore index b81ade5..017826d 100644 --- a/RobotVisionPlatform/.gitignore +++ b/RobotVisionPlatform/.gitignore @@ -7,5 +7,8 @@ device/stage/ *.user *.log .env +.venv/ +__pycache__/ secrets/ models/*.engine +device/artifacts/ diff --git a/RobotVisionPlatform/README.md b/RobotVisionPlatform/README.md index ff45ad3..dd4fd4d 100644 --- a/RobotVisionPlatform/README.md +++ b/RobotVisionPlatform/README.md @@ -127,6 +127,7 @@ dotnet run --project server/src/RobotVision.Server.Api - [초보자 시작 가이드](docs/getting-started-for-beginners.md) - [용어집](docs/glossary.md) - [문제 해결](docs/troubleshooting.md) +- [ONNX/TensorRT 모델 배포 가이드](device/guides/README.md) ## 기술 기준 diff --git a/RobotVisionPlatform/device/README.md b/RobotVisionPlatform/device/README.md index c0bdee3..53e7830 100644 --- a/RobotVisionPlatform/device/README.md +++ b/RobotVisionPlatform/device/README.md @@ -18,3 +18,14 @@ Boost.System을 포함해 빌드했다면 실행 중인 MVP 서버로 바로 전 실제 장치 구현 순서는 `ICamera`의 GStreamer/NvArgus adapter, `IDetector`의 TensorRT/DeepStream adapter, `IEventSink`의 gRPC/mTLS adapter, `IVideoPublisher`의 WebRTC adapter입니다. 코어가 CPU `pixels`를 정의하지만 Jetson adapter에서는 NVMM/CUDA handle을 별도 frame payload로 확장해 zero-copy를 유지합니다. Boost.System이 발견되면 Boost.Asio/Beast HTTP sink가 함께 컴파일됩니다. 이 sink는 서버 계약 smoke test용이며 장기 운영 경로는 공용 proto의 bidi gRPC입니다. + +## 학습 모델을 장치에 넣기 + +모델 배포는 `PyTorch → ONNX → ONNX Runtime 검증 → Jetson TensorRT engine → bundle 배포` 순서로 진행합니다. 처음에는 [Device 모델 배포 가이드](guides/README.md)를 읽으세요. + +- ONNX 검사·실행 도구: `tools/model/` +- TensorRT 변환 스크립트: `tools/model/build_tensorrt_engine.sh` +- DeepStream 설정 예제: `config/deepstream/config_infer_primary.example.txt` +- 모델 bundle 예제: `models/example/` + +현재 `DemoDetector`는 합성 결과를 만드는 테스트 구현입니다. 실제 TensorRT/DeepStream detector를 `IDetector`에 연결하는 작업은 ONNX와 전·후처리 parity가 확인된 뒤 진행합니다. diff --git a/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt b/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt new file mode 100644 index 0000000..e20627f --- /dev/null +++ b/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt @@ -0,0 +1,27 @@ +# Copy this file into a model version directory and replace every path/value. +[property] +gpu-id=0 +onnx-file=/opt/robot-vision/models/example-detector/0.1.0/model.onnx +model-engine-file=/opt/robot-vision/models/example-detector/0.1.0/model.fp16.engine +labelfile-path=/opt/robot-vision/models/example-detector/0.1.0/labels.txt +batch-size=1 +network-mode=2 +network-type=0 +num-detected-classes=2 +gie-unique-id=1 +interval=0 +model-color-format=0 +net-scale-factor=0.00392156862745098 +maintain-aspect-ratio=1 +symmetric-padding=1 +cluster-mode=2 + +# Most detectors require a model-specific output parser. Uncomment after building it. +# custom-lib-path=/opt/robot-vision/lib/libnvdsinfer_custom_robotvision.so +# parse-bbox-func-name=NvDsInferParseRobotVision + +[class-attrs-all] +pre-cluster-threshold=0.50 +nms-iou-threshold=0.45 +topk=300 + diff --git a/RobotVisionPlatform/device/guides/README.md b/RobotVisionPlatform/device/guides/README.md new file mode 100644 index 0000000..bb612fe --- /dev/null +++ b/RobotVisionPlatform/device/guides/README.md @@ -0,0 +1,32 @@ +# Device 모델 배포 가이드 + +학습한 모델을 Jetson 장치에 넣을 때는 다음 순서로 진행합니다. + +```text +PyTorch/학습 도구 + -> ONNX export + -> ONNX 구조·연산 검증 + -> PC의 ONNX Runtime 기준 결과 저장 + -> Jetson에서 TensorRT engine 생성 + -> 실제 영상으로 결과·속도·온도 검증 + -> model bundle 배포 +``` + +처음에는 아래 문서를 순서대로 읽습니다. + +1. [ONNX 실전 흐름](onnx-workflow.md) +2. [Jetson TensorRT 변환](tensorrt-on-jetson.md) +3. [모델 bundle과 manifest](model-bundle.md) +4. 여러 카메라를 처리할 때 [DeepStream custom model](deepstream-custom-model.md) + +## 어떤 실행 방식을 선택할까? + +| 상황 | 권장 방식 | 이유 | +|---|---|---| +| PC에서 ONNX가 정상인지 확인 | ONNX Runtime CPU | 설치와 재현이 간단함 | +| 단일 카메라, 특수 전·후처리 | 직접 TensorRT C++ adapter | 파이프라인을 세밀하게 제어 가능 | +| 여러 카메라, tracker/OSD/encode | DeepStream `nvinfer` | NVIDIA 영상 plugin을 조합하기 쉬움 | +| TensorRT가 지원하지 않는 일부 ONNX 연산 | ONNX Runtime TensorRT EP | 지원 node는 TensorRT, 나머지는 CUDA/CPU fallback 가능 | + +TensorRT와 DeepStream 중 하나가 항상 정답은 아닙니다. 첫 모델은 두 방식의 정확도, FPS, 지연, 메모리를 실제 Jetson에서 비교한 후 선택합니다. + diff --git a/RobotVisionPlatform/device/guides/deepstream-custom-model.md b/RobotVisionPlatform/device/guides/deepstream-custom-model.md new file mode 100644 index 0000000..4138109 --- /dev/null +++ b/RobotVisionPlatform/device/guides/deepstream-custom-model.md @@ -0,0 +1,34 @@ +# DeepStream에 custom ONNX 모델 연결 + +DeepStream은 GStreamer 위에서 카메라 입력, batch, TensorRT 추론, tracker, 화면 표시, 하드웨어 인코딩을 연결합니다. 카메라가 여러 대이거나 tracker와 video encode가 중요하면 직접 모든 단계를 구현하는 것보다 유리합니다. + +## 필요한 파일 + +- `model.onnx`: ONNX 모델 +- `labels.txt`: 클래스 번호 순서와 같은 label 목록 +- `config_infer_primary.txt`: `nvinfer` 설정 +- detector output이 표준 형식이 아니면 bounding-box parser 공유 라이브러리 + +예제는 `device/config/deepstream/config_infer_primary.example.txt`에 있습니다. + +```ini +onnx-file=/opt/robot-vision/models/person-detector/0.1.0/model.onnx +model-engine-file=/opt/robot-vision/models/person-detector/0.1.0/model.fp16.engine +labelfile-path=/opt/robot-vision/models/person-detector/0.1.0/labels.txt +network-mode=2 +``` + +`network-mode=2`는 FP16입니다. detector는 output tensor를 box/class/confidence로 바꾸는 parser가 필요할 수 있습니다. 모델별 parser 함수명과 `custom-lib-path`를 설정하지 않으면 engine 생성에는 성공해도 탐지 결과가 나오지 않을 수 있습니다. + +## 적용 순서 + +1. `trtexec`로 ONNX parsing과 단독 추론이 되는지 확인합니다. +2. DeepStream `nvinfer`만 연결해 tensor/output을 확인합니다. +3. model-specific parser와 NMS 결과를 기준 구현과 비교합니다. +4. tracker를 추가합니다. +5. OSD와 NVENC/WebRTC 출력은 마지막에 추가합니다. + +여러 문제를 한 번에 연결하면 모델 문제와 영상 pipeline 문제를 구분하기 어렵습니다. + +공식 자료: [DeepStream custom model guide](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_using_custom_model.html) + diff --git a/RobotVisionPlatform/device/guides/model-bundle.md b/RobotVisionPlatform/device/guides/model-bundle.md new file mode 100644 index 0000000..c29861a --- /dev/null +++ b/RobotVisionPlatform/device/guides/model-bundle.md @@ -0,0 +1,31 @@ +# 모델 bundle과 배포 규칙 + +모델 한 버전은 아래 파일을 함께 배포합니다. + +```text +person-detector/0.1.0/ +├── model.onnx +├── model.fp16.engine # 대상 Jetson에서 생성, Git에는 저장하지 않음 +├── labels.txt +├── manifest.json +├── preprocessing.json # 필요하면 별도 상세 설정 +└── calibration.cache # INT8일 때만 +``` + +## manifest가 필요한 이유 + +`model.onnx`만 보면 RGB/BGR, resize 방식, normalization, label 순서, threshold를 알 수 없습니다. 서로 다른 설정으로 같은 모델 파일을 실행하면 조용히 잘못된 결과가 생길 수 있습니다. manifest는 실행 코드가 시작 전에 이 조건을 검증할 수 있게 합니다. + +예제: `device/models/example/manifest.json` + +배포 과정: + +1. 서버가 manifest와 서명을 제공합니다. +2. 장치는 임시 version 폴더에 파일을 받습니다. +3. 각 SHA-256과 호환 조건을 검사합니다. +4. ONNX를 검사하고 TensorRT engine을 생성하거나 검증합니다. +5. smoke test를 통과하면 `current` symlink를 새 version으로 atomic switch합니다. +6. health guardrail 위반 시 이전 symlink로 rollback합니다. + +engine은 Git에 commit하지 않습니다. ONNX도 크기가 크면 Git LFS나 모델 registry/object storage에서 관리하고 저장소에는 manifest만 둡니다. + diff --git a/RobotVisionPlatform/device/guides/onnx-workflow.md b/RobotVisionPlatform/device/guides/onnx-workflow.md new file mode 100644 index 0000000..7cf28cf --- /dev/null +++ b/RobotVisionPlatform/device/guides/onnx-workflow.md @@ -0,0 +1,99 @@ +# ONNX 실전 흐름 + +## ONNX가 하는 일 + +ONNX는 PyTorch 같은 학습 도구에서 만든 모델을 다른 추론 도구로 옮기기 위한 공통 모델 형식입니다. 이 프로젝트에서는 다음 세 가지 목적으로 사용합니다. + +1. 학습 코드와 Jetson 실행 코드를 분리합니다. +2. 모델의 입력 이름·크기·자료형과 출력 구조를 자동 검사합니다. +3. ONNX Runtime 결과를 TensorRT 결과와 비교하는 기준으로 사용합니다. + +ONNX 파일만으로는 전처리, label 순서, confidence threshold, NMS 방식이 모두 표현되지 않을 수 있습니다. 따라서 `model.onnx`, `labels.txt`, `manifest.json`을 항상 하나의 bundle로 관리합니다. + +## 1. Python 환경 준비 + +PC에서 실행합니다. 프로젝트가 사용하는 패키지만 격리하기 위해 `uv`를 권장합니다. + +```bash +cd device/tools/model +uv sync +``` + +일반 Python 환경이라면 다음도 가능합니다. + +```bash +python -m venv .venv +# Windows: .venv\Scripts\activate +# Linux: source .venv/bin/activate +python -m pip install -e . +``` + +## 2. 학습 모델 export + +Ultralytics YOLO 계열 예시: + +```bash +uv sync --extra export +python export_ultralytics.py \ + --weights runs/detect/train/weights/best.pt \ + --output artifacts/person-detector/model.onnx \ + --image-size 640 \ + --opset 17 +``` + +`ultralytics`는 export할 때만 필요한 선택 dependency라서 기본 `uv sync`에는 설치되지 않습니다. + +`--dynamic`은 입력 크기나 batch가 실제로 바뀌어야 할 때만 사용합니다. 고정된 `1x3x640x640` 입력은 TensorRT engine 생성과 메모리 예측이 더 단순합니다. + +export 옵션은 모델 라이브러리와 TensorRT 버전에 따라 달라집니다. opset 숫자를 무조건 최신으로 올리기보다 대상 Jetson의 TensorRT parser에서 읽히는지 확인합니다. + +## 3. ONNX 구조 검사 + +```bash +python inspect_onnx.py artifacts/person-detector/model.onnx \ + --save-inferred artifacts/person-detector/model.inferred.onnx +``` + +이 명령은 ONNX checker를 실행하고 입력·출력 이름, shape, type, opset을 출력합니다. `-1`, `?`, 문자로 표시되는 차원은 실행 시 결정되는 dynamic dimension입니다. + +## 4. ONNX Runtime 추론 검증 + +```bash +python validate_onnx.py artifacts/person-detector/model.onnx \ + --provider cpu \ + --shape images=1,3,640,640 \ + --runs 10 +``` + +출력 shape, 최소/최대/평균, warm-up 이후 평균 시간이 표시됩니다. 무작위 입력은 실행 가능 여부만 검사합니다. 정확도를 검증하려면 학습 전처리를 거친 `.npy` 입력과 기대 출력을 별도 회귀 테스트로 만들어야 합니다. + +TensorRT Execution Provider가 설치된 Jetson/서버에서는 다음처럼 확인할 수 있습니다. + +```bash +python validate_onnx.py model.onnx --provider tensorrt --shape images=1,3,640,640 +``` + +provider 우선순위는 TensorRT → CUDA → CPU입니다. 일부 node가 fallback되면 성능이 예상보다 낮을 수 있으므로 verbose log나 profile로 실제 할당을 확인합니다. + +## 5. manifest 생성 + +```bash +python create_manifest.py \ + --model model.onnx \ + --model-id person-detector \ + --version 0.1.0 \ + --labels labels.txt \ + --input-name images \ + --input-shape 1,3,640,640 \ + --color RGB \ + --output manifest.json +``` + +manifest의 SHA-256은 서버와 장치가 다운로드 파일이 손상되거나 바뀌지 않았는지 확인할 때 사용합니다. 보안 배포에서는 checksum과 별도로 manifest 전자서명이 필요합니다. + +## 공식 참고 + +- [ONNX 개요](https://onnx.ai/onnx/intro/) +- [ONNX Runtime Execution Providers](https://onnxruntime.ai/docs/execution-providers/) +- [ONNX Runtime TensorRT EP](https://onnxruntime.ai/docs/execution-providers/TensorRT-ExecutionProvider.html) +- [TensorRT ONNX opset guide](https://docs.nvidia.com/deeplearning/tensorrt/latest/reference/onnx-opset-guide.html) diff --git a/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md b/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md new file mode 100644 index 0000000..54ad010 --- /dev/null +++ b/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md @@ -0,0 +1,63 @@ +# Jetson에서 TensorRT engine 만들기 + +## 왜 Jetson에서 만드는가? + +TensorRT engine(`.engine`, `.plan`)은 TensorRT 버전, GPU compute capability, builder 설정에 영향을 받습니다. PC에서 만든 engine을 그대로 복사하기보다 범용 ONNX를 배포하고 대상과 동일한 JetPack/TensorRT 환경에서 engine을 생성하는 방식을 기본값으로 사용합니다. + +## 1. 설치 확인 + +```bash +dpkg-query -W nvidia-jetpack +trtexec --version +``` + +JetPack이 설치되어도 `trtexec`가 PATH에 없으면 `/usr/src/tensorrt/bin/trtexec`를 확인합니다. + +## 2. FP16 engine 생성 + +고정 shape 모델: + +```bash +./device/tools/model/build_tensorrt_engine.sh \ + --onnx /opt/robot-vision/models/person-detector/0.1.0/model.onnx \ + --engine /opt/robot-vision/models/person-detector/0.1.0/model.fp16.engine \ + --input images \ + --shape 1x3x640x640 +``` + +dynamic shape 모델: + +```bash +./device/tools/model/build_tensorrt_engine.sh \ + --onnx model.onnx --engine model.fp16.engine --input images \ + --min-shape 1x3x320x320 \ + --opt-shape 1x3x640x640 \ + --max-shape 4x3x1280x1280 +``` + +`opt-shape`은 가장 자주 사용할 크기로 지정합니다. 최대 범위를 불필요하게 크게 잡으면 engine 생성 시간과 메모리 사용량이 늘 수 있습니다. + +## 3. benchmark 읽는 법 + +스크립트는 engine 생성 후 `trtexec --loadEngine` benchmark를 실행합니다. 확인할 값: + +- GPU Compute Time: GPU가 추론 계산에 사용한 시간 +- Host Latency: 입력 준비와 enqueue 등을 포함한 host 관점 시간 +- Throughput: 초당 처리 횟수 +- percentile: 평균뿐 아니라 p95/p99 지연 확인 + +`trtexec` 결과는 모델 단독 성능입니다. 최종 승인은 카메라 캡처, 전처리, 후처리, 인코딩을 모두 포함한 device pipeline의 end-to-end latency로 합니다. + +## 4. INT8은 나중에 적용 + +FP16을 먼저 정확도 기준선으로 만듭니다. INT8은 실제 운영 장면을 대표하는 calibration dataset, calibration cache 관리, class별 정확도 회귀 검사가 준비된 뒤 사용합니다. 단순히 `--int8`만 추가한 결과를 production에 배포하지 않습니다. + +## 5. 실패할 때 + +- `Unsupported operator`: exporter/opset을 확인하거나 TensorRT plugin/custom layer를 검토 +- dynamic input 오류: 입력 tensor 이름과 min/opt/max profile 확인 +- engine load 실패: 다른 JetPack/TensorRT/GPU에서 생성된 engine인지 확인 +- 속도가 느림: CPU fallback, 입력 복사, power mode, thermal throttling을 함께 확인 + +공식 자료: [TensorRT ONNX deployment quick start](https://docs.nvidia.com/deeplearning/tensorrt/latest/getting-started/quick-start-onnx-deployment.html), [TensorRT dynamic shapes](https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/work-with-dynamic-shapes.html) + diff --git a/RobotVisionPlatform/device/models/example/labels.txt b/RobotVisionPlatform/device/models/example/labels.txt new file mode 100644 index 0000000..fffe470 --- /dev/null +++ b/RobotVisionPlatform/device/models/example/labels.txt @@ -0,0 +1,3 @@ +person +forklift + diff --git a/RobotVisionPlatform/device/models/example/manifest.json b/RobotVisionPlatform/device/models/example/manifest.json new file mode 100644 index 0000000..628019c --- /dev/null +++ b/RobotVisionPlatform/device/models/example/manifest.json @@ -0,0 +1,29 @@ +{ + "schemaVersion": 1, + "modelId": "example-detector", + "version": "0.1.0", + "artifact": { + "file": "model.onnx", + "format": "onnx", + "sha256": "replace-with-create-manifest-output" + }, + "input": { + "name": "images", + "shape": [1, 3, 640, 640], + "layout": "NCHW", + "color": "RGB", + "dataType": "float32", + "scale": 0.00392156862745098 + }, + "labels": ["person", "forklift"], + "postprocessing": { + "kind": "model-specific", + "confidenceThreshold": 0.5, + "nmsIouThreshold": 0.45 + }, + "runtime": { + "preferred": "tensorrt-fp16", + "engineBuildLocation": "target-device" + } +} + diff --git a/RobotVisionPlatform/device/tools/model/README.md b/RobotVisionPlatform/device/tools/model/README.md new file mode 100644 index 0000000..30e1f35 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/README.md @@ -0,0 +1,20 @@ +# Model Tools + +`device/guides/onnx-workflow.md`를 자동화하는 작은 CLI 도구 모음입니다. + +```bash +uv sync +uv run python inspect_onnx.py model.onnx +uv run python validate_onnx.py model.onnx --shape images=1,3,640,640 +uv run python create_manifest.py --help +``` + +| 파일 | 역할 | +|---|---| +| `inspect_onnx.py` | ONNX checker, shape inference, 입출력 계약 출력 | +| `validate_onnx.py` | CPU/CUDA/TensorRT EP 추론과 출력 통계·평균 시간 측정 | +| `create_manifest.py` | artifact hash와 전처리 계약을 포함한 manifest 생성 | +| `export_ultralytics.py` | Ultralytics checkpoint를 ONNX bundle 경로로 export | +| `build_tensorrt_engine.sh` | Jetson에서 `trtexec` FP16 engine 생성과 benchmark | + +Ultralytics export 도구까지 설치하려면 `uv sync --extra export`를 사용합니다. 실제 모델과 생성된 TensorRT engine은 이 도구 폴더에 저장하지 말고 version별 model bundle 또는 외부 model registry에 저장합니다. diff --git a/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh b/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh new file mode 100755 index 0000000..642c358 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +onnx_path="" +engine_path="" +input_name="" +fixed_shape="" +min_shape="" +opt_shape="" +max_shape="" +trtexec_bin="${TRTEXEC_BIN:-trtexec}" + +usage() { + echo "Usage: $0 --onnx MODEL --engine ENGINE --input NAME [--shape 1x3x640x640 | --min-shape ... --opt-shape ... --max-shape ...]" +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --onnx) onnx_path="$2"; shift 2 ;; + --engine) engine_path="$2"; shift 2 ;; + --input) input_name="$2"; shift 2 ;; + --shape) fixed_shape="$2"; shift 2 ;; + --min-shape) min_shape="$2"; shift 2 ;; + --opt-shape) opt_shape="$2"; shift 2 ;; + --max-shape) max_shape="$2"; shift 2 ;; + --trtexec) trtexec_bin="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown argument: $1" >&2; usage >&2; exit 2 ;; + esac +done + +if [[ -z "$onnx_path" || -z "$engine_path" || -z "$input_name" ]]; then + usage >&2 + exit 2 +fi +if [[ ! -f "$onnx_path" ]]; then + echo "ONNX model not found: $onnx_path" >&2 + exit 2 +fi +if ! command -v "$trtexec_bin" >/dev/null 2>&1; then + echo "trtexec not found. Set TRTEXEC_BIN or pass --trtexec /usr/src/tensorrt/bin/trtexec" >&2 + exit 2 +fi + +mkdir -p "$(dirname "$engine_path")" +build_shape_args=() +run_shape_args=() +if [[ -n "$fixed_shape" ]]; then + build_shape_args+=("--shapes=${input_name}:${fixed_shape}") + run_shape_args+=("--shapes=${input_name}:${fixed_shape}") +elif [[ -n "$min_shape" && -n "$opt_shape" && -n "$max_shape" ]]; then + build_shape_args+=("--minShapes=${input_name}:${min_shape}") + build_shape_args+=("--optShapes=${input_name}:${opt_shape}") + build_shape_args+=("--maxShapes=${input_name}:${max_shape}") + run_shape_args+=("--shapes=${input_name}:${opt_shape}") +else + echo "Provide --shape or all of --min-shape, --opt-shape, --max-shape" >&2 + exit 2 +fi + +"$trtexec_bin" \ + "--onnx=$onnx_path" \ + "--saveEngine=$engine_path" \ + --fp16 \ + --skipInference \ + "${build_shape_args[@]}" + +"$trtexec_bin" \ + "--loadEngine=$engine_path" \ + --warmUp=1000 \ + --duration=10 \ + "${run_shape_args[@]}" + +echo "TensorRT engine ready: $engine_path" diff --git a/RobotVisionPlatform/device/tools/model/create_manifest.py b/RobotVisionPlatform/device/tools/model/create_manifest.py new file mode 100644 index 0000000..7699a76 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/create_manifest.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +"""Create a deterministic model manifest with SHA-256 and preprocessing metadata.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + + +def sha256(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--model", type=Path, required=True) + parser.add_argument("--model-id", required=True) + parser.add_argument("--version", required=True) + parser.add_argument("--labels", type=Path, required=True) + parser.add_argument("--input-name", default="images") + parser.add_argument("--input-shape", default="1,3,640,640") + parser.add_argument("--color", choices=("RGB", "BGR"), default="RGB") + parser.add_argument("--scale", type=float, default=1.0 / 255.0) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + dimensions = [int(value) for value in args.input_shape.split(",")] + if len(dimensions) != 4 or any(value <= 0 for value in dimensions): + parser.error("--input-shape must contain four positive dimensions, e.g. 1,3,640,640") + labels = [line.strip() for line in args.labels.read_text(encoding="utf-8").splitlines() if line.strip()] + if not labels: + parser.error("labels file is empty") + + manifest = { + "schemaVersion": 1, + "modelId": args.model_id, + "version": args.version, + "artifact": {"file": args.model.name, "format": "onnx", "sha256": sha256(args.model)}, + "input": { + "name": args.input_name, + "shape": dimensions, + "layout": "NCHW", + "color": args.color, + "dataType": "float32", + "scale": args.scale, + }, + "labels": labels, + "postprocessing": {"kind": "model-specific", "confidenceThreshold": 0.5, "nmsIouThreshold": 0.45}, + "runtime": {"preferred": "tensorrt-fp16", "engineBuildLocation": "target-device"}, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(f"manifest={args.output} sha256={manifest['artifact']['sha256']}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/RobotVisionPlatform/device/tools/model/export_ultralytics.py b/RobotVisionPlatform/device/tools/model/export_ultralytics.py new file mode 100644 index 0000000..7d43b43 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/export_ultralytics.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""Export an Ultralytics checkpoint to ONNX and copy it to a stable bundle path.""" + +from __future__ import annotations + +import argparse +import shutil +from pathlib import Path + +from ultralytics import YOLO + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--weights", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--image-size", type=int, default=640) + parser.add_argument("--opset", type=int, default=17) + parser.add_argument("--dynamic", action="store_true") + parser.add_argument("--simplify", action="store_true") + args = parser.parse_args() + + model = YOLO(str(args.weights)) + exported = Path(model.export( + format="onnx", + imgsz=args.image_size, + opset=args.opset, + dynamic=args.dynamic, + simplify=args.simplify, + )) + args.output.parent.mkdir(parents=True, exist_ok=True) + if exported.resolve() != args.output.resolve(): + shutil.copy2(exported, args.output) + print(f"onnx={args.output}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/RobotVisionPlatform/device/tools/model/inspect_onnx.py b/RobotVisionPlatform/device/tools/model/inspect_onnx.py new file mode 100644 index 0000000..469334a --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/inspect_onnx.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +"""Validate an ONNX graph and print its deployment-relevant metadata.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + +import onnx +from onnx import TensorProto, checker, shape_inference + + +def tensor_shape(value: onnx.ValueInfoProto) -> str: + tensor_type = value.type.tensor_type + dimensions: list[str] = [] + for dimension in tensor_type.shape.dim: + if dimension.HasField("dim_value"): + dimensions.append(str(dimension.dim_value)) + elif dimension.HasField("dim_param"): + dimensions.append(dimension.dim_param) + else: + dimensions.append("?") + return "x".join(dimensions) + + +def describe(kind: str, values: list[onnx.ValueInfoProto]) -> None: + for value in values: + element_type = TensorProto.DataType.Name(value.type.tensor_type.elem_type) + print(f"{kind}: name={value.name} shape={tensor_shape(value)} type={element_type}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("model", type=Path) + parser.add_argument("--save-inferred", type=Path, help="Write a shape-inferred ONNX copy") + args = parser.parse_args() + + model = onnx.load(args.model, load_external_data=True) + checker.check_model(model) + inferred = shape_inference.infer_shapes(model) + + print(f"model={args.model}") + print("opsets=" + ",".join(f"{item.domain or 'ai.onnx'}:{item.version}" for item in model.opset_import)) + print(f"nodes={len(model.graph.node)} initializers={len(model.graph.initializer)}") + initializer_names = {item.name for item in inferred.graph.initializer} + describe("input", [item for item in inferred.graph.input if item.name not in initializer_names]) + describe("output", list(inferred.graph.output)) + print("checker=ok") + + if args.save_inferred: + args.save_inferred.parent.mkdir(parents=True, exist_ok=True) + onnx.save(inferred, args.save_inferred) + print(f"shape_inferred_model={args.save_inferred}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) + diff --git a/RobotVisionPlatform/device/tools/model/pyproject.toml b/RobotVisionPlatform/device/tools/model/pyproject.toml new file mode 100644 index 0000000..0753dbf --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "robot-vision-model-tools" +version = "0.1.0" +description = "ONNX inspection, validation, export, and manifest tools for RobotVisionPlatform" +requires-python = ">=3.11" +dependencies = [ + "numpy>=1.26", + "onnx>=1.16", + "onnxruntime>=1.20" +] + +[project.optional-dependencies] +export = ["ultralytics>=8.3"] + +[tool.uv] +package = false + diff --git a/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py b/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py new file mode 100644 index 0000000..53b4b2f --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +import hashlib +import json +import subprocess +import sys +import tempfile +import unittest +from pathlib import Path + + +class CreateManifestTests(unittest.TestCase): + def test_cli_writes_hash_labels_and_input_contract(self) -> None: + tool = Path(__file__).parents[1] / "create_manifest.py" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + model = root / "model.onnx" + labels = root / "labels.txt" + output = root / "manifest.json" + model.write_bytes(b"small-test-model") + labels.write_text("person\n\nforklift\n", encoding="utf-8") + + subprocess.run( + [ + sys.executable, str(tool), + "--model", str(model), + "--model-id", "test-detector", + "--version", "1.2.3", + "--labels", str(labels), + "--input-name", "images", + "--input-shape", "1,3,320,320", + "--output", str(output), + ], + check=True, + ) + + manifest = json.loads(output.read_text(encoding="utf-8")) + self.assertEqual(manifest["modelId"], "test-detector") + self.assertEqual(manifest["labels"], ["person", "forklift"]) + self.assertEqual(manifest["input"]["shape"], [1, 3, 320, 320]) + self.assertEqual(manifest["artifact"]["sha256"], hashlib.sha256(model.read_bytes()).hexdigest()) + + +if __name__ == "__main__": + unittest.main() diff --git a/RobotVisionPlatform/device/tools/model/uv.lock b/RobotVisionPlatform/device/tools/model/uv.lock new file mode 100644 index 0000000..820bbe8 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/uv.lock @@ -0,0 +1,1684 @@ +version = 1 +revision = 3 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'win32'", + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform != 'win32'", +] + +[[package]] +name = "anyio" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/cc/a381afa6efea9f496eff839d4a6a1aed3bfafc7b3ab4b0d1b243a12573dd/anyio-4.14.2.tar.gz", hash = "sha256:cfa139f3ed1a23ee8f88a145ddb5ac7605b8bbfd8592baacd7ce3d8bb4313c7f", size = 260176, upload-time = "2026-07-12T20:29:07.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813, upload-time = "2026-07-12T20:29:05.763Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/b6/034f6802e9c3f6418966cfabb7db8c9252cc2429c5098f41cc43af804149/charset_normalizer-3.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eda059b6bc8bc0812d626fd91a7ce01bf583df0a61296eff390fd94141a34e30", size = 363585, upload-time = "2026-08-15T08:16:46.646Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/6a7e2a7c4b5451912b8c417732df79574354443592a88d616de03da66ae5/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa2bb0b37202dca27175591f761108b5d34096ade1191ffe4808bdf6b1571488", size = 251189, upload-time = "2026-08-15T08:16:48.287Z" }, + { url = "https://files.pythonhosted.org/packages/a4/c8/ab42b07cfd82e919f427fcfaa7c41abae8242833ad1aad66d42bae40b669/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b2b1b3fa5670c127b246df1d0c059defd41f689a868a3b9d79df9b1cac42d22", size = 239724, upload-time = "2026-08-15T08:16:49.67Z" }, + { url = "https://files.pythonhosted.org/packages/e7/80/b9348b5d3041209f98b4cdad7655766369233f1d533f4f4f7558e9717bec/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6e5e4d73d588ca5ed09df1b7dcd1b203d1df3c542e3f50d126c947d432b10731", size = 280078, upload-time = "2026-08-15T08:16:51.228Z" }, + { url = "https://files.pythonhosted.org/packages/82/38/083a24028304bc85bb9e376fed801178423dcbb67495f73b6ea0624e1894/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b54e7e13267d49ffbfe68e25b3cbd774dab38fa37238f71265e91b36146eb21c", size = 276650, upload-time = "2026-08-15T08:16:52.625Z" }, + { url = "https://files.pythonhosted.org/packages/0d/35/731ac04aa0a097fc1c97f0994c375bdb230c6c96619db794208fe664e9ce/charset_normalizer-3.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7b742bf31c88566b4bb6335a7f393bb322e580b6bb98df7bd0c25e6e3519ce8", size = 262325, upload-time = "2026-08-15T08:16:54.085Z" }, + { url = "https://files.pythonhosted.org/packages/f5/28/c2028e7021fb89c6e56868ed0e387b8e9aa811abdd2ab3208d6578d2c930/charset_normalizer-3.5.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6ba32c4d2abf1d2fe7cf27d280f4cca5664233b0f885549c7761719eb977f486", size = 261140, upload-time = "2026-08-15T08:16:55.604Z" }, + { url = "https://files.pythonhosted.org/packages/28/f0/0c0ceec6d98b7daa62e361e418135d59685811d79ba11529aad5cdf15e84/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0722590aabf9dc6a6c0343d523c05458fa2b5047dbe6302fd526bb570600753f", size = 252791, upload-time = "2026-08-15T08:16:57.103Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3e/48f4cd187b1c33189d86039e9cbe4f92c05454175504b44ff81806d4d1bf/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:aa1099b956fb795e686d073568f6dc002a0bb89765ea6d5b055dd7d9bf1b116c", size = 240730, upload-time = "2026-08-15T08:16:58.418Z" }, + { url = "https://files.pythonhosted.org/packages/42/85/f9e22af69af67c54cce42be9455d9c81294f918b4ccc454db01f66efcac2/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd6c173f04743d483881bffa1478d5a4624475b8cd1d2194956a75548e191c18", size = 280791, upload-time = "2026-08-15T08:16:59.918Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4c/9044135f42127630b6fa742feb51256353f6ab87a78f2fdd1de3de955a7f/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f298e218441525d3794428b4c8b8fb8662c6d3ea79925d4807ee6b9a96a3bca5", size = 259598, upload-time = "2026-08-15T08:17:01.421Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ed/1dd7cfebb4e75812934c49ca3b79757d11948053f7937ab7070c151f3c55/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6e2912d4babbc65196ac13c2f53468dc57fb8b9c25ef913e8c59ddf7c6dc0e1b", size = 278217, upload-time = "2026-08-15T08:17:02.782Z" }, + { url = "https://files.pythonhosted.org/packages/bf/eb/239c84503cc9e3ba6eb34686a24bc66e84f3924efdd7e38e751a19f6bc10/charset_normalizer-3.5.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3d27167433c0d5f18dc850f07d0b3816221984fecdc405d6c157a6f0b8f8e9e6", size = 263417, upload-time = "2026-08-15T08:17:04.216Z" }, + { url = "https://files.pythonhosted.org/packages/37/ab/4e4510e1e288478e2c8333131d1c1382382ba8cd2165053c79e39d1da961/charset_normalizer-3.5.1-cp311-cp311-win32.whl", hash = "sha256:ac00177c4831ffa650f8609e4bdddd5fe09c03b1c0c47acece7e6ea20421598b", size = 181774, upload-time = "2026-08-15T08:17:05.58Z" }, + { url = "https://files.pythonhosted.org/packages/e3/57/32f0ccea59e8612057c61d6fd22ef2cb63cca93c9fe594094919696ac170/charset_normalizer-3.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9b1e28d0e8dbfa858abdba91d6b547beaf2df1a59bec6da6faae7b96a4991a9", size = 206653, upload-time = "2026-08-15T08:17:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/17/d4/b65c433fc521e58b5f54293982a5e51c05cb5f2dd3f1c7a6acb65b75324e/charset_normalizer-3.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:ae31a1a1db2ee6cc2942fccaf695c934bc7f3db9f2133a3fef1f367cf1a4ab10", size = 185630, upload-time = "2026-08-15T08:17:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" }, + { url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" }, + { url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" }, + { url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" }, + { url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" }, + { url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" }, + { url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" }, + { url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" }, + { url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" }, + { url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" }, + { url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" }, + { url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" }, + { url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" }, + { url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" }, + { url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" }, + { url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" }, + { url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" }, + { url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" }, + { url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" }, + { url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" }, + { url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" }, + { url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" }, + { url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318, upload-time = "2026-08-15T08:18:04.403Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897, upload-time = "2026-08-15T08:18:05.997Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848, upload-time = "2026-08-15T08:18:07.397Z" }, + { url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163, upload-time = "2026-08-15T08:18:08.989Z" }, + { url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823, upload-time = "2026-08-15T08:18:10.458Z" }, + { url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458, upload-time = "2026-08-15T08:18:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717, upload-time = "2026-08-15T08:18:13.401Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111, upload-time = "2026-08-15T08:18:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128, upload-time = "2026-08-15T08:18:16.515Z" }, + { url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240, upload-time = "2026-08-15T08:18:18.127Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282, upload-time = "2026-08-15T08:18:19.625Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597, upload-time = "2026-08-15T08:18:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376, upload-time = "2026-08-15T08:18:22.57Z" }, + { url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715, upload-time = "2026-08-15T08:18:24.235Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848, upload-time = "2026-08-15T08:18:25.726Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521, upload-time = "2026-08-15T08:18:27.193Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054, upload-time = "2026-08-15T08:18:28.568Z" }, + { url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580, upload-time = "2026-08-15T08:18:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325, upload-time = "2026-08-15T08:18:31.877Z" }, + { url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175, upload-time = "2026-08-15T08:18:33.466Z" }, + { url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123, upload-time = "2026-08-15T08:18:34.913Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682, upload-time = "2026-08-15T08:18:36.349Z" }, + { url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826, upload-time = "2026-08-15T08:18:37.887Z" }, + { url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861, upload-time = "2026-08-15T08:18:39.315Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758, upload-time = "2026-08-15T08:18:40.88Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950, upload-time = "2026-08-15T08:18:42.351Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329, upload-time = "2026-08-15T08:18:43.914Z" }, + { url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137, upload-time = "2026-08-15T08:18:45.368Z" }, + { url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820, upload-time = "2026-08-15T08:18:46.842Z" }, + { url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504, upload-time = "2026-08-15T08:18:48.353Z" }, + { url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087, upload-time = "2026-08-15T08:18:49.859Z" }, + { url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269, upload-time = "2026-08-15T08:18:51.601Z" }, + { url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766, upload-time = "2026-08-15T08:18:53.23Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814, upload-time = "2026-08-15T08:18:54.768Z" }, + { url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074, upload-time = "2026-08-15T08:18:56.305Z" }, + { url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476, upload-time = "2026-08-15T08:18:57.889Z" }, + { url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115, upload-time = "2026-08-15T08:18:59.418Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048, upload-time = "2026-08-15T08:19:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997, upload-time = "2026-08-15T08:19:02.492Z" }, + { url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014, upload-time = "2026-08-15T08:19:03.943Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174, upload-time = "2026-08-15T08:19:05.598Z" }, + { url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361, upload-time = "2026-08-15T08:19:07.251Z" }, + { url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143, upload-time = "2026-08-15T08:19:08.816Z" }, + { url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086, upload-time = "2026-08-15T08:19:10.255Z" }, + { url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231, upload-time = "2026-08-15T08:19:11.807Z" }, + { url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546, upload-time = "2026-08-15T08:19:13.416Z" }, + { url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033, upload-time = "2026-08-15T08:19:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045, upload-time = "2026-08-15T08:19:16.618Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866, upload-time = "2026-08-15T08:19:18.101Z" }, + { url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932, upload-time = "2026-08-15T08:19:19.734Z" }, + { url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320, upload-time = "2026-08-15T08:19:21.37Z" }, + { url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174, upload-time = "2026-08-15T08:19:23.088Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126, upload-time = "2026-08-15T08:19:24.471Z" }, + { url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441, upload-time = "2026-08-15T08:19:26.038Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742, upload-time = "2026-08-15T08:19:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298, upload-time = "2026-08-15T08:19:29.274Z" }, + { url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500, upload-time = "2026-08-15T08:19:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888, upload-time = "2026-08-15T08:19:32.458Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243, upload-time = "2026-08-15T08:19:33.94Z" }, + { url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871, upload-time = "2026-08-15T08:19:35.413Z" }, + { url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580, upload-time = "2026-08-15T08:19:36.886Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807, upload-time = "2026-08-15T08:19:38.392Z" }, + { url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083, upload-time = "2026-08-15T08:19:39.856Z" }, + { url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317, upload-time = "2026-08-15T08:19:41.53Z" }, + { url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173, upload-time = "2026-08-15T08:19:43.07Z" }, + { url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960, upload-time = "2026-08-15T08:19:44.587Z" }, + { url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186, upload-time = "2026-08-15T08:19:46.293Z" }, + { url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947, upload-time = "2026-08-15T08:19:47.853Z" }, + { url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909, upload-time = "2026-08-15T08:19:49.383Z" }, + { url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" }, + { url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" }, + { url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" }, + { url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" }, + { url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" }, + { url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" }, + { url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" }, + { url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" }, + { url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, + { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, + { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, + { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://files.pythonhosted.org/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://files.pythonhosted.org/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://files.pythonhosted.org/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://files.pythonhosted.org/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://files.pythonhosted.org/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://files.pythonhosted.org/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://files.pythonhosted.org/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://files.pythonhosted.org/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://files.pythonhosted.org/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://files.pythonhosted.org/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://files.pythonhosted.org/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://files.pythonhosted.org/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://files.pythonhosted.org/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://files.pythonhosted.org/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://files.pythonhosted.org/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://files.pythonhosted.org/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://files.pythonhosted.org/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://files.pythonhosted.org/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, + { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, + { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, + { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, +] + +[[package]] +name = "cuda-bindings" +version = "13.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-pathfinder" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6b/457ca12dad3ee9bfcc9a545cfd6b64b359ba49de40f776f6e028e678f262/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5879712accf6e14bb01aa5e67440eb84998b8d104b509cc7a6dc0b8f656a474", size = 6053539, upload-time = "2026-05-29T23:11:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/95/7a/c5e3c34a409b148f5c0f5a4ea374158f95d488862c1dffedf9aa5c639df9/cuda_bindings-13.3.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04436a9364059c84b8f9636f359eccda1cf814341f5b670c71d80d2f79dbc708", size = 6674166, upload-time = "2026-05-29T23:11:45.478Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/5e7dba1ba576dd73da5dee894ca076ca5e959450dfff66d6d510a255d1f7/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7855c4868aabc0cfae28abbe83d56734bdfbd08f08fc234ac1912a12858bf49", size = 6025351, upload-time = "2026-05-29T23:11:49.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/6d2e9047d1fb243dbaa364b01e0297534b9ed7fd27dba1c9f361519cf69b/cuda_bindings-13.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e32d08f71ebcdf00f0f41eab2eb37e8da94c8ed411cc9f7f7a019ce6b34abe3a", size = 6657965, upload-time = "2026-05-29T23:11:52.227Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/34/c2/ef9b6a63f7dc432712a462c816662e662e00d38caa9b861c8c2588195d03/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2732904099e0a4d4db774a5fc6d91ee95fae065b4d2ecabb4968c5fe2406c9d7", size = 6476660, upload-time = "2026-05-29T23:11:59.188Z" }, + { url = "https://files.pythonhosted.org/packages/b1/81/bff68ce829999c1e4209c761bbf903b1c06ec570416ddb25020864ad5907/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ab2f74ed65bfef4163ba07a8db16f1085e0729291db12a2423aff84ee8278b8", size = 6013639, upload-time = "2026-05-29T23:12:03.509Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e0/c8a1f0c8f9ffdea4f5fe6dbab89b326cef4d85caf489dad39e209da89416/cuda_bindings-13.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd4c814d311ec08c981f6dded1dbe7d4b371067ee4f6c14cccec4bde9590f80", size = 6534419, upload-time = "2026-05-29T23:12:05.633Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/83b1f563925b290f2d11a01a77a84013ba56052fe3653a5bef3ccfbb43d6/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3c772dfff49681541d59630c90f858e173ac926b9c593a2b7123f2a1043cc76", size = 5809771, upload-time = "2026-05-29T23:12:10.422Z" }, + { url = "https://files.pythonhosted.org/packages/12/20/e79b4bfe98f075195afb6343d41c498f9dbd2d161d7021d4d28bceb83581/cuda_bindings-13.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36febb7c1079d68a981dbbd8d5a67235b399802b82075c9388624719607e52b9", size = 6358584, upload-time = "2026-05-29T23:12:12.767Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/b4/d088047afe39827556df21118cac9ffd20cc3f968c99a7681494d1eb333c/cuda_pathfinder-1.6.0-py3-none-any.whl", hash = "sha256:1503af579d8379c24bdd65528379bc57039b0455be9f5f9686cf8e473a1fce51", size = 54591, upload-time = "2026-07-21T15:03:56.224Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.3.0" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/c7/a79086a62c98befcdb8349656c6f114e2db3b8b2422f6e25c97a7f2a9a3c/cuda_toolkit-13.0.3.0-py2.py3-none-any.whl", hash = "sha256:d693caaa261214ddd7dbb60d68e71cbed884e68c2be7509778f3051da0b91c3f", size = 2512, upload-time = "2026-04-14T00:50:08.173Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +curand = [ + { name = "nvidia-curand", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cusolver = [ + { name = "nvidia-cublas", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusolver", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "platform_machine == 'aarch64' or platform_machine == 'x86_64'" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/64/a02e6765de08964ed371eca577870593245afc9dfac16d037de7c10d18e6/filelock-3.32.3.tar.gz", hash = "sha256:0ffa185a3540854c95caa7fa76b76cb219d907415e2c5dc9af25fd970563487f", size = 218135, upload-time = "2026-08-13T16:00:05.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/8e/50f46a9c0ce8d2861a394c1347caae037ea0431d2f67d7feb151cbc4649a/filelock-3.32.3-py3-none-any.whl", hash = "sha256:7f0ca4bcc0e181c60dbbd8aa9ab5b120ebb99e4e064e83636340056f833a1f09", size = 98901, upload-time = "2026-08-13T16:00:03.974Z" }, +] + +[[package]] +name = "flatbuffers" +version = "25.12.19" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661, upload-time = "2025-12-19T23:16:13.622Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/2b/a7f1545bdf5da69c4bda0cea2a5781f0ad2a6623e0277267672db43c5fe6/fonttools-4.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b8ae05d9eacf6081414d759c0a352769ac28ce31280d6bb8e77b03f9e3c449f", size = 2881793, upload-time = "2026-05-14T12:02:56.645Z" }, + { url = "https://files.pythonhosted.org/packages/49/50/965308c703f085f225db2886813b27e015b8b3438c350b22dd65b52c2a2c/fonttools-4.63.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79cdc9f567aec74a72918fd060283911406750cbc9fd28c1316023deb6ce31a9", size = 2428130, upload-time = "2026-05-14T12:02:58.891Z" }, + { url = "https://files.pythonhosted.org/packages/d8/38/6937fbd7f2dc3a6b48725851bc2c15ec949b9af14d9bbcb5fe83cdf9bdf9/fonttools-4.63.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c14b4fd138c4bafcca294765c547914e1aa431ae1ca94ab99d8db08c958bd3b", size = 5111952, upload-time = "2026-05-14T12:03:01.263Z" }, + { url = "https://files.pythonhosted.org/packages/0b/43/a81f20050a3115b57d62c8e781446949512eac36690dc384ccea65ff4cc1/fonttools-4.63.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76ac49f929aecaf82d83250b8347e099d7aecba0f4726c1d9b6df3b8bb5fe18", size = 5082308, upload-time = "2026-05-14T12:03:03.211Z" }, + { url = "https://files.pythonhosted.org/packages/67/00/cdd9d4944ca6ae280d01e69cc37bde3bf663630b837a6fc6d2cd65d80e0e/fonttools-4.63.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dcf076a4474fe0d7367e5bbf5b052c7284fa1feca729c04176ce513521afd8a0", size = 5087932, upload-time = "2026-05-14T12:03:05.147Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f1/0aa0dbea778c75adbef223c42019fd47d22262b905974d62d829545d485f/fonttools-4.63.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7dd683fef0663e9f0f45cf541d788d24caa3ec9db50796b588e1757d8b3bc007", size = 5213271, upload-time = "2026-05-14T12:03:07.238Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/253e4056e1f0e67b9390125a154b73b5eb73ad521bece95c004858fdeec2/fonttools-4.63.0-cp311-cp311-win32.whl", hash = "sha256:afefc1ed0a59785a7fb06ea7e1678e849c193e1e387db783579bc7b3056fcfcb", size = 2304473, upload-time = "2026-05-14T12:03:09.271Z" }, + { url = "https://files.pythonhosted.org/packages/08/60/defa5e69641db890a63be281f41345f4c33b157824eaf0b9fad3e08b0dcb/fonttools-4.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:063e08bd17bd5a90127a14123de0d6a952dbc847695fd98b63c043d58057f90c", size = 2356389, upload-time = "2026-05-14T12:03:11.53Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/b3c6b9b5be2f82416d73fe2ed2e96e2793cd80e7510bd6a17ca79cdd88ec/fonttools-4.63.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:37dd23e621e3b0aef1baa70a303b80aaf38449632cfc8fd2a55fb285bbccfc02", size = 2881131, upload-time = "2026-05-14T12:03:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/c815bea63117fa63e4e1c01f8a1110d2112fa003f838e6467094ec2432ce/fonttools-4.63.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9faff9e0c1f76f9fd55899d2ce785832efebab37eb8ae13995853aef178bef0", size = 2426704, upload-time = "2026-05-14T12:03:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/44/04/0b91d8e916e92ad1fac9e4624760baf0fd5ff2ead614c2f68fb21373f03f/fonttools-4.63.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef3048ef05dbb552b89817713d9cac912e00d0fde4a3105c00d29e52e10c89af", size = 5044298, upload-time = "2026-05-14T12:03:18.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/c7/2342da9830e3e9d4870305ca5d2091d2a83284f2953079b7bdd3b5e029d8/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58dc6bb86a78d782f00f9190ca02c119cf5bbe2807536e361e18d42019f877d8", size = 4999800, upload-time = "2026-05-14T12:03:20.161Z" }, + { url = "https://files.pythonhosted.org/packages/e6/6d/67fe16c48d7ce050979b33f47e0d28a318f02da030602e944c34f7a16ef3/fonttools-4.63.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee08ebfa58f6e1aeff5697ab9582105bb620008c1caafb681e4c557e7483027b", size = 4982666, upload-time = "2026-05-14T12:03:22.87Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/3bbab338c07c71fa56269953845e92c951a61457bbbb0f1022551ea266d9/fonttools-4.63.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27fdc65af8da6f88b9c6121c47a464cbe359fcfff7ff6fc2d37a1f395d755b78", size = 5133598, upload-time = "2026-05-14T12:03:25.168Z" }, + { url = "https://files.pythonhosted.org/packages/62/f2/aa27c7f98db5b064883dadcc5283947e81e034de42e22a33675878d98b54/fonttools-4.63.0-cp312-cp312-win32.whl", hash = "sha256:af2fd1664d00a397d75f806985ddb36282091c2131a73a6485c23b4a34722263", size = 2292575, upload-time = "2026-05-14T12:03:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/87/36/cccb9bc2a6ab63d1b2980374f0dca72ce95ae267c9b4cfe77455bb70d0d4/fonttools-4.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:59ac449f8cca9b4ffa08d2e7bbadad87ce710d69d1eda5c3c1ce579baa987272", size = 2343211, upload-time = "2026-05-14T12:03:30.057Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://files.pythonhosted.org/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://files.pythonhosted.org/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://files.pythonhosted.org/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://files.pythonhosted.org/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://files.pythonhosted.org/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://files.pythonhosted.org/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://files.pythonhosted.org/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://files.pythonhosted.org/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://files.pythonhosted.org/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://files.pythonhosted.org/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://files.pythonhosted.org/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://files.pythonhosted.org/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://files.pythonhosted.org/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://files.pythonhosted.org/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://files.pythonhosted.org/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://files.pythonhosted.org/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/78/f34251dadb8f3921264a1d9b8946f5e542014ee2614b285261b4e40e6775/fsspec-2026.7.0.tar.gz", hash = "sha256:c803c40f4cf860b49dea58ee3e1c33cb9c790520e233537e1340049f89b82a88", size = 317040, upload-time = "2026-07-28T16:34:51.052Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/3c/6a2bf344106328fd04963664a60b9bb6496fc25df8e962fcdc1367285fb9/fsspec-2026.7.0-py3-none-any.whl", hash = "sha256:b57ddbafedfaef7018c1ecab32aa200a9d7ca26b77965f64e48b70061249d279", size = 206583, upload-time = "2026-07-28T16:34:49.538Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/64/f9a391af28f518b11ad45a8a712353c94a0aefce09d3703200e5c54b610a/matplotlib-3.11.1.tar.gz", hash = "sha256:69647db5746941c793d6e445a4cd349323ffb87d9cc958c2ad84a659b4832d30", size = 32612045, upload-time = "2026-07-18T03:39:46.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/d0/791aa183dd88491555cf7d4be0b52b0bcf6c3c2a2c22c815a2e819bf53e2/matplotlib-3.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:b7cf158e7add54a8d51ac9b5a84abd6d4e13ed4951b4f25f1c5139f41c2addb2", size = 9440302, upload-time = "2026-07-18T03:38:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/35/74/82bbdf683a301f4478384c8aaba6903631a2ca18294b2d7655c9a542bffb/matplotlib-3.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d2ace7273b9a5061a3b420918a16fae1f2dc5dfee1abcc13aba71b5d94b1820c", size = 9268549, upload-time = "2026-07-18T03:38:06.144Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f0/9b4298911303f74e6d83e64a81d996c0616405ec95046fac7f17e4258b9e/matplotlib-3.11.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee55e9041211bf84302ab55ec3965df18dd90ae19f8b58332a7feaf208bfe83", size = 10024922, upload-time = "2026-07-18T03:38:08.236Z" }, + { url = "https://files.pythonhosted.org/packages/84/6f/0bc3c3d05b021db44c14bc379a7c0df7d57302aa15380c16fd4e63fd6a9b/matplotlib-3.11.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f4bdeea33a8d15a071dbfe6d119451b1d719c733ac666d65357082901a9099", size = 10832170, upload-time = "2026-07-18T03:38:10.276Z" }, + { url = "https://files.pythonhosted.org/packages/db/4d/e375f39acdb2af5a9342730618608e39790ec842e6f1b392863028781459/matplotlib-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b4c78ceb2f11bcac7389d305cda17aeb1f4586a857854ab5780bd3dd8dbfc407", size = 10916701, upload-time = "2026-07-18T03:38:12.512Z" }, + { url = "https://files.pythonhosted.org/packages/bc/be/fa26ed085b41298f64a8f9b7592c671bbf1acc8b0df124c1c5de96b859f8/matplotlib-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f33a781e12b1e53b278deb2f5373c2e55ec4f10727be3440c0cfb5cda9f944f", size = 9315331, upload-time = "2026-07-18T03:38:14.949Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f3/eb5bdf3b6e191b200db298b08bbc1638b7f3c82cdc8680f9d88bf72559ae/matplotlib-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:67e4c3cd578c65ebd81bdc09a1b6592ceafee6dfafe116dc85dfcb647b5bbb18", size = 9003475, upload-time = "2026-07-18T03:38:17.205Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6c/7ef7ebcb2bd9739b2b66b18b076e077f44bb46fdbe28ca0506edb3c62c79/matplotlib-3.11.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e15ef41507f3d525f46154ac9e3ae785dacde9f20e593a25de8986267892ef74", size = 9453849, upload-time = "2026-07-18T03:38:19.593Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/6d0c312c8d9738e7d9677f09fe5c986b3239e651a7b73a2deb38b65e4a71/matplotlib-3.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:21a67b961a6d597bca54fae826cd20695ba4a6e4d05424a08da6e13e3176fd6b", size = 9283113, upload-time = "2026-07-18T03:38:21.95Z" }, + { url = "https://files.pythonhosted.org/packages/c9/cf/b4ad2cc81b6672ea29ea04e64e350a9f9b493b0908ccd884c67eeff8f7b2/matplotlib-3.11.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba8f811b8ddfac493734d6af0b2dff96919d0c28ca0d641858dab4262777c6ea", size = 10035615, upload-time = "2026-07-18T03:38:24.315Z" }, + { url = "https://files.pythonhosted.org/packages/88/90/4e10e033d9b66589d8ed98b84c95cdbb57033d57c1f41339d7393dbd2f2e/matplotlib-3.11.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c52f7ad20ef476806ed212380b1d54d20310c8b86bdc2c9a68b51f0024a44472", size = 10842559, upload-time = "2026-07-18T03:38:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/88/eb/799612d0f8cd3e816a10fec59329fca52cd2353264df80378dfc541ae855/matplotlib-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b14eb22961fe865efb0e4ff167e333e428908b00115a8d800ccb65ee108e481", size = 10927532, upload-time = "2026-07-18T03:38:28.532Z" }, + { url = "https://files.pythonhosted.org/packages/88/89/56649bbaa2fd12e20f3be03dbcc135b0c8676d88bac17977599e3eb442a0/matplotlib-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88a2a27dd9691ae448dfae4b26f59036be90c3c28757edd3553a29559d00859f", size = 9333886, upload-time = "2026-07-18T03:38:30.477Z" }, + { url = "https://files.pythonhosted.org/packages/c1/11/4d124efbbad677b7b7552f6f85a3bd432d4232f95400cea98fcd2ae36ef3/matplotlib-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:480194afceca4df2f137c2721227d3cba67121fbf4397b69cee7f83714b0a58a", size = 9007545, upload-time = "2026-07-18T03:38:32.833Z" }, + { url = "https://files.pythonhosted.org/packages/04/6c/4798363b7fb5644e309fe1fac30216e9146c9f70859d80d588c18caf5317/matplotlib-3.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6771b0cd7838c6a857a7209814158c0ad09bfef878db3033dd82d70ad101f191", size = 9454341, upload-time = "2026-07-18T03:38:35.001Z" }, + { url = "https://files.pythonhosted.org/packages/59/98/6acadbe7f98df19d274bc107ac58bb439fa75df82c33dc110d71a4a8501f/matplotlib-3.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2abdee5ffa2fe11b2d19f7a5c63b785fb7c28cc46c7bc1814156341d9d1a33e1", size = 9283627, upload-time = "2026-07-18T03:38:37.061Z" }, + { url = "https://files.pythonhosted.org/packages/24/ea/65cec46fe241390ccea1b1754207ee28eb71c5ab866bd5f22fe47e538fa4/matplotlib-3.11.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0a19dcf73406d3746d25a5ed42d713604c9a3e024d129b102852b0d941cb9f3", size = 10035860, upload-time = "2026-07-18T03:38:39.663Z" }, + { url = "https://files.pythonhosted.org/packages/c7/10/63fdccccbabe002fb0960876baabc5e3f24d9c1bb4cfb25651457f74b3a0/matplotlib-3.11.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7389b77ed2ab0552f46d9a90b81b7b8e6dfcdc42adc36c37a0865799843e0e3e", size = 10843594, upload-time = "2026-07-18T03:38:42.144Z" }, + { url = "https://files.pythonhosted.org/packages/98/51/a1155945bff7b91381875022ac1522c5dfdac0d006be8e7df389b3134eae/matplotlib-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c90be0b73568da4f662afac580956a76e308437e641b4a45aa08925eeb67d95f", size = 10927962, upload-time = "2026-07-18T03:38:44.302Z" }, + { url = "https://files.pythonhosted.org/packages/0d/3a/3d5e1f42dc761bf53401a62a83ff93389b37de9d2c093b2a3aa49ac34f1b/matplotlib-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:68408341f2312836fbbdf6b3c78047f65b2d8752f5fd221c3e72d348f5b34f8b", size = 9334074, upload-time = "2026-07-18T03:38:46.616Z" }, + { url = "https://files.pythonhosted.org/packages/e2/db/3f5ea5a5b64060ef5e1ff60a19170423e41ce21b8497a6fe15a36e0b43e3/matplotlib-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c1f44890d435c1b4ef52f701ad5828cb450ea97bcc83918fda6be74965d6cd2", size = 9007662, upload-time = "2026-07-18T03:38:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/c7ae5e0531425b69c0826b00ebbc264c85cab853f1cd6e096c9983c2cdc1/matplotlib-3.11.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:5e510088c27a89d53580a752f959146893563e63c330e161d159b0fee652af6f", size = 9503790, upload-time = "2026-07-18T03:38:51.527Z" }, + { url = "https://files.pythonhosted.org/packages/92/79/15be162e0a2ed546939674e2e97d0e33ec2447d86d4d4e611fa295bb178c/matplotlib-3.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:1524e2bdd48a93557aa47ddcfe9c225dfdd57d5a01a5c49128c20f0632980ee1", size = 9336148, upload-time = "2026-07-18T03:38:53.564Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7f/36ffe144fc4aacfe0e3ed2318f72b6755d1e73b041d619b4d393e60f5a66/matplotlib-3.11.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:11664c551345553db92e61cae6cf1376f138f8c47cafdf13b64b18f3e3e9e464", size = 10049244, upload-time = "2026-07-18T03:38:55.911Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/55812d68c0a840d3a463638f48c00ab1fe338518ec49a640cb6473b444af/matplotlib-3.11.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e1f8922ba31959cf6a9dfb51be64b7f7bc582801a3957dc0c2f3afcd3537adf", size = 10860798, upload-time = "2026-07-18T03:38:58.282Z" }, + { url = "https://files.pythonhosted.org/packages/7a/64/cca444b4eb5e6c768c44fc5e1f0b5211f20ca2b282778051996e996a2bdf/matplotlib-3.11.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83235693abde86e5e0129998f80ee39fc7f58e6d56a88fafb28a9278833e9d5f", size = 10943282, upload-time = "2026-07-18T03:39:00.465Z" }, + { url = "https://files.pythonhosted.org/packages/e5/0f/a49c329d394f2e9ef38506982107e8b04ecf94dd41a9d8423ff82cc737c7/matplotlib-3.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:9a076f4fc5cdc43fdf510f5981418d25c2db4973418d9f22d8bb3dc8045ada78", size = 9383532, upload-time = "2026-07-18T03:39:02.468Z" }, + { url = "https://files.pythonhosted.org/packages/e4/50/103e86afb806d8f64d04ede14e4cfc09dbfc25f512421ff85fdd6ebd59cf/matplotlib-3.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:216fbb93a74add02ddb4cb38ef5348f59ac00b3e84567eaf16598772d40e150a", size = 9059665, upload-time = "2026-07-18T03:39:04.607Z" }, + { url = "https://files.pythonhosted.org/packages/35/04/3079499fa8cb661ea66d13d6439d5a3ae6710a7afd5c7f72e08914f275f8/matplotlib-3.11.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:30c492d4ba9448595b6fd8708c6725963f8148e25c0d8842948da5b05f0ee8d3", size = 9456022, upload-time = "2026-07-18T03:39:07.041Z" }, + { url = "https://files.pythonhosted.org/packages/53/a2/69acfe84ec1f32930e801a5782a07fc5c79c8c6599a507b806d859d5da8e/matplotlib-3.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ac104be2768ffdd8655db9e71b768cbb45f2b9aa7b450cf1595e8f65d3822319", size = 9285475, upload-time = "2026-07-18T03:39:09.562Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b3/31b15a2ca56d4ddd6aaa1c884c2f51cf9a61cfaf5ca6f6fbd6343d38e6df/matplotlib-3.11.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be943cb68bc6660ead58c55b3aa6366cba2ef7feb06460fbcce32360376f19f", size = 10847102, upload-time = "2026-07-18T03:39:11.532Z" }, + { url = "https://files.pythonhosted.org/packages/64/0d/a17e966e620545c1548125af0b29ac812dd17b197a18a7462ac12fa859ee/matplotlib-3.11.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5af0dcda57d471440a7b5b623e70e0a61003518443d9098f211a96ecfbbc25be", size = 11131087, upload-time = "2026-07-18T03:39:13.764Z" }, + { url = "https://files.pythonhosted.org/packages/97/c5/5e100efdd67abb7de20befaa333612ef9bfc63417fb71398f904f25d083c/matplotlib-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3d3fd84082b1afbd9398466c81309e20045be20d48fe0fb18c43504d164cbbb2", size = 10929036, upload-time = "2026-07-18T03:39:16.888Z" }, + { url = "https://files.pythonhosted.org/packages/ce/04/d719a0a36930ecc8dfc801ff340f9dcfc4223f8ca5d39d06b4020032fff8/matplotlib-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:9601a1e90be21e4884c53b4f3dc3ee0544654946f9975258d691f1c2e2f119c6", size = 9489571, upload-time = "2026-07-18T03:39:19.449Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/facabdc2f1f6caba7e856db64dfedddca25f7608df07d96a1c8fd114fd3b/matplotlib-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:ae30c6109848ac0f9fa36c5d6270938487614c47ba31860bd5361266dabc5685", size = 9164486, upload-time = "2026-07-18T03:39:21.424Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/18da6cd01cf96354534f98c468a25380c68ce582a2c9dd0cae12b04af4f2/matplotlib-3.11.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dadfe80797174e2984aae3be0b77594a3c72d2c0a40fbd4a0de48d2728caf3ae", size = 9504876, upload-time = "2026-07-18T03:39:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/b0/f0b63555a18b79d038c81fd6126f35fc4dfce0eaff48d96103348c7cf935/matplotlib-3.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:89b193b255f4f6f7948dbcee3691f4f341ab05d9a8874a67b45ddb4182922eda", size = 9336120, upload-time = "2026-07-18T03:39:25.797Z" }, + { url = "https://files.pythonhosted.org/packages/c6/dd/f210ec7c4a6f198d5567237048a93d0811fb5a1f1691f13320e592f95b41/matplotlib-3.11.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191163532cdefcb1571ca38a6d7e6474baccde64495783e6ba47aa07ec4b9bbb", size = 10858033, upload-time = "2026-07-18T03:39:27.999Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d2/d6d5324507c5fbb316db48e258c09c2807f3de03d9af47017e120070926f/matplotlib-3.11.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fdf1c818ab05d0e74002091ddaf414478a3a449ec9d51c8976d45be7e3a01e2", size = 11141827, upload-time = "2026-07-18T03:39:30.092Z" }, + { url = "https://files.pythonhosted.org/packages/0f/68/3c22e9320bdce2c4d2f1320643ef706db7a24cb7420eea28b97a2d67f5a8/matplotlib-3.11.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b937b9dba5f5f6c1e31c47abe2186c865c0914fd18f2ce0dfc39c9adcef5951d", size = 10943061, upload-time = "2026-07-18T03:39:32.356Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4a/907ed190ee81a9df581e0ed5456134fc0f7cb55ffcfda2f9e54ca900761c/matplotlib-3.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f2912f647f3fbe1ccf085f91e213936f9101bead81a5e670565b1f1b3712f4fb", size = 9540074, upload-time = "2026-07-18T03:39:34.789Z" }, + { url = "https://files.pythonhosted.org/packages/23/d4/97c19b77e0a6e3b48581185bb65088f431cd20186076cc0f650a1757ea46/matplotlib-3.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:54d47b8ae8b579633a3902ca5b4ad6c1e132a5626d64447b2e22a66394e79987", size = 9213472, upload-time = "2026-07-18T03:39:37.141Z" }, + { url = "https://files.pythonhosted.org/packages/ee/38/ceb1d637c4db6d06141f3739e93af3321e7caaabe69b57ae48ffe3ee95b1/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:427258425f9a3fc4ed79a91f9e9b9aaf5a82cb6571e85dc14063cc6fbb993741", size = 9438045, upload-time = "2026-07-18T03:39:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/72ad8b58602d3a6ef1dfc4b65ecd01634ab65a2bdf494c9fe0e966dbf081/matplotlib-3.11.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:1ac697e591c11b6ad04679a73c2d2f9980fe9d9f0311fb414a2e329706343dfb", size = 9266127, upload-time = "2026-07-18T03:39:41.597Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/69552382fcc8e93d1f2763ef2665980a900a48b7f3a4c57ed290726d1cbc/matplotlib-3.11.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e4b9ac2f1f607ecda2af90a5232beee2af7582fce1cc30c4b6a1b012dc21ee99", size = 10019439, upload-time = "2026-07-18T03:39:43.78Z" }, +] + +[[package]] +name = "ml-dtypes" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/72/307d7c4bd0600601c7133fba5cb78af7db968152951c1cd473abb1cda782/ml_dtypes-0.6.0.tar.gz", hash = "sha256:5e60251d32ced5598972e4d5e06a2f044341f9291402551a3f6f0ec44f9299b0", size = 3032327, upload-time = "2026-08-13T14:14:40.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/2c/318cd1a9014c63939ffe687e19559ae12831fcc37d66c71ad1f616f1ffd6/ml_dtypes-0.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f59f83c82ab480e924b988e7b1b4eb4de836dfcf5390c6f59148d1a00e1d02", size = 566813, upload-time = "2026-08-13T14:13:55.053Z" }, + { url = "https://files.pythonhosted.org/packages/d9/83/706b8a39449f0d55a7d5f7d07a169da4decfafae8a1f4983a9236d4b49e8/ml_dtypes-0.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7728c0420ec1c338564fc8b01015ff2d58567e70f17fedce5a0a7c0308c0d5b9", size = 356864, upload-time = "2026-08-13T14:13:56.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b1/135a7bf47633f5b9184f0d0316af819884124d12b40965064bd216266514/ml_dtypes-0.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c8e39b53e90afda8ce52859c93de4dba3e02b76d85dcf091cc469f9184c6dae", size = 412043, upload-time = "2026-08-13T14:13:57.614Z" }, + { url = "https://files.pythonhosted.org/packages/07/23/8870bb62d6e499d6bcbc1242b9f11689bae00a3d39d3684a9aefad8b6ee6/ml_dtypes-0.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3035518e3e19add1a4cac9236ab22888b208a4074912514313ccb2d6d242cde8", size = 433670, upload-time = "2026-08-13T14:13:59.097Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7a/5d8fbe24d0bffd0d7cb5165a89f8ab7c3de000f26d6705242aeed99d583c/ml_dtypes-0.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:5a519c9e95a216fbcb8e759793ef7fb40793fc803ed839142d6dc5be9be5bc89", size = 551915, upload-time = "2026-08-13T14:14:00.368Z" }, + { url = "https://files.pythonhosted.org/packages/84/6a/441eb053b078954f7fea284dfb288701884d0a1404d39babb858e1649023/ml_dtypes-0.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5359c588cc62de6f78d7430f06b65853d884955494d86d6ad90b6dd64a3f3a08", size = 565447, upload-time = "2026-08-13T14:14:01.737Z" }, + { url = "https://files.pythonhosted.org/packages/ed/cf/87e8a6c57eed63a91782a0d229856ddf73e138ce004dd71e2799a9dcdb33/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37da32aa97749251025666d62372775019594577b9c9e9cfda83bed48d778fdb", size = 360227, upload-time = "2026-08-13T14:14:02.938Z" }, + { url = "https://files.pythonhosted.org/packages/c7/f9/7d76c1eae866f5d4636401b31b6d6dd90e4b4ced1fa7cfdfcca9c60e4bd3/ml_dtypes-0.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b4a480aa8fd54a1805b8ac10f3f91763926a74f73c0c364c10f9231854f4170", size = 409890, upload-time = "2026-08-13T14:14:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/ba/db/9c61ec2760b5cbfb1c6558d5c991a6d8fd3271053c32db20506a9a90272b/ml_dtypes-0.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a3e9d53925597fbffafd2a37048dadeddd0bdaba58058f6ae0869ed709a184d", size = 439333, upload-time = "2026-08-13T14:14:05.501Z" }, + { url = "https://files.pythonhosted.org/packages/6a/57/780ca3e5ab135b9fbdd8e5441abf5f801b30398371b691291e05ab9834c0/ml_dtypes-0.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:6eaed129a4afe90694b8685e2f9b6294849f5eda4af9a15be83a4326eeebd775", size = 552268, upload-time = "2026-08-13T14:14:06.866Z" }, + { url = "https://files.pythonhosted.org/packages/50/51/fd1582b8f5ed8a9e7be0e161a6ea0dff70cb280479a12178df0b3a72700e/ml_dtypes-0.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:084dfe51a7ad58b171f05115f8226ed4233a454a1611371947e806e76f0c638d", size = 565468, upload-time = "2026-08-13T14:14:08.5Z" }, + { url = "https://files.pythonhosted.org/packages/d2/22/20fd70ca6ed12446cb92d5b2a7745bd185f9d8b8cdeeadad976574398e6b/ml_dtypes-0.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28d676428b104bb9717b0928bc5c5129f2d6b51b6727587cc4289e7bf8713cb5", size = 360232, upload-time = "2026-08-13T14:14:09.873Z" }, + { url = "https://files.pythonhosted.org/packages/89/a5/da8ae6c6f1babe4b68e3e55d43d39b529e29774f10e0910671a6b8c86eb8/ml_dtypes-0.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26b1f1fa4f0435a2946859823f6e2bf06796f1e9f10f5a05b08a5e3c8f46ff69", size = 410169, upload-time = "2026-08-13T14:14:11.036Z" }, + { url = "https://files.pythonhosted.org/packages/e2/55/4561acefa00fa4bcbfb82ca6a48578b41f372cd7dd7cdd6eb4720abc2e5f/ml_dtypes-0.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:fb87f46b4f7ad7b5d3ad8f4b452b024bd4229d44c8ff934798c1fe656210387a", size = 439357, upload-time = "2026-08-13T14:14:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5d/6a01538e507ef0ed5e879985b13a92467bf8960696fb1131f8b8cadc60ff/ml_dtypes-0.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:57ed0d6b4ac5e7868361303a9c57fbcf63b768236ee14456f585dfcf260d0292", size = 552278, upload-time = "2026-08-13T14:14:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7a/97dc35667b7c9db33c5344c673cd27f87e34771875ea7100138726132ac9/ml_dtypes-0.6.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:84fa136b8602c8c39e3b6cb24918960cd6f36cade7a70376f56770729cd56510", size = 562551, upload-time = "2026-08-13T14:14:14.774Z" }, + { url = "https://files.pythonhosted.org/packages/db/48/77f0ede10558d0d935da2e3276ed7e9c8cc2bad3463b9a0b66b03fc60be2/ml_dtypes-0.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:317be9967fb84b0ce4e80e6b1bf71213d21971621cf6f1e501a63602a95297bf", size = 360334, upload-time = "2026-08-13T14:14:16.079Z" }, + { url = "https://files.pythonhosted.org/packages/1c/b1/1831dd8c9b06c013085d31a2ac4f03392d43bd36bfc6ff591a08bcedc1cf/ml_dtypes-0.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f490c003369ce60e514a0c3b12374f05274c101fee1bead6740ec8a564032b0", size = 409966, upload-time = "2026-08-13T14:14:17.477Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ad/9c32c53f823dda3742df19a79c10bc198365937873ea125ba65747440c23/ml_dtypes-0.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:d574c2b28921dc72e869df248f1a278f6eee176a1f237c8642e1a71eb15f3977", size = 457224, upload-time = "2026-08-13T14:14:18.608Z" }, + { url = "https://files.pythonhosted.org/packages/41/3d/dd98205418a13353d41c52bf5326d8cbec515aace46174e23c6ea01c2978/ml_dtypes-0.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:f4adb4af61516510d786cf8c01851a66f6d3ddfa79e1144deaa5b40d8507231e", size = 568378, upload-time = "2026-08-13T14:14:19.843Z" }, + { url = "https://files.pythonhosted.org/packages/65/36/32e7beef3281fed74883451477ad976364323206dbfaa95e948ba788dac7/ml_dtypes-0.6.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3e169214e0d80ff1c038e1b3017e33c23e43bdf948d42d31de8283111c7e2fa3", size = 590177, upload-time = "2026-08-13T14:14:20.971Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/99b3d9b3c984b3bd1e81d8244f1fa2f812e44060d853205b2df6271aa17c/ml_dtypes-0.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:573b11f3c327e17ef3826d266e676cf1149a1f3016f822a05f2306c55d8246bf", size = 363142, upload-time = "2026-08-13T14:14:22.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/fb/8091c0aee7f2712de99c7fd4b1642382644dec6a4962effe4f5b9d16a973/ml_dtypes-0.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b76fa1d3f92967d58289ac47ab7458ede66e6f3527fff3e59142aee57d9307cd", size = 430645, upload-time = "2026-08-13T14:14:23.737Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/962d2c589513b5930d05b6eae5fbd22ad8bbcf26bb763449f3d8f912360f/ml_dtypes-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3be9911d953f97cddded4b9961d7b650473b7e55806d20f6176f8356dfe7b38e", size = 465667, upload-time = "2026-08-13T14:14:25.04Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/bcb25e246edd19af5fa1cf6267040bd9977a7afca846e6cfd4a52078b44f/ml_dtypes-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e74266ca8e97874a937b7646378c178025650a236584f7474d10d8086a6edea3", size = 572706, upload-time = "2026-08-13T14:14:26.296Z" }, + { url = "https://files.pythonhosted.org/packages/12/42/46cb442648e3c774d8cb25f2e1e41d496cdcc91fbe9c2a6f75c0b8df7af6/ml_dtypes-0.6.0-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:b1b503864fada3f74fabf8d9fee7b4c1cbe956301e6fdece975d5f77c2fce958", size = 562550, upload-time = "2026-08-13T14:14:27.542Z" }, + { url = "https://files.pythonhosted.org/packages/07/56/844eff5af7a2d1a09d75df12c70225c3a6b6a771f95876b2bf5f7d10ad44/ml_dtypes-0.6.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c6ad60af4102789a5c09824004beade2f7f28cd1cd581ee5c170d9dc2fbb00e", size = 360332, upload-time = "2026-08-13T14:14:28.767Z" }, + { url = "https://files.pythonhosted.org/packages/b6/29/b7165a3a76364a5baa6aa4ee82a0adf73a3c014b8cd126120b62cc087992/ml_dtypes-0.6.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4f1b9329a251e4affe3bb58f4d3e2db22a714396fd7ffb40d0b5db423c24d17", size = 409964, upload-time = "2026-08-13T14:14:30.023Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2e/f61c54a0544b6a170ac1bb89bcf406af53fb2deffc5476b6d2d3df5ba13e/ml_dtypes-0.6.0-cp315-cp315-win_amd64.whl", hash = "sha256:488c99ab181a2f59d9ec3b12c5fa11ec904e92be2c4ba18cded54dd7501208fe", size = 457249, upload-time = "2026-08-13T14:14:31.213Z" }, + { url = "https://files.pythonhosted.org/packages/63/00/bee1bc9faa02a46e7a851019fd23f47ca1f906609edbec8b6ba5decc3cc3/ml_dtypes-0.6.0-cp315-cp315-win_arm64.whl", hash = "sha256:de9d14748dbf3968951436ef514a29c9d1fe438aa680d110134ee2f7a9f9df18", size = 568381, upload-time = "2026-08-13T14:14:32.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/f7/9a5edede28f73185fd51d75030ef7f11d76997bab3a92427d986e54fe2eb/ml_dtypes-0.6.0-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:e25bb3b0ad1217b60626e4ed45b10ca170c41d99fbe44a12bebc1e07ec4aad55", size = 589877, upload-time = "2026-08-13T14:14:33.695Z" }, + { url = "https://files.pythonhosted.org/packages/fd/81/d5924a141b850b606eb027493c9c3ca3c665cca5163af3f5b6e5e3345503/ml_dtypes-0.6.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31f1ce979d31a357e95aa81812f20412c8c954fa43c44ee3ead1e1c8a78575ef", size = 362788, upload-time = "2026-08-13T14:14:34.996Z" }, + { url = "https://files.pythonhosted.org/packages/59/8f/3298e3f334832bc28dd144af6b99cdc93502a8687e71922ea68b0a319929/ml_dtypes-0.6.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2d6149f3a57f405bcad5fb41e03218b8373936253f23e1ca84c0108abbc3392", size = 430823, upload-time = "2026-08-13T14:14:36.44Z" }, + { url = "https://files.pythonhosted.org/packages/93/d2/f2dbf118f42ce4c325a139c9236737f436b7f8e00cd18701c99ef2405e6f/ml_dtypes-0.6.0-cp315-cp315t-win_amd64.whl", hash = "sha256:ce7563e0b1a4482cbc1b4a6272145e54e4489e54fe7428f94908c3d87103abfa", size = 465119, upload-time = "2026-08-13T14:14:37.776Z" }, + { url = "https://files.pythonhosted.org/packages/5a/ff/bda40387b5c5c64254595f4d81a12351770856acc5de4e6d43606a31f161/ml_dtypes-0.6.0-cp315-cp315t-win_arm64.whl", hash = "sha256:f6cb525101b6b903779188c1e9e9490c343b455ab822883e02cf01e5547338d2", size = 572666, upload-time = "2026-08-13T14:14:38.993Z" }, +] + +[[package]] +name = "mpmath" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, +] + +[[package]] +name = "networkx" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12' and sys_platform == 'win32'", + "python_full_version < '3.12' and sys_platform != 'win32'", +] +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/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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, + { 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" }, +] + +[[package]] +name = "numpy" +version = "2.5.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version == '3.13.*' and sys_platform == 'win32'", + "python_full_version == '3.12.*' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform != 'win32'", + "python_full_version == '3.13.*' and sys_platform != 'win32'", + "python_full_version == '3.12.*' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161, upload-time = "2026-08-09T13:48:27.962Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693, upload-time = "2026-08-09T13:44:51.702Z" }, + { url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109, upload-time = "2026-08-09T13:44:55.501Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202, upload-time = "2026-08-09T13:44:58.401Z" }, + { url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736, upload-time = "2026-08-09T13:45:00.813Z" }, + { url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696, upload-time = "2026-08-09T13:45:04.151Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264, upload-time = "2026-08-09T13:45:07.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396, upload-time = "2026-08-09T13:45:11.316Z" }, + { url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044, upload-time = "2026-08-09T13:45:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817, upload-time = "2026-08-09T13:45:17.867Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674, upload-time = "2026-08-09T13:45:20.734Z" }, + { url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131, upload-time = "2026-08-09T13:45:23.73Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595, upload-time = "2026-08-09T13:45:27.323Z" }, + { url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845, upload-time = "2026-08-09T13:45:31.638Z" }, + { url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880, upload-time = "2026-08-09T13:45:34.373Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264, upload-time = "2026-08-09T13:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566, upload-time = "2026-08-09T13:45:39.518Z" }, + { url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995, upload-time = "2026-08-09T13:45:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511, upload-time = "2026-08-09T13:45:47.094Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609, upload-time = "2026-08-09T13:45:50.808Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204, upload-time = "2026-08-09T13:45:54.111Z" }, + { url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532, upload-time = "2026-08-09T13:45:57.167Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725, upload-time = "2026-08-09T13:46:00.478Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180, upload-time = "2026-08-09T13:46:03.939Z" }, + { url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878, upload-time = "2026-08-09T13:46:07.045Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922, upload-time = "2026-08-09T13:46:10.04Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168, upload-time = "2026-08-09T13:46:12.275Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501, upload-time = "2026-08-09T13:46:15.322Z" }, + { url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701, upload-time = "2026-08-09T13:46:18.949Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065, upload-time = "2026-08-09T13:46:23.006Z" }, + { url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031, upload-time = "2026-08-09T13:46:27.102Z" }, + { url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028, upload-time = "2026-08-09T13:46:30.046Z" }, + { url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627, upload-time = "2026-08-09T13:46:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414, upload-time = "2026-08-09T13:46:36.036Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967, upload-time = "2026-08-09T13:46:39.084Z" }, + { url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874, upload-time = "2026-08-09T13:46:42.075Z" }, + { url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276, upload-time = "2026-08-09T13:46:44.387Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154, upload-time = "2026-08-09T13:46:47.538Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909, upload-time = "2026-08-09T13:46:51.442Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685, upload-time = "2026-08-09T13:46:55.095Z" }, + { url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181, upload-time = "2026-08-09T13:46:59.09Z" }, + { url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085, upload-time = "2026-08-09T13:47:01.903Z" }, + { url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971, upload-time = "2026-08-09T13:47:04.963Z" }, + { url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306, upload-time = "2026-08-09T13:47:07.976Z" }, + { url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274, upload-time = "2026-08-09T13:47:11.439Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846, upload-time = "2026-08-09T13:47:15.128Z" }, + { url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892, upload-time = "2026-08-09T13:47:18.07Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309, upload-time = "2026-08-09T13:47:20.456Z" }, + { url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850, upload-time = "2026-08-09T13:47:24.365Z" }, + { url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664, upload-time = "2026-08-09T13:47:27.965Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749, upload-time = "2026-08-09T13:47:31.541Z" }, + { url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495, upload-time = "2026-08-09T13:47:35.364Z" }, + { url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696, upload-time = "2026-08-09T13:47:38.561Z" }, + { url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324, upload-time = "2026-08-09T13:47:41.401Z" }, + { url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466, upload-time = "2026-08-09T13:47:44.873Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947, upload-time = "2026-08-09T13:47:48.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331, upload-time = "2026-08-09T13:47:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336, upload-time = "2026-08-09T13:47:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387, upload-time = "2026-08-09T13:47:58.192Z" }, + { url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096, upload-time = "2026-08-09T13:48:01.562Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730, upload-time = "2026-08-09T13:48:05.706Z" }, + { url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686, upload-time = "2026-08-09T13:48:09.627Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727, upload-time = "2026-08-09T13:48:13.744Z" }, + { url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775, upload-time = "2026-08-09T13:48:17.543Z" }, + { url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559, upload-time = "2026-08-09T13:48:21.023Z" }, + { url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103, upload-time = "2026-08-09T13:48:24.21Z" }, +] + +[[package]] +name = "nvidia-cublas" +version = "13.1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cuda-nvrtc" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, + { url = "https://files.pythonhosted.org/packages/3b/cd/154ca20c38269e05eff77c1464e6c1da89f50a6390b565e9d82e06bc11e1/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:37936a16db8fe4ac1f065c2139360608a543a09275cb1a1af612e08cfa065436", size = 423138758, upload-time = "2026-04-08T18:46:58.655Z" }, +] + +[[package]] +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, +] + +[[package]] +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, +] + +[[package]] +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, +] + +[[package]] +name = "nvidia-cudnn-cu13" +version = "9.20.0.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/5e/edb9c0ae051602c3ccaffe424256463636d639e27d7f302dde9975ef9e7a/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0c45dd8eeb50b603f07995b1b300c62ffe6a1980482b82b3bcf94a4ca9d49304", size = 366173588, upload-time = "2026-03-09T19:29:34.474Z" }, +] + +[[package]] +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, +] + +[[package]] +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, +] + +[[package]] +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, +] + +[[package]] +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-cublas" }, + { name = "nvidia-cusparse" }, + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, +] + +[[package]] +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nvidia-nvjitlink" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, +] + +[[package]] +name = "nvidia-cusparselt-cu13" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/e1/cdc1797eadf82d3a9a575a19b33fdc871a97edbec42c00b5b5e914f4aff4/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4dca476c50bf4780d46cd0bfbd82e2bc10a08e4fef7950917ce8d7578d22a23f", size = 221051344, upload-time = "2025-09-05T18:49:51.289Z" }, + { url = "https://files.pythonhosted.org/packages/34/7d/2661f2fb3ac4302f3a246f5fc030213ac60c1fe0bce84f9783dbd831dbb7/nvidia_cusparselt_cu13-0.8.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:786ce87568c303fadb5afcc7102d454cd3040d75f6f8626f5db460d1871f4dd0", size = 170148586, upload-time = "2025-09-05T18:50:50.248Z" }, +] + +[[package]] +name = "nvidia-ml-py" +version = "13.610.43" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/b5/a8fbc356f768fa5c9cfd646668fd7d34bf55bdd1c6e20754642a64d930d4/nvidia_ml_py-13.610.43.tar.gz", hash = "sha256:65437eb73d68d0c62c931ca4d45038472faff03bd0b8729abba4b899f70d60f2", size = 52109, upload-time = "2026-06-01T18:54:08.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/45/caa600acfab94560807a20a64b5830d2cd3c3202b7f1328644d70b7d6bd8/nvidia_ml_py-13.610.43-py3-none-any.whl", hash = "sha256:f13c72698edef492f985cc225f14faafe68ae065a2e407f45bdf6f4b9b43fde8", size = 53163, upload-time = "2026-06-01T18:54:07.704Z" }, +] + +[[package]] +name = "nvidia-nccl-cu13" +version = "2.29.7" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/0d/daf50d44177ee0cbc7ff0a0c91eb5ff676c82be42f9a970bc7597f440c3a/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:674a12383e3c38a1bcccae7d4f3633b37852230b6047883cb2f4c2d1b36d9bf5", size = 206014712, upload-time = "2026-03-03T05:34:20.843Z" }, + { url = "https://files.pythonhosted.org/packages/67/f4/58e4e91b6919367c7aafb8e36fce9aad1a3047e536bf7e2fd560927d3a4c/nvidia_nccl_cu13-2.29.7-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:edd81538446786ec3b73972543e53bb43bcaf0bfc8ef76cb679fcc390ffe136d", size = 205976000, upload-time = "2026-03-03T05:36:24.472Z" }, +] + +[[package]] +name = "nvidia-nvjitlink" +version = "13.3.33" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f0/ee/580ca6f29dcab0221db8706badca1bbbb084f1975c4d4e83329c3a7e31f0/nvidia_nvjitlink-13.3.33-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:26a6de7fb4c8fdaa7703d3dad720d6d427ddfea5c48a528fd97c11733ad830e5", size = 40742423, upload-time = "2026-05-26T16:54:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/69/30/45414e35ff2eee7db3da037e5707037ccf9d2b5218ffbdb055ea4d5aa98a/nvidia_nvjitlink-13.3.33-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ce48b37dfeb3cb1eae4cf85adacb47d7a6539ea2272870c9a3628ce275c2037e", size = 39168635, upload-time = "2026-05-26T16:54:13.906Z" }, +] + +[[package]] +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, +] + +[[package]] +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, +] + +[[package]] +name = "onnx" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/19/8ea73a64b368b75fe339771a20a02bc61ea1f551484c9e3d9d0bfbd0450f/onnx-1.22.0.tar.gz", hash = "sha256:ef40c0aaf0b643857ea9306fc7eddce17eaf9fb0407e4801f1fc5758443a38e0", size = 12024721, upload-time = "2026-06-15T12:50:05.354Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/55/30825c02c92a0380ce84c3feeeec95d329fa77548ba58cb10ad4bbfd83c6/onnx-1.22.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:2d8f229a553fa440fe623ed7b36fca5e7762da3af871c3f8f8ce451df73e2914", size = 20167891, upload-time = "2026-06-15T12:49:14.212Z" }, + { url = "https://files.pythonhosted.org/packages/4b/24/cd4ab52ecaf41c3fbed674772ccbfe39041cb257b8471a47a37e48bff3f8/onnx-1.22.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1a89a7cb9ba13d78f009bdec448ec82a98972589734f157022a2bff7a5973a6", size = 18892720, upload-time = "2026-06-15T12:49:16.904Z" }, + { url = "https://files.pythonhosted.org/packages/2b/a0/c9d9d56ceadb1c0a90a7cbec5a0510520ab6538938944fa84548e4b5b054/onnx-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1d0a2bdb15eb2b3cb65c438f3423d9620d14fdce32f92380e6bb1b2e09568ef5", size = 19110720, upload-time = "2026-06-15T12:49:19.812Z" }, + { url = "https://files.pythonhosted.org/packages/0a/6e/e43e5a68d9cadde55df75310027f87127333a77e5ddcea14c73e96a10cac/onnx-1.22.0-cp311-cp311-win32.whl", hash = "sha256:239958534464612fbcb6ed23d5228aaa925b39b8773f58726809ffdccb4edd1c", size = 17083746, upload-time = "2026-06-15T12:49:22.935Z" }, + { url = "https://files.pythonhosted.org/packages/54/57/cc0a9f2cf4522e42829d089927b4b75924d32f50dca237482e7b741df003/onnx-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:8561a2c00041c07e08db0c228593b5b4694100398685f348532af7dbb84189da", size = 17215684, upload-time = "2026-06-15T12:49:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/c9/99/0f049f9eaa06c8383060c5f0a338e3a6caac8822e6e326c9162f05abf95a/onnx-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:8907b9b9389893bc0dc6314cc00ee1e3a69844e48d689eacc6a0340411a7da58", size = 17210398, upload-time = "2026-06-15T12:49:29.091Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6a/481561f1093834376ed493e4ca42a73e5be0d50031f2969c86593bdc7c96/onnx-1.22.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:596fbf0490947533c1c1045ba860851dc9fb77471023dac9a71ba5b42ceab103", size = 20167081, upload-time = "2026-06-15T12:49:32.078Z" }, + { url = "https://files.pythonhosted.org/packages/84/55/b34fc2aa30aa54b4a775402d24c4082242c720283a274fe976ac8eb94480/onnx-1.22.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae5a563f281cd9d2845622cecf6c092a57e4ee1b138f66fdbbdd4200567a5e16", size = 18889249, upload-time = "2026-06-15T12:49:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/09/a6/bd32357e6cc1ecb473afd78193d7231724f284435d2db25696ecfaaa1503/onnx-1.22.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:955e02e1f6d385b53d52f9cd7b9cdf5caf417c300bcfe3c64c6d542be763845b", size = 19106514, upload-time = "2026-06-15T12:49:37.424Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9d/3af461ac6c714b8b369cb71499659932f4f12cfb066250b62f7567c3d530/onnx-1.22.0-cp312-abi3-pyemscripten_2025_0_wasm32.whl", hash = "sha256:82e9f27fc1223cb06d68a56bed6f9d3caf3d0dad1b61bce45006d529b15bd94c", size = 16966387, upload-time = "2026-06-15T12:49:40.918Z" }, + { url = "https://files.pythonhosted.org/packages/d0/f0/68195b5e5a53e333faf2660f5352ee43738d0e42fc5216cc6b1871a9fbfb/onnx-1.22.0-cp312-abi3-win32.whl", hash = "sha256:cc8b66b312f8f03a53e268afb67180a2d97dd12cc79e2b61361c6c0073448016", size = 17081568, upload-time = "2026-06-15T12:49:43.398Z" }, + { url = "https://files.pythonhosted.org/packages/13/a8/734725bb703c5fabb687f79c79e51249475212b3eb37771ac4a4ac9b487f/onnx-1.22.0-cp312-abi3-win_amd64.whl", hash = "sha256:72ccebab3bac07215c204ce8848d42e78eaaa666badbf72d25cd359b9f269e3a", size = 17213290, upload-time = "2026-06-15T12:49:45.933Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/8ce48d8ae26a8761ad4e5dc771961b155c5c3c7c8540ec7f2f2d71b69af0/onnx-1.22.0-cp312-abi3-win_arm64.whl", hash = "sha256:f3c120dcdb70ad738f3c061b32798f408ea299eb69f84dd69ab4a6bf3c2ec01f", size = 17207030, upload-time = "2026-06-15T12:49:48.635Z" }, + { url = "https://files.pythonhosted.org/packages/f3/13/47323b97846387848efb1044ded11bb94b83526f3d1fbdb37c6480d4520f/onnx-1.22.0-cp314-cp314t-macosx_12_0_universal2.whl", hash = "sha256:19e45e4af88e3fe3261458d4b8cc461957ae2782a358a3560503569bf3b23b72", size = 20176465, upload-time = "2026-06-15T12:49:51.311Z" }, + { url = "https://files.pythonhosted.org/packages/13/0c/d3b8a7e7eee123938586c608bb9894b5723f2342b9450c0eec59fbec7099/onnx-1.22.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c21a0e59fd967a95b358e4a6e756d1f1eec2d304a83480f329f66e30d2bf0223", size = 18894028, upload-time = "2026-06-15T12:49:54.451Z" }, + { url = "https://files.pythonhosted.org/packages/b8/8a/da2a97ab46fe6e0cd9beb3ac14603a22f5be492f9ca347faf8233a07bb33/onnx-1.22.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2632406b8f523ef2e2873c363f90b20a3d88c0fbcfac757d3addffccf8f452c2", size = 19110420, upload-time = "2026-06-15T12:49:57.665Z" }, + { url = "https://files.pythonhosted.org/packages/b9/a3/ce984063017518307ebfaa545782fc400e593dc2d7fdf4f23ce4be1ed197/onnx-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a3a39fc4643867aecb33417fdddb11e308ee79d2d4a584b9d50cc7aec2091b13", size = 17237547, upload-time = "2026-06-15T12:50:00.382Z" }, + { url = "https://files.pythonhosted.org/packages/00/50/257a880384a1dd502d543b0067945074d63cd17d0840e958355bc8197da8/onnx-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:8e268cdc0547e3949799ffd4a44451dc2b9080b57d0824a2db680b6ec65506f0", size = 17231391, upload-time = "2026-06-15T12:50:03.047Z" }, +] + +[[package]] +name = "onnxruntime" +version = "1.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flatbuffers" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, + { name = "protobuf" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/4d/5014667e2a3a77d6e1b74cc3d88948d06163b8e0a33a84c85073322b5dec/onnxruntime-1.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f5c5daabd28aad610f83fdcf32acec8fb57e6adc6c6a39fe2a3c755db957b410", size = 19130506, upload-time = "2026-07-25T01:22:34.489Z" }, + { url = "https://files.pythonhosted.org/packages/ea/97/b7ce1bc8bb6048b5fe9129f55d6506dc19499068ef2e0a0af1ae3c8aa4e7/onnxruntime-1.28.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8d66f9ceb29909c70839e4e4fb3435c7b490050d8f162bd5f3aba4ca01ee517f", size = 17039880, upload-time = "2026-07-25T01:21:37.538Z" }, + { url = "https://files.pythonhosted.org/packages/f3/17/4e5ecd8764f87573c495d834ce79e61ecca47f7a01d1e444a606e570edcb/onnxruntime-1.28.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a166b78ee04f3a37fa1ef82034b6a3ce96d9684e582d4d30b296de83e9998bb5", size = 19193162, upload-time = "2026-07-25T01:21:59.151Z" }, + { url = "https://files.pythonhosted.org/packages/9f/10/3d946d5d5f2cdcc3c8da36cae63190c516d16349edaffd944bda60ca4c3e/onnxruntime-1.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:0d650aeee29368414367b65529e90afe4bf1bab76254789063b8b2f7ea3013c8", size = 13752539, upload-time = "2026-07-25T01:22:24.524Z" }, + { url = "https://files.pythonhosted.org/packages/8f/74/1c440be7af1e026280b139caa1be5d11bd4dc368011ddbe8f5362b58e12f/onnxruntime-1.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:0faf85fb447a663c9cdadc39bd6b19bdf7bedded6699e45731b9b36c46fd993d", size = 13449940, upload-time = "2026-07-25T01:22:14.97Z" }, + { url = "https://files.pythonhosted.org/packages/98/f8/dcbe7700dca82fa540035abd3c868fe5ad0f86af00b9a3db7c2e27d15c7d/onnxruntime-1.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:26ff0fdd06efb6c155bae95387a09db1a2be89c7a03e4d0bffd5a171cc2826da", size = 19141362, upload-time = "2026-07-25T01:22:36.965Z" }, + { url = "https://files.pythonhosted.org/packages/28/5b/1d77e62097fdbe07e2dc827f389b1c4c0c275f6fab0369a8f46d2461af27/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e81a23df16e7acb9d51b06d30cc098e49315ef9180f97bc2221d167b4b04d9c", size = 17050628, upload-time = "2026-07-25T01:21:40.481Z" }, + { url = "https://files.pythonhosted.org/packages/95/df/5486ab03e9be288d5268867054c8b04bebcf95bfd12e801c05cc67703dab/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a83bdb70d143cede762b677789bf2a7acca54b3fb82565601d5c30695aa933c", size = 19214257, upload-time = "2026-07-25T01:22:01.695Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3b/986ca67c274932ba9ac5332fb10de56f643dfd433c74e33f8ae8f847cf24/onnxruntime-1.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:c35064f9b3c43c81c5d5d282091401d0f1ff22796d93ccade4ea2ece5e137ab8", size = 13755036, upload-time = "2026-07-25T01:22:26.89Z" }, + { url = "https://files.pythonhosted.org/packages/1d/46/059dba81d46c6ba88e0c2d1c64321ac8098847d678423300a183d42ecbd6/onnxruntime-1.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:e02feeb0165c5f13b4cc954738078d59b90128516ac12b671ee24a530242bf02", size = 13454462, upload-time = "2026-07-25T01:22:17.38Z" }, + { url = "https://files.pythonhosted.org/packages/9c/12/3807e2b17d9eb71d3cb78ed2ba76869b05c637c9b9d6112e636098b0c97a/onnxruntime-1.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:31410f544674f534c2f27348af52ef81682ca9c8719154bf4d48f0ef23823b1e", size = 19141759, upload-time = "2026-07-25T01:21:53.765Z" }, + { url = "https://files.pythonhosted.org/packages/c0/23/b46045c3bf67a9cf54c12f5df0f018a422c65fbb9d6072b10071bebfaae2/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f649dd6f6452d12a8059888aa489fe519e062e18793dac72b9efa0f9fdb64135", size = 17049339, upload-time = "2026-07-25T01:21:43.005Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/8c5396e7894e77c5a7d1e026f3acb9dd39c4b5644e412e37a0055eaa3bc5/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fa221d669282bd8f582708ce4c96010a7e9fb0661f9006b37fe2fedafb73fe", size = 19214329, upload-time = "2026-07-25T01:22:04.133Z" }, + { url = "https://files.pythonhosted.org/packages/56/f1/51225c202edba4dfc94e1ea03f3d78f1aaf307da75fd792c0ce1946b2514/onnxruntime-1.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:1a1a19175464665c9b8d50bc916f216cc0b569110045b7bbca8f9f290b186f58", size = 13755033, upload-time = "2026-07-25T01:22:29.302Z" }, + { url = "https://files.pythonhosted.org/packages/f4/db/f59f715edfdd96a051f32b5ef0e680a20a8755d4ecd75f63090e960e347a/onnxruntime-1.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:cfab507abe09d6ffeb817eee07944d452fdc0b00fdcef34cab4db10a45e378c7", size = 13454175, upload-time = "2026-07-25T01:22:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/47/28/810314fa88647af9f4cdaf438a30ad1cfebebb53ded55499232d7a0094e6/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac301f53b1930402fc46c368e268acfed02f3207272aaff05070d7e09f96f031", size = 17057307, upload-time = "2026-07-25T01:21:45.492Z" }, + { url = "https://files.pythonhosted.org/packages/3d/cc/9e9f193cc0f29f263a8f09ec08487aed6c96ee856d5fd77da32a425c1949/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f022a1103cae591c75fc4565589a515f2ddd14a6ac8e8a05812dfeda142e28", size = 19222954, upload-time = "2026-07-25T01:22:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/4e/eb/952314c451d9463e5c9aed9978eec76cf32930d407d9ab8700dd0f4ea1ea/onnxruntime-1.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:8adff67a3f28257b37cfe945a7e952e4122666aa8c91a0380862e9fd4c2ed19f", size = 19143748, upload-time = "2026-07-25T01:21:56.297Z" }, + { url = "https://files.pythonhosted.org/packages/3f/e9/139180b4dd810329aaa42c238b4e6383c906202d98609ae29d66eb7c32b1/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc2565e487b4896fb988d6383577d875d958e071fc5f6c3550bd5d02ae98264b", size = 17051950, upload-time = "2026-07-25T01:21:48.606Z" }, + { url = "https://files.pythonhosted.org/packages/03/88/9432428273356ad3c8aa01f52c1b3e7f53c4c0192748f41ad983872b436b/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6afdc83f1317c136e92fc29f5ee9f058de59d87c0b22cee3fdbfbaa0ccc2098a", size = 19214924, upload-time = "2026-07-25T01:22:09.727Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e2/6feb3a43517aaf2b1bf7e46897ba5eb81a29717f7d7901420614d5ee4653/onnxruntime-1.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:f2a3b9e30ce880d4ca54999cb313569e36da4f62eefe25f87be18f43e9a3a4d5", size = 14093738, upload-time = "2026-07-25T01:22:31.629Z" }, + { url = "https://files.pythonhosted.org/packages/fc/8f/83974a1e201dc2e58e5e7111bcaeb1ca2413e9c41f505d26419ee9e3dddf/onnxruntime-1.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:07fb3cbe990d6bf0ab3c22bfbbfb0e314151266046ea6edb4a07f556b4258c5f", size = 13821117, upload-time = "2026-07-25T01:22:22.387Z" }, + { url = "https://files.pythonhosted.org/packages/0d/83/00e606bc25c756d76a267370c39b7516ad52f9cf134d7ff2bff8b6108bc4/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e562d6e36a749f6764481c0ddb0f2af3d0b5a3c164291361d08803c557f369af", size = 17055518, upload-time = "2026-07-25T01:21:51.08Z" }, + { url = "https://files.pythonhosted.org/packages/94/a9/68707e1ce345cbdbcd4df65932ebc82a673e917d63eda0007ebcff948691/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f6e92367ddce1e4d33cf295024f40192be6c6171a09208f515ba169ced06c8e", size = 19222976, upload-time = "2026-07-25T01:22:12.474Z" }, +] + +[[package]] +name = "opencv-python" +version = "5.0.0.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749, upload-time = "2026-07-02T06:59:53.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443, upload-time = "2026-07-02T05:50:25.466Z" }, + { url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755, upload-time = "2026-07-02T05:51:30.556Z" }, + { url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064, upload-time = "2026-07-02T06:53:22.604Z" }, + { url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711, upload-time = "2026-07-02T06:54:13.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576, upload-time = "2026-07-02T06:54:33.781Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032, upload-time = "2026-07-02T06:55:03.415Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734, upload-time = "2026-07-02T05:49:57.704Z" }, + { url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345, upload-time = "2026-07-02T05:49:54.971Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "pillow" +version = "12.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035, upload-time = "2026-07-01T11:56:38.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/c8/0a78b0e02d7ac54bc03e5321c9220da52f0c2ea83b21f7c40e7f3169c502/pillow-12.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:00808c5e14ef63ac5161091d242999076604ff74b883423a11e5d7bbb38bf756", size = 5392415, upload-time = "2026-07-01T11:53:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/b2/5b/a02d30018abd97ced9f5a6c63d28597694a00d066516b9c1c6de45859fc9/pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6", size = 4785266, upload-time = "2026-07-01T11:53:49.079Z" }, + { url = "https://files.pythonhosted.org/packages/c8/98/766667a4be768150a202836acd9fad19c06824ca86c4286d3cf6b274964e/pillow-12.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcb46e2f9feff8d06323983bd83ed00c201fdcab3d74973e7072a889b3979fcd", size = 6263814, upload-time = "2026-07-01T11:53:51.32Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2d/ede717bc1144f63886c21fd349bb95860b0d1a21149ff16f2bb362b612b6/pillow-12.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23d27a3e0307ec2244cc51e7287b919aa68d097504ebe19df4e76a98a3eea5bd", size = 6934408, upload-time = "2026-07-01T11:53:53.487Z" }, + { url = "https://files.pythonhosted.org/packages/a3/48/9c58b685e69d49c31af6c8eb9012055fab7e665785165c84796e2c73ce72/pillow-12.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4f883547d4b7f0495ebe7056b0cc2aea76094e7a4abc8e933540f3271df27d9c", size = 6337160, upload-time = "2026-07-01T11:53:55.457Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/dc2a5c0ba6df93f67c31d34b808b7ce440b40cdbf96f0b81cde1d1e6fa93/pillow-12.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:236ff70b9312fb68943c703aa842ca6a758abfa45ac187a5e7c1452e96ef72b5", size = 7045172, upload-time = "2026-07-01T11:53:57.736Z" }, + { url = "https://files.pythonhosted.org/packages/86/a5/444817a4d4c4c2417df00513086ca196f388d8f9ef40c2e4ccd1ad1af54b/pillow-12.3.0-cp311-cp311-win32.whl", hash = "sha256:10e41f0fbf1eec8cfd234b8fe17a4caac7c9d0db4c204d3c173a8f9f6ef3232b", size = 6472232, upload-time = "2026-07-01T11:53:59.767Z" }, + { url = "https://files.pythonhosted.org/packages/63/c6/4bad1b18d132a50b27e1365e1ab163616f7a5bb56d330f66f9d1d9d4f9d4/pillow-12.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8e95e1385e4998ae9694eeaa4730ba5457ff61185b3a55e2e7bea0880aef452a", size = 7233653, upload-time = "2026-07-01T11:54:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/fd/16/00f91ab7760dc842f5aad55217e80fc4a7067a0604535249bc8a2d6d9870/pillow-12.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:ebaea975e03d3141d9d3a507df75c9b3ec90fa9d2ffd07567b3a978d9d790b26", size = 2568195, upload-time = "2026-07-01T11:54:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969, upload-time = "2026-07-01T11:54:06.397Z" }, + { url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323, upload-time = "2026-07-01T11:54:09.351Z" }, + { url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838, upload-time = "2026-07-01T11:54:11.71Z" }, + { url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830, upload-time = "2026-07-01T11:54:13.732Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383, upload-time = "2026-07-01T11:54:15.756Z" }, + { url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934, upload-time = "2026-07-01T11:54:17.721Z" }, + { url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684, upload-time = "2026-07-01T11:54:19.839Z" }, + { url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137, upload-time = "2026-07-01T11:54:22.025Z" }, + { url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267, upload-time = "2026-07-01T11:54:24.051Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684, upload-time = "2026-07-01T11:54:25.934Z" }, + { url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487, upload-time = "2026-07-01T11:54:27.935Z" }, + { url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433, upload-time = "2026-07-01T11:54:29.813Z" }, + { url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889, upload-time = "2026-07-01T11:54:31.97Z" }, + { url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109, upload-time = "2026-07-01T11:54:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736, upload-time = "2026-07-01T11:54:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129, upload-time = "2026-07-01T11:54:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562, upload-time = "2026-07-01T11:54:40.354Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439, upload-time = "2026-07-01T11:54:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287, upload-time = "2026-07-01T11:54:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691, upload-time = "2026-07-01T11:54:47.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185, upload-time = "2026-07-01T11:54:49.137Z" }, + { url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736, upload-time = "2026-07-01T11:54:51.156Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435, upload-time = "2026-07-01T11:54:53.414Z" }, + { url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262, upload-time = "2026-07-01T11:54:55.739Z" }, + { url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344, upload-time = "2026-07-01T11:54:57.657Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131, upload-time = "2026-07-01T11:54:59.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757, upload-time = "2026-07-01T11:55:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962, upload-time = "2026-07-01T11:55:03.93Z" }, + { url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171, upload-time = "2026-07-01T11:55:05.989Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116, upload-time = "2026-07-01T11:55:08.131Z" }, + { url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209, upload-time = "2026-07-01T11:55:10.408Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707, upload-time = "2026-07-01T11:55:12.745Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995, upload-time = "2026-07-01T11:55:14.736Z" }, + { url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503, upload-time = "2026-07-01T11:55:17.076Z" }, + { url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956, upload-time = "2026-07-01T11:55:19.448Z" }, + { url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855, upload-time = "2026-07-01T11:55:21.613Z" }, + { url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642, upload-time = "2026-07-01T11:55:24.006Z" }, + { url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281, upload-time = "2026-07-01T11:55:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716, upload-time = "2026-07-01T11:55:28.318Z" }, + { url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125, upload-time = "2026-07-01T11:55:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939, upload-time = "2026-07-01T11:55:34.044Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506, upload-time = "2026-07-01T11:55:35.988Z" }, + { url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063, upload-time = "2026-07-01T11:55:37.941Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549, upload-time = "2026-07-01T11:55:40.022Z" }, + { url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331, upload-time = "2026-07-01T11:55:41.98Z" }, + { url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370, upload-time = "2026-07-01T11:55:44.028Z" }, + { url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147, upload-time = "2026-07-01T11:55:46.073Z" }, + { url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659, upload-time = "2026-07-01T11:55:48.264Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439, upload-time = "2026-07-01T11:55:50.503Z" }, + { url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577, upload-time = "2026-07-01T11:55:52.697Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394, upload-time = "2026-07-01T11:55:55.149Z" }, + { url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375, upload-time = "2026-07-01T11:55:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048, upload-time = "2026-07-01T11:55:59.975Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006, upload-time = "2026-07-01T11:56:02.143Z" }, + { url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509, upload-time = "2026-07-01T11:56:04.2Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167, upload-time = "2026-07-01T11:56:06.631Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237, upload-time = "2026-07-01T11:56:08.868Z" }, + { url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047, upload-time = "2026-07-01T11:56:11.379Z" }, + { url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440, upload-time = "2026-07-01T11:56:13.908Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895, upload-time = "2026-07-01T11:56:16.575Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384, upload-time = "2026-07-01T11:56:18.855Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537, upload-time = "2026-07-01T11:56:21.214Z" }, + { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, + { url = "https://files.pythonhosted.org/packages/75/18/2e8b40223153ccbc60df07f9e8928dc0c76202aa4e55ae9f53962b6510d6/pillow-12.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b3c777e849237620b022f7f297dd67705f9f5cf1685f09f02e46f93e92725468", size = 5302510, upload-time = "2026-07-01T11:56:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/46/3e/51fabf59d5ab801ceab709453d3ab6b180083496579549de4c45ced6528a/pillow-12.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b343699e8308bdc51978310e1c959c584e7869cc8c40780058c87da7781a1e94", size = 4736058, upload-time = "2026-07-01T11:56:28.041Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/22fe9384b7949e25fb1293bcfc84fb82590ff4ea6b37c95b24d26d793d86/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbd139c8447d25dd750ab79ee274cc5e1fe80fc56340ab10b18a195e1b6eca3e", size = 5237776, upload-time = "2026-07-01T11:56:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/08/14/f6ba68107680ffa74b39985f3f30884e41318fbc4250caa423c79b4788bb/pillow-12.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7e480451b9fa137494bccd3a7d69adbe8ac65a87d97be61e11f1b1050a5bac3", size = 5860358, upload-time = "2026-07-01T11:56:32.68Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, +] + +[[package]] +name = "polars" +version = "1.43.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "polars-runtime-32" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/13/3873f213304bcbaaf39e63c8b905ceb460a0524448d57f86a829f6d4d0fd/polars-1.43.2.tar.gz", hash = "sha256:c699671b99eb71ff53334d237917aaa3db5ad4dda480abcb6c80e0eaee7b677b", size = 750312, upload-time = "2026-08-01T06:28:30.872Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/fe/0888040a24e4504098b85d8ad486b14cb01cf6b030bbe479dfc2dcffc2ac/polars-1.43.2-py3-none-any.whl", hash = "sha256:22aa0cb92a1ee2d60d6a15a638b2e8e0dd99aea21ac0cd8fb29da8e382e075a9", size = 847150, upload-time = "2026-08-01T06:27:15.543Z" }, +] + +[[package]] +name = "polars-runtime-32" +version = "1.43.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/06/11b578eeef05f867e3ee31b2a2fdd8e7684c2aa47822c49935d1be789c38/polars_runtime_32-1.43.2.tar.gz", hash = "sha256:d7b7c486bccee75a6af0158b87077da3d054657e3c60036b28644f4e1c7fdbf7", size = 3095669, upload-time = "2026-08-01T06:28:32.315Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/fc/12e6d4ca34d820297651134cfa35f86c33e898539fc6629cbb35d0089697/polars_runtime_32-1.43.2-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:91abf205d4ec93f92ba95386b7f8776559ae3dfce425ed2e527efa75d117d04a", size = 53088908, upload-time = "2026-08-01T06:27:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/833b0853551deb810854f96b43dea342b6e6c9b0ea1afcccf774157d519d/polars_runtime_32-1.43.2-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:2cc3ff96fd44789b02eb5c15b98dfcb000101636b177d3034fae2feec19b118f", size = 47540529, upload-time = "2026-08-01T06:27:21.391Z" }, + { url = "https://files.pythonhosted.org/packages/83/55/7b2a75af14c9294d97f3bec132dd3018ddcd988bef32b5d28322150b8c11/polars_runtime_32-1.43.2-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:10ed36e615ab362feb7406e6d084e124b445ad284caa73bd93ae7e65745ed894", size = 51366340, upload-time = "2026-08-01T06:27:24.776Z" }, + { url = "https://files.pythonhosted.org/packages/62/60/64deacb3abc70c52e2d88a808a052d1621c86a48fe9194f2c065579ab1cd/polars_runtime_32-1.43.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d5a7ae004a2723ebf4427f6d6a639f30f86af4cf077075f6b35d04711154fc3", size = 57304599, upload-time = "2026-08-01T06:27:27.875Z" }, + { url = "https://files.pythonhosted.org/packages/52/95/d6e3a236d7630e17c40d0ddee839bf2be9acf548fdc0e5ad65ed9ff0cac6/polars_runtime_32-1.43.2-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:09339eacc6d392206e78aabbaaa37d7276eb969b798f46cb1f367fd718798c60", size = 51520580, upload-time = "2026-08-01T06:27:30.913Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/2deb8eac70e9a2ac26d88a66ae7cf52612865026f4f4a5e7ab11ad9d52bf/polars_runtime_32-1.43.2-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:452b400e59e7f56e4c6437f435e796903272a9388feee12de2bea049ae87025e", size = 55204471, upload-time = "2026-08-01T06:27:33.886Z" }, + { url = "https://files.pythonhosted.org/packages/29/9e/647401ae8a607bc0cc40ed7b8592d5b1be90ded0dc9b9d6d3aeb03f9524b/polars_runtime_32-1.43.2-cp310-abi3-win_amd64.whl", hash = "sha256:00e33c28e321410c8d66e814a90043101e3bdd9ed2c6dabda07565aa8adbbdf1", size = 52572176, upload-time = "2026-08-01T06:27:37.048Z" }, + { url = "https://files.pythonhosted.org/packages/96/8d/60a50c3f36c85218a7ffcb48c6fe2ce1f7bec799152d68b8658ebed2179c/polars_runtime_32-1.43.2-cp310-abi3-win_arm64.whl", hash = "sha256:350a4868cae85bf8b3f81b33ba47927c15256bd9264dfc8c0753f1b927eac9d3", size = 46582513, upload-time = "2026-08-01T06:27:40.025Z" }, +] + +[[package]] +name = "protobuf" +version = "7.35.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717, upload-time = "2026-06-11T21:55:40.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226, upload-time = "2026-06-11T21:55:31.719Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847, upload-time = "2026-06-11T21:55:34.035Z" }, + { url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030, upload-time = "2026-06-11T21:55:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130, upload-time = "2026-06-11T21:55:35.921Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945, upload-time = "2026-06-11T21:55:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996, upload-time = "2026-06-11T21:55:38.123Z" }, + { url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659, upload-time = "2026-06-11T21:55:39.155Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "robot-vision-model-tools" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "onnx" }, + { name = "onnxruntime" }, +] + +[package.optional-dependencies] +export = [ + { name = "ultralytics" }, +] + +[package.metadata] +requires-dist = [ + { name = "numpy", specifier = ">=1.26" }, + { name = "onnx", specifier = ">=1.16" }, + { name = "onnxruntime", specifier = ">=1.20" }, + { name = "ultralytics", marker = "extra == 'export'", specifier = ">=8.3" }, +] +provides-extras = ["export"] + +[[package]] +name = "setuptools" +version = "84.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/44/f5da03a8ef95d369145c5bb53050e7877c9f3d312e128605fd9504829143/setuptools-84.0.0.tar.gz", hash = "sha256:f4695c21257f0d9b537ec2692c941d02ee143b7cc1276941349a546573b2ef73", size = 1168449, upload-time = "2026-08-08T18:27:58.365Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl", hash = "sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670", size = 818216, upload-time = "2026-08-08T18:27:56.719Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sympy" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mpmath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, +] + +[[package]] +name = "torch" +version = "2.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cuda-bindings", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, + { name = "filelock" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "networkx" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, + { name = "sympy" }, + { name = "triton", marker = "python_full_version < '3.15' and sys_platform == 'linux'" }, + { name = "typing-extensions" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/fe/cba54dc58523434919b66f13a667e36e436deddd77ca519e96553617d4ec/torch-2.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:e76f9bcecc52b8ff711239a2f7547d5353df95878ab232f0773c1d95928b92f8", size = 111187938, upload-time = "2026-07-08T16:05:17.065Z" }, + { url = "https://files.pythonhosted.org/packages/c2/59/1e3160e18e12aa3038390efab3ce02b36a9d4d6a527ecdd8520dca2e68d8/torch-2.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:092790c696a760c729fd5722835f50b9d81fd7c8f141571f3f3cf4081a8f664c", size = 427199369, upload-time = "2026-07-08T16:04:51.054Z" }, + { url = "https://files.pythonhosted.org/packages/01/79/1f2d34ad7034ee1c7ffc1cf8bf0f8213af2a81df6ecdb3997ecec107c09d/torch-2.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:60fcdcb2f3876e21146cb4524ef06397d727ca9ad5f020818547e25075fe3cb7", size = 526574961, upload-time = "2026-07-08T16:04:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fd/0f2ce40f58aefbdb3392f9acce3c8171940943ae2d661f70558bfa73befb/torch-2.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:a0d8b11f16a48d60e2015d8213aa0390744cbebb98e58b62b3514dddc656e330", size = 122015870, upload-time = "2026-07-08T16:05:27.59Z" }, + { url = "https://files.pythonhosted.org/packages/c4/3a/ed0f4d4d1dcde03bced7aac9a28e800abcdc0cbd06b6775044c9fbd877b7/torch-2.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2fe228aba290d14b9f31b049be550dbd469c3fd3013d7a19705b30454da97027", size = 111213045, upload-time = "2026-07-08T16:05:22.997Z" }, + { url = "https://files.pythonhosted.org/packages/df/a9/f6a2a4d763ff1df02e9a64c477029db614295bc9367f4131223791ccc243/torch-2.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:572df8be8ffb4599c88cbd6a0726f1f854f4da65d2e3c09f0e2c2283333cd6d4", size = 427210998, upload-time = "2026-07-08T16:04:37.708Z" }, + { url = "https://files.pythonhosted.org/packages/f3/82/fea946351658e6534db52d2cc12bc53087cbf87f9440c5f180f367c1950b/torch-2.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:796633c4cdf0fe2cdced72d8f88f22e73dbcfce83132763162f6d4bff13b820b", size = 526605292, upload-time = "2026-07-08T16:04:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/21/d6/e8f3c6f7e01f626f77259de9860d2a78bc84c40539e28e79b7e98b0bb659/torch-2.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:024c6cc0c1b085f2f91f20a3dc27b0471d021c31ce84b81be3afdc39f791fd9d", size = 122057313, upload-time = "2026-07-08T16:03:53.43Z" }, + { url = "https://files.pythonhosted.org/packages/0d/fa/c1c10b7aff4a9a3e8956d4f0a5f468fa6db7abc3208805719076772b4833/torch-2.13.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:33449899ce5496c1b84b4853179d94fd102028ae1407314d9fb956bb79e70d09", size = 111213743, upload-time = "2026-07-08T16:03:28.579Z" }, + { url = "https://files.pythonhosted.org/packages/11/18/9ecb37b56293a0be8d80f810bf672a72fe7e02f8b475d5ef1b9bf8a0d748/torch-2.13.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:1e09d6a722504957c694faceca843acde562786df1144ebcc5a74075ec7f6005", size = 427213008, upload-time = "2026-07-08T16:03:44.106Z" }, + { url = "https://files.pythonhosted.org/packages/d4/5a/7c50ba1b7b713d71d34669c6d13dab0a11531a3eceb0307a5162dbfec0f7/torch-2.13.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:a3a9a21312872af8a26950b2c15680335a386a1f56ed03e780653d78b9607e9e", size = 526602329, upload-time = "2026-07-08T16:03:12.649Z" }, + { url = "https://files.pythonhosted.org/packages/91/3d/e7adcc6aaf36961cd18f56cf8ad0f3058c3a5c84ccf391762176c94581b8/torch-2.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:49b58f1e2c52440abb6f17c28f0335fe6c6d01ad1a7f55b0183b81e4b34d64e6", size = 122057920, upload-time = "2026-07-08T16:03:01.808Z" }, + { url = "https://files.pythonhosted.org/packages/36/76/6dcc7f0c07052102dd36f83cbc5800842a909c8c3fbf1a7f8a5844954de9/torch-2.13.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d849b390e07d8d333ce8ecaf91b273c656c598379a19c9acf1318a883f6b391c", size = 111227066, upload-time = "2026-07-08T16:03:33.6Z" }, + { url = "https://files.pythonhosted.org/packages/e9/09/2c10e8cd0e00fa5d23c052df6ce467eaa7182399f5e0f824f1e4ff42ccae/torch-2.13.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:a3893dc2da0a972a8ca5d698c85a9f967559ac5f8ee1797b77408aa8734d073c", size = 427226309, upload-time = "2026-07-08T16:02:53.127Z" }, + { url = "https://files.pythonhosted.org/packages/76/c6/22c2102bbef14ca6a6cb4c20e42f088e49c5f812be4e160ae57502e325f9/torch-2.13.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:49f1ea385c754e54919408a9bb3b5a72b0b755bbe2c916c1d6f70afbec4908a2", size = 526614507, upload-time = "2026-07-08T16:02:16.441Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0c/7d1deb6bce5bc3e6042caf39100ac768eba3b9a098e1dddd16f75bd6489b/torch-2.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f8573e3ce9ebcd53fe922f01077a6085ccdfbe5f12fd215883a9d87d7a744fd", size = 122051871, upload-time = "2026-07-08T16:03:23.521Z" }, + { url = "https://files.pythonhosted.org/packages/f4/ce/aa8b7f9949d32e0f2f624f342bc3b48112c1b8a130288465938bc83bcbf9/torch-2.13.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c28def70706c2f9ecc752574766e8ae4da9b810ab6676b611166761a78a9f1e1", size = 111537025, upload-time = "2026-07-08T16:02:44.28Z" }, + { url = "https://files.pythonhosted.org/packages/69/d1/491e3a0389430946145888b0203f2b6a759ce2a61481b96a85c2da4f2ced/torch-2.13.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:31061ff56ed8fbf26c749806905aeb749ebeb819810fd5d52508aa5afd90dddc", size = 427219769, upload-time = "2026-07-08T16:02:31.18Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1d/38006e045bf0a1fc28ef01e757c554e59e59a8770c284bc4f47b14e60441/torch-2.13.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:cc26eead4cf51d0b544e31e364dcf000846549c273bd148936fe9d24d29acb92", size = 526571320, upload-time = "2026-07-08T16:01:59.348Z" }, + { url = "https://files.pythonhosted.org/packages/56/94/655c91992a882bd5071aa0b5d22a07dbb130d801e872be97c0b627a7c693/torch-2.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:a7de8a313090dc5c7d7ba4bfe5c3be222528f9a4dba1acc83bddb1157360c4b8", size = 122306773, upload-time = "2026-07-08T16:02:39.832Z" }, +] + +[[package]] +name = "torchvision" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pillow" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/b2/1e010052079e4c577007b789db336ea7075f1a426e84d17121fbc3745516/torchvision-0.28.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83fe6c020866a85acd7d97deccc45ff11d66daf42916d04396a4309c66c0ccb8", size = 1856017, upload-time = "2026-07-08T16:07:55.533Z" }, + { url = "https://files.pythonhosted.org/packages/27/be/1b9c5de9c655ca2df4a74100fa671a7b848532ff787e077ccde14a7dea2a/torchvision-0.28.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5a38bc6da3d72621be003400b66f66a2b4c6d644fde05f680c2cb7ca8cf8dd6c", size = 7841822, upload-time = "2026-07-08T16:07:49.207Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9b/f1e68e861d4462e3e195a642c2b448e7b7d3fad5f209487162b9a2133d9b/torchvision-0.28.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7e80f543b22503d9415e126db5f0ff3917036925e38560ee6b9ae38c571a4002", size = 7670718, upload-time = "2026-07-08T16:07:46.525Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/1494610ff54cbb154beb55033cc2cd50f3de04dac132fa2dd00e4f2b2556/torchvision-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:9a45ea67235d965ef52187130d20002a4de20c54ea3d927a24286961d268dc37", size = 3814319, upload-time = "2026-07-08T16:07:37.153Z" }, + { url = "https://files.pythonhosted.org/packages/15/49/c1cab1ecbb3ff1a380a3f99283db1dee61b8afe354f6352c643b65937130/torchvision-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:e9f54c30cd52e3ef7fd034cc69b7bb7e0964e1c8f8743e018ab92e95b40f9eee", size = 1856020, upload-time = "2026-07-08T16:07:52.182Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4c/95233776e2def960e5abb7a07931230a545f43717a56a1e1140162033598/torchvision-0.28.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5cf78ebc401ce64ae19b8c55de866bb836797d559a4de9c25ccbe74cfa642d3a", size = 7842127, upload-time = "2026-07-08T16:07:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/93/e4/e9b2495d0d57b9f60d63c57d0a910410a81b4b073bf70917bef815291119/torchvision-0.28.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:028a3d481b37d785605620d7cdad897064c5a55bae2aa1f2658766333e291940", size = 7675040, upload-time = "2026-07-08T16:07:58.017Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9c/55ed9cb6dfe3ee9c837df5cd0e758372e5829aa38b8dd71343aa632cc4e2/torchvision-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:87dc16b2df427c1318ad335f1e2be2b3b15b2cf20f7934c83b0505a48425ee5d", size = 4085785, upload-time = "2026-07-08T16:07:50.928Z" }, + { url = "https://files.pythonhosted.org/packages/20/55/08a726c14c67b37c8aca04b077766909f1c7ed23f76116884fe63b9bd033/torchvision-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d483b4aa3f5237569053f749cd1a2b5bb548ca456e40461a5dd087f21149d123", size = 1856021, upload-time = "2026-07-08T16:07:45.386Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/40beacd53809194f5259e590d1afaeaa8ad57da15f77c646e6560bcc4616/torchvision-0.28.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bb6dd6918460ed89cc7644adcc2402991474d6933cf1ce92b390641cb233fddf", size = 7797014, upload-time = "2026-07-08T16:07:43.04Z" }, + { url = "https://files.pythonhosted.org/packages/32/db/062cdb5a84380a60439775311fff34d89229760d2a50680393dc18699956/torchvision-0.28.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad7b3a439265cc3739a4ab5b4c998c0e38ea99c0ee7ca4dea35c5d0b099ec237", size = 7674669, upload-time = "2026-07-08T16:07:38.91Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a6/b4081e2d04e1541abf82785ac9e5178a494c19330391f551356c8c18b7b3/torchvision-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:7e9dd6f60d6e15f8dc27d4f877fdb6002fc70d70272412135f1c2ff9cfa08d3b", size = 4157380, upload-time = "2026-07-08T16:07:40.22Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b9/da40eca5bbe9596c12ae9899ab7abaf887f5e20f29d08b924b4633714821/torchvision-0.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3bd9dba55224a9db4a2d77f6feaa5651770d8c8e86d3d0ddb0fa6bec54c8712b", size = 1856014, upload-time = "2026-07-08T16:07:44.282Z" }, + { url = "https://files.pythonhosted.org/packages/06/d6/313aafd3df4eaf5f330211bd4e75b7598bddbfee4f55580d3b58536e1b20/torchvision-0.28.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:89f90e29b0966352811b12589f3a3c61943bf2bb9487b9d7bbec10efb1096bb5", size = 7796873, upload-time = "2026-07-08T16:07:30.907Z" }, + { url = "https://files.pythonhosted.org/packages/b3/41/31f8e959ab8f942600b6357f8999c21d779d5fd3304b0fd204ff4b518239/torchvision-0.28.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:36beb0782976906069ca03d4c9aacaf4b6b838b06ed6c20960ea9c51cce7acdd", size = 7674634, upload-time = "2026-07-08T16:07:29.657Z" }, + { url = "https://files.pythonhosted.org/packages/15/15/4c5115253fd470672cdac0a1cf139e06b4f3e29d041238a2b255937f63be/torchvision-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:3557cc7b539f46dabcda2b6f2b14017ccbeef024de466d4fc5835fc3f287f769", size = 4184005, upload-time = "2026-07-08T16:07:35.805Z" }, + { url = "https://files.pythonhosted.org/packages/6a/80/822a6163da716f8a78141cf6678d74e26a572285d4ea866ef8aa657bb307/torchvision-0.28.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:09ce8f56e81f19b9c378ae7bb109f83f6659fd8bc3cd14241a48e4af46e9ed49", size = 1856011, upload-time = "2026-07-08T16:07:33.404Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d1/cd3f9463b39a790ec8c0c2f6e6c8061edb1562114d04fcdfa786ed889345/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:62c7d110f86a039245b587e4fae60278c649f3bd42ff79cfbc1178eca4e72542", size = 7796742, upload-time = "2026-07-08T16:07:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/3e0a7ad18e99831e2d7f4713d3be717b7159ff5a920862dd5c23c454aa71/torchvision-0.28.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:904cf89af220f8c6b2ed0296bb5065b474ce43b77558e48b2bf9de8b0ba17204", size = 7675526, upload-time = "2026-07-08T16:07:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/18/d4/23aea03b28297bc66a4461f55ae4296368a9d85fa9a454bafcb2a5348bd7/torchvision-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:46f581979c010ad6da6bd85ee602aa707e1ff44312670223b7a0ee517ad06d47", size = 4291452, upload-time = "2026-07-08T16:07:32.236Z" }, +] + +[[package]] +name = "triton" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/f9/19d842d06a08559534fa1eaab6ca551b1bcf40f06620bddec1babaa2772d/triton-3.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a0e1cd4c4a76370ed74a8432a53cea28716827d19e40ffc732233e35ceb3f6", size = 184664887, upload-time = "2026-06-17T20:03:42.913Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5e/fce69606f7f240297f163e25539906732b199530d486ce67ae319877e821/triton-3.7.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6744957e9fd610a29680ec2346057d0c86948ed3812468670719f391e94b44a5", size = 197701306, upload-time = "2026-06-17T19:53:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/94/fa/f856e24deb462d5f18bd4b5a746957862ab9b6ee5834bda60605ec348366/triton-3.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9497f2e696ee368862a181a90b2dcc03ca978cc4f602abd67c7d81022a6988e1", size = 184692359, upload-time = "2026-06-17T20:03:48.288Z" }, + { url = "https://files.pythonhosted.org/packages/c4/6f/fb96d15db6f36d6eae4cafb998c2e0353bf59d7c4ea1662d7497f269134a/triton-3.7.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7e40869937a68206ec70d7f25bb7ec6433cb083f9135e1f36dbd318dc449a728", size = 197719725, upload-time = "2026-06-17T19:53:20.419Z" }, + { url = "https://files.pythonhosted.org/packages/00/42/c5089d4d9327fcd1e862c599cc2927f39418f84dd11a84cb2ccff9d4787a/triton-3.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cdbfc09d9ec58bc5e68321525653220de7515c199e7a8097a97c85e62b52cd0a", size = 184694629, upload-time = "2026-06-17T20:03:53.444Z" }, + { url = "https://files.pythonhosted.org/packages/07/42/2c3ac59253ae8892b6f307875263dd23dc875cdf732d3aea40d6d41fb7cb/triton-3.7.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58c0e131da05134a2a4788ccbcc0c1105cf0f54c8e98f19e34cd465396dc15eb", size = 197729241, upload-time = "2026-06-17T19:53:27.801Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/e01aa7ad573883ed9456f130226babdec70b005e098c4d6226a6238e761b/triton-3.7.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe4ea396a06171f1f1f58cbd39c70b09294398f7dd7c620939bab54ad6f934fa", size = 184705764, upload-time = "2026-06-17T20:03:59.064Z" }, + { url = "https://files.pythonhosted.org/packages/a4/09/5683146fda6a2b569deb78ccfd8fbfea8bfe55f726b081c0a6bb18dd6f28/triton-3.7.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2020153b08280415ec0da6607834e79166442147e78e144df06b508c75b186d2", size = 197729537, upload-time = "2026-06-17T19:53:35.516Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f8/448220c3092019f9fdfab39ec47985968181d67da34b44f6a7f6280a5cbb/triton-3.7.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c58e4c61f0c73b5dba3b5d19b4a7093c32f90dc18b2a7f121a7c16ccd31107b7", size = 184814760, upload-time = "2026-06-17T20:04:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ac/229b7d4589d2e5937310e72c6d46e89599d16a4a12b479ffa1499fee8eb8/triton-3.7.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10ba85fa2cca4a2fbdeb36bf1cb082f2c252bda55bf9fccd74f65ec5bc647e68", size = 197824404, upload-time = "2026-06-17T19:53:42.772Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "ultralytics" +version = "8.4.120" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "nvidia-ml-py" }, + { name = "opencv-python" }, + { name = "pillow" }, + { name = "polars" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "torch" }, + { name = "torchvision" }, + { name = "ultralytics-platform" }, + { name = "ultralytics-thop" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/d7/bee7f5a1bd6bcecaf173eca5fdc092cfa79fd39001a96ff272c88eefc710/ultralytics-8.4.120.tar.gz", hash = "sha256:e6eec3adfcbe8623360d3eea949e98a0d143d00f4cdf72d130b013f5c11ed19c", size = 1205284, upload-time = "2026-08-13T22:39:00.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/d2/cdbb826de49f2c000808b9ed8d43dd779618f0e4387f5b06521db2c4033b/ultralytics-8.4.120-py3-none-any.whl", hash = "sha256:d1872e5ee295c5cdbcdd1309199f2326b4d3c72a871af272bf6218e4d747ee72", size = 1424205, upload-time = "2026-08-13T22:38:56.387Z" }, +] + +[[package]] +name = "ultralytics-platform" +version = "0.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/53/898352d8c872aee2aacffb96b2d054d49ef06095c23c85c0f35db1fcd652/ultralytics_platform-0.1.5.tar.gz", hash = "sha256:72f6aba7f6251747c7f69d68253036d106e0142242fbc7244b4fa761e265c1ea", size = 52592, upload-time = "2026-08-15T17:22:42.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/56/684ae3e973f6fbf813714cb875eca1be09876cdfa34806e044f86b3844a1/ultralytics_platform-0.1.5-py3-none-any.whl", hash = "sha256:2e1b101b4b7a8a61757005c01101afa8cd535d653ab11f849653e17877bd14d9", size = 63884, upload-time = "2026-08-15T17:22:41.637Z" }, +] + +[[package]] +name = "ultralytics-thop" +version = "2.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.5.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "torch" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/20/d6b6aaa8ecf7dbdeaf0c05f73bdcd04cf8ce468d936bc48dbcd368e75baf/ultralytics_thop-2.1.6.tar.gz", hash = "sha256:0ec2df8ebd3db35795e1f80cdc8bce6734446dbe989bca1b0c89396353f0f08c", size = 36364, upload-time = "2026-07-30T22:31:28.143Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/98/f1fa3d40d548c8a2a3eec33b7f856063bb6c7d51e16d5198b1f390b2c79d/ultralytics_thop-2.1.6-py3-none-any.whl", hash = "sha256:23f7b8ad124fa3432c1a7de9279102c4fdda699216032a7dff49f87ec3d1a3af", size = 30479, upload-time = "2026-07-30T22:31:26.874Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] diff --git a/RobotVisionPlatform/device/tools/model/validate_onnx.py b/RobotVisionPlatform/device/tools/model/validate_onnx.py new file mode 100644 index 0000000..7256949 --- /dev/null +++ b/RobotVisionPlatform/device/tools/model/validate_onnx.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Run an ONNX model and report output shapes, numeric ranges, and average latency.""" + +from __future__ import annotations + +import argparse +import time +from pathlib import Path + +import numpy as np +import onnxruntime as ort + + +def parse_shapes(items: list[str]) -> dict[str, tuple[int, ...]]: + result: dict[str, tuple[int, ...]] = {} + for item in items: + try: + name, dimensions = item.split("=", 1) + shape = tuple(int(value) for value in dimensions.split(",")) + except ValueError as error: + raise ValueError(f"Invalid --shape '{item}'; expected name=1,3,640,640") from error + if not shape or any(value <= 0 for value in shape): + raise ValueError(f"Shape values must be positive: {item}") + result[name] = shape + return result + + +def providers_for(name: str) -> list[str | tuple[str, dict[str, object]]]: + if name == "cpu": + return ["CPUExecutionProvider"] + if name == "cuda": + return ["CUDAExecutionProvider", "CPUExecutionProvider"] + return [ + ("TensorrtExecutionProvider", {"trt_fp16_enable": True, "trt_timing_cache_enable": True}), + "CUDAExecutionProvider", + "CPUExecutionProvider", + ] + + +def concrete_shape(name: str, model_shape: list[int | str | None], overrides: dict[str, tuple[int, ...]]) -> tuple[int, ...]: + if name in overrides: + return overrides[name] + if any(not isinstance(value, int) or value <= 0 for value in model_shape): + raise ValueError(f"Input '{name}' is dynamic; provide --shape {name}=1,3,640,640") + return tuple(int(value) for value in model_shape) + + +def numpy_type(ort_type: str) -> np.dtype: + supported = { + "tensor(float)": np.dtype(np.float32), + "tensor(float16)": np.dtype(np.float16), + "tensor(uint8)": np.dtype(np.uint8), + "tensor(int64)": np.dtype(np.int64), + } + if ort_type not in supported: + raise ValueError(f"Unsupported demo input type: {ort_type}") + return supported[ort_type] + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("model", type=Path) + parser.add_argument("--provider", choices=("cpu", "cuda", "tensorrt"), default="cpu") + parser.add_argument("--shape", action="append", default=[], help="Input override, e.g. images=1,3,640,640") + parser.add_argument("--input-npy", type=Path, help="Use a .npy array for a single-input model") + parser.add_argument("--runs", type=int, default=10) + parser.add_argument("--seed", type=int, default=42) + args = parser.parse_args() + if args.runs < 1: + parser.error("--runs must be at least 1") + + required_provider = { + "cpu": "CPUExecutionProvider", + "cuda": "CUDAExecutionProvider", + "tensorrt": "TensorrtExecutionProvider", + }[args.provider] + available = ort.get_available_providers() + if required_provider not in available: + raise RuntimeError( + f"Requested provider '{required_provider}' is not installed. " + f"Available providers: {', '.join(available)}" + ) + requested = providers_for(args.provider) + session = ort.InferenceSession(str(args.model), providers=requested) + print("available_providers=" + ",".join(available)) + print("active_providers=" + ",".join(session.get_providers())) + + overrides = parse_shapes(args.shape) + rng = np.random.default_rng(args.seed) + feeds: dict[str, np.ndarray] = {} + for index, item in enumerate(session.get_inputs()): + if args.input_npy and len(session.get_inputs()) == 1: + value = np.load(args.input_npy) + else: + shape = concrete_shape(item.name, item.shape, overrides) + dtype = numpy_type(item.type) + value = rng.random(shape).astype(dtype) if np.issubdtype(dtype, np.floating) else np.zeros(shape, dtype=dtype) + feeds[item.name] = value + print(f"input[{index}] name={item.name} shape={value.shape} dtype={value.dtype}") + + session.run(None, feeds) # warm-up + started = time.perf_counter() + outputs: list[np.ndarray] = [] + for _ in range(args.runs): + outputs = session.run(None, feeds) + elapsed_ms = (time.perf_counter() - started) * 1000.0 / args.runs + + for index, value in enumerate(outputs): + numeric = np.asarray(value) + finite = numeric[np.isfinite(numeric)] if np.issubdtype(numeric.dtype, np.number) else np.array([]) + stats = "non-numeric-or-empty" + if finite.size: + stats = f"min={finite.min():.6g} max={finite.max():.6g} mean={finite.mean():.6g}" + print(f"output[{index}] shape={numeric.shape} dtype={numeric.dtype} {stats}") + print(f"average_inference_ms={elapsed_ms:.3f} runs={args.runs}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/RobotVisionPlatform/docs/getting-started-for-beginners.md b/RobotVisionPlatform/docs/getting-started-for-beginners.md index 1ac9058..c9820a6 100644 --- a/RobotVisionPlatform/docs/getting-started-for-beginners.md +++ b/RobotVisionPlatform/docs/getting-started-for-beginners.md @@ -97,6 +97,6 @@ ctest --test-dir device/build --output-on-failure 3. [Fine-tuning 실습 순서](../tips/training/fine-tuning-playbook.md) 4. [전체 아키텍처](architecture.md) 5. [Jetson 배포](jetson-deployment.md) +6. [ONNX/TensorRT 모델 배포](../device/guides/README.md) 문제가 생기면 [문제 해결 문서](troubleshooting.md)를 먼저 확인합니다. - diff --git a/RobotVisionPlatform/scripts/build.sh b/RobotVisionPlatform/scripts/build.sh old mode 100644 new mode 100755 diff --git a/RobotVisionPlatform/scripts/test.ps1 b/RobotVisionPlatform/scripts/test.ps1 index f3aff20..d0a60ce 100644 --- a/RobotVisionPlatform/scripts/test.ps1 +++ b/RobotVisionPlatform/scripts/test.ps1 @@ -2,9 +2,9 @@ $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot dotnet run --project "$root/server/tests/RobotVision.Server.Tests" --configuration Release +python -m unittest discover -s "$root/device/tools/model/tests" -v if (Test-Path "$root/device/build/CTestTestfile.cmake") { ctest --test-dir "$root/device/build" -C Release --output-on-failure } else { Write-Warning 'C++ build directory is unavailable; device tests were skipped.' } - diff --git a/RobotVisionPlatform/scripts/test.sh b/RobotVisionPlatform/scripts/test.sh old mode 100644 new mode 100755 index efd8e72..93c40a7 --- a/RobotVisionPlatform/scripts/test.sh +++ b/RobotVisionPlatform/scripts/test.sh @@ -2,5 +2,5 @@ set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" dotnet run --project "$repo_root/server/tests/RobotVision.Server.Tests" --configuration Release +python3 -m unittest discover -s "$repo_root/device/tools/model/tests" -v ctest --test-dir "$repo_root/device/build" --output-on-failure - diff --git a/RobotVisionPlatform/tips/resources/official-resources.md b/RobotVisionPlatform/tips/resources/official-resources.md index 2d8cc36..cf1efb5 100644 --- a/RobotVisionPlatform/tips/resources/official-resources.md +++ b/RobotVisionPlatform/tips/resources/official-resources.md @@ -12,6 +12,8 @@ - [ASP.NET Core gRPC](https://learn.microsoft.com/aspnet/core/grpc/) - [.NET MAUI Blazor Hybrid](https://learn.microsoft.com/aspnet/core/blazor/hybrid/) - [OpenTelemetry C++](https://opentelemetry.io/docs/languages/cpp/) +- [ONNX Runtime Execution Providers](https://onnxruntime.ai/docs/execution-providers/) +- [TensorRT ONNX 배포 Quick Start](https://docs.nvidia.com/deeplearning/tensorrt/latest/getting-started/quick-start-onnx-deployment.html) +- [DeepStream custom model 가이드](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_using_custom_model.html) 블로그의 명령을 그대로 복사하기보다 JetPack–DeepStream 호환표와 각 릴리스 노트를 먼저 확인합니다. - From bd1b9b791db4876bcedc5b27e229508f0c3a8441 Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 19:40:50 +0900 Subject: [PATCH 4/7] clean model toolkit formatting --- RobotVisionPlatform/.gitattributes | 1 - .../device/config/deepstream/config_infer_primary.example.txt | 1 - RobotVisionPlatform/device/guides/README.md | 1 - RobotVisionPlatform/device/guides/deepstream-custom-model.md | 1 - RobotVisionPlatform/device/guides/model-bundle.md | 1 - RobotVisionPlatform/device/guides/tensorrt-on-jetson.md | 1 - RobotVisionPlatform/device/models/example/labels.txt | 1 - RobotVisionPlatform/device/models/example/manifest.json | 1 - RobotVisionPlatform/device/tools/model/create_manifest.py | 1 - RobotVisionPlatform/device/tools/model/export_ultralytics.py | 1 - RobotVisionPlatform/device/tools/model/inspect_onnx.py | 1 - RobotVisionPlatform/device/tools/model/pyproject.toml | 1 - 12 files changed, 12 deletions(-) diff --git a/RobotVisionPlatform/.gitattributes b/RobotVisionPlatform/.gitattributes index 86d1962..afba686 100644 --- a/RobotVisionPlatform/.gitattributes +++ b/RobotVisionPlatform/.gitattributes @@ -3,4 +3,3 @@ *.yaml text eol=lf *.service text eol=lf *.proto text eol=lf - diff --git a/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt b/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt index e20627f..d807a54 100644 --- a/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt +++ b/RobotVisionPlatform/device/config/deepstream/config_infer_primary.example.txt @@ -24,4 +24,3 @@ cluster-mode=2 pre-cluster-threshold=0.50 nms-iou-threshold=0.45 topk=300 - diff --git a/RobotVisionPlatform/device/guides/README.md b/RobotVisionPlatform/device/guides/README.md index bb612fe..54b0f75 100644 --- a/RobotVisionPlatform/device/guides/README.md +++ b/RobotVisionPlatform/device/guides/README.md @@ -29,4 +29,3 @@ PyTorch/학습 도구 | TensorRT가 지원하지 않는 일부 ONNX 연산 | ONNX Runtime TensorRT EP | 지원 node는 TensorRT, 나머지는 CUDA/CPU fallback 가능 | TensorRT와 DeepStream 중 하나가 항상 정답은 아닙니다. 첫 모델은 두 방식의 정확도, FPS, 지연, 메모리를 실제 Jetson에서 비교한 후 선택합니다. - diff --git a/RobotVisionPlatform/device/guides/deepstream-custom-model.md b/RobotVisionPlatform/device/guides/deepstream-custom-model.md index 4138109..31de025 100644 --- a/RobotVisionPlatform/device/guides/deepstream-custom-model.md +++ b/RobotVisionPlatform/device/guides/deepstream-custom-model.md @@ -31,4 +31,3 @@ network-mode=2 여러 문제를 한 번에 연결하면 모델 문제와 영상 pipeline 문제를 구분하기 어렵습니다. 공식 자료: [DeepStream custom model guide](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_using_custom_model.html) - diff --git a/RobotVisionPlatform/device/guides/model-bundle.md b/RobotVisionPlatform/device/guides/model-bundle.md index c29861a..d16df2d 100644 --- a/RobotVisionPlatform/device/guides/model-bundle.md +++ b/RobotVisionPlatform/device/guides/model-bundle.md @@ -28,4 +28,3 @@ person-detector/0.1.0/ 6. health guardrail 위반 시 이전 symlink로 rollback합니다. engine은 Git에 commit하지 않습니다. ONNX도 크기가 크면 Git LFS나 모델 registry/object storage에서 관리하고 저장소에는 manifest만 둡니다. - diff --git a/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md b/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md index 54ad010..754768c 100644 --- a/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md +++ b/RobotVisionPlatform/device/guides/tensorrt-on-jetson.md @@ -60,4 +60,3 @@ FP16을 먼저 정확도 기준선으로 만듭니다. INT8은 실제 운영 장 - 속도가 느림: CPU fallback, 입력 복사, power mode, thermal throttling을 함께 확인 공식 자료: [TensorRT ONNX deployment quick start](https://docs.nvidia.com/deeplearning/tensorrt/latest/getting-started/quick-start-onnx-deployment.html), [TensorRT dynamic shapes](https://docs.nvidia.com/deeplearning/tensorrt/latest/inference-library/work-with-dynamic-shapes.html) - diff --git a/RobotVisionPlatform/device/models/example/labels.txt b/RobotVisionPlatform/device/models/example/labels.txt index fffe470..981c24b 100644 --- a/RobotVisionPlatform/device/models/example/labels.txt +++ b/RobotVisionPlatform/device/models/example/labels.txt @@ -1,3 +1,2 @@ person forklift - diff --git a/RobotVisionPlatform/device/models/example/manifest.json b/RobotVisionPlatform/device/models/example/manifest.json index 628019c..fc9ece7 100644 --- a/RobotVisionPlatform/device/models/example/manifest.json +++ b/RobotVisionPlatform/device/models/example/manifest.json @@ -26,4 +26,3 @@ "engineBuildLocation": "target-device" } } - diff --git a/RobotVisionPlatform/device/tools/model/create_manifest.py b/RobotVisionPlatform/device/tools/model/create_manifest.py index 7699a76..eff5bf0 100644 --- a/RobotVisionPlatform/device/tools/model/create_manifest.py +++ b/RobotVisionPlatform/device/tools/model/create_manifest.py @@ -62,4 +62,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/RobotVisionPlatform/device/tools/model/export_ultralytics.py b/RobotVisionPlatform/device/tools/model/export_ultralytics.py index 7d43b43..99cc510 100644 --- a/RobotVisionPlatform/device/tools/model/export_ultralytics.py +++ b/RobotVisionPlatform/device/tools/model/export_ultralytics.py @@ -37,4 +37,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/RobotVisionPlatform/device/tools/model/inspect_onnx.py b/RobotVisionPlatform/device/tools/model/inspect_onnx.py index 469334a..b151809 100644 --- a/RobotVisionPlatform/device/tools/model/inspect_onnx.py +++ b/RobotVisionPlatform/device/tools/model/inspect_onnx.py @@ -56,4 +56,3 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) - diff --git a/RobotVisionPlatform/device/tools/model/pyproject.toml b/RobotVisionPlatform/device/tools/model/pyproject.toml index 0753dbf..5b8fbe9 100644 --- a/RobotVisionPlatform/device/tools/model/pyproject.toml +++ b/RobotVisionPlatform/device/tools/model/pyproject.toml @@ -14,4 +14,3 @@ export = ["ultralytics>=8.3"] [tool.uv] package = false - From 21e7437b21dbcd1df9ac0f0cc02465226fd1fdd6 Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 20:19:27 +0900 Subject: [PATCH 5/7] document code and add API reference --- RobotVisionPlatform/README.md | 2 + RobotVisionPlatform/device/CMakeLists.txt | 3 + .../device/include/rv/bounded_queue.hpp | 13 +- .../device/include/rv/interfaces.hpp | 21 +- .../device/include/rv/pipeline.hpp | 22 ++- .../device/include/rv/types.hpp | 19 +- .../device/src/http_event_sink.cpp | 7 +- RobotVisionPlatform/device/src/main.cpp | 6 + RobotVisionPlatform/device/src/pipeline.cpp | 10 +- .../device/src/synthetic_adapters.cpp | 7 +- .../device/tests/core_tests.cpp | 3 +- .../tools/model/build_tensorrt_engine.sh | 8 + .../device/tools/model/create_manifest.py | 4 + .../device/tools/model/export_ultralytics.py | 3 + .../device/tools/model/inspect_onnx.py | 7 + .../tools/model/tests/test_create_manifest.py | 4 + .../device/tools/model/validate_onnx.py | 9 + RobotVisionPlatform/docs/README.md | 1 + .../docs/getting-started-for-beginners.md | 1 + RobotVisionPlatform/reference/README.md | 38 ++++ .../reference/cpp20-device-reference.md | 187 ++++++++++++++++++ .../reference/csharp-server-reference.md | 158 +++++++++++++++ .../reference/python-model-tools-reference.md | 171 ++++++++++++++++ .../scripts-and-protocol-reference.md | 138 +++++++++++++ RobotVisionPlatform/scripts/build.ps1 | 5 +- RobotVisionPlatform/scripts/build.sh | 3 +- .../scripts/send-demo-event.ps1 | 5 +- RobotVisionPlatform/scripts/test.ps1 | 2 + RobotVisionPlatform/scripts/test.sh | 1 + .../src/RobotVision.Dashboard.Maui/App.xaml | 2 +- .../RobotVision.Dashboard.Maui/App.xaml.cs | 2 +- .../src/RobotVision.Dashboard.Maui/Main.razor | 2 +- .../RobotVision.Dashboard.Maui/MainPage.xaml | 2 +- .../MainPage.xaml.cs | 7 +- .../RobotVision.Dashboard.Maui/MauiProgram.cs | 4 +- .../DetectionFanoutWorker.cs | 9 +- .../RobotVision.Server.Api/DeviceRegistry.cs | 11 +- .../RobotVision.Server.Api/MonitoringHub.cs | 5 +- .../src/RobotVision.Server.Api/Program.cs | 9 +- .../RobotVision.Server.Api/wwwroot/index.html | 10 +- .../DeviceContracts.cs | 9 +- .../tests/RobotVision.Server.Tests/Program.cs | 2 +- .../shared/proto/vision/v1/device.proto | 22 ++- 43 files changed, 928 insertions(+), 26 deletions(-) create mode 100644 RobotVisionPlatform/reference/README.md create mode 100644 RobotVisionPlatform/reference/cpp20-device-reference.md create mode 100644 RobotVisionPlatform/reference/csharp-server-reference.md create mode 100644 RobotVisionPlatform/reference/python-model-tools-reference.md create mode 100644 RobotVisionPlatform/reference/scripts-and-protocol-reference.md diff --git a/RobotVisionPlatform/README.md b/RobotVisionPlatform/README.md index dd4fd4d..faf6df1 100644 --- a/RobotVisionPlatform/README.md +++ b/RobotVisionPlatform/README.md @@ -53,6 +53,7 @@ RobotVisionPlatform/ ├── device/ # Jetson에서 실행되는 C++20 프로젝트 ├── server/ # ASP.NET Core 수집 서버와 MAUI 관제 앱 ├── shared/proto/ # 장치/서버 공용 protobuf 계약 +├── reference/ # 함수·타입·명령을 이름으로 찾는 개발 사전 ├── deploy/ # Compose, systemd, 배포 설정 ├── scripts/ # 개발·검증 스크립트 └── ../.github/workflows/ # GitHub가 인식하는 저장소 루트 CI/릴리스 자동화 @@ -128,6 +129,7 @@ dotnet run --project server/src/RobotVision.Server.Api - [용어집](docs/glossary.md) - [문제 해결](docs/troubleshooting.md) - [ONNX/TensorRT 모델 배포 가이드](device/guides/README.md) +- [함수·타입·명령 Reference 사전](reference/README.md) ## 기술 기준 diff --git a/RobotVisionPlatform/device/CMakeLists.txt b/RobotVisionPlatform/device/CMakeLists.txt index c0249ef..4500356 100644 --- a/RobotVisionPlatform/device/CMakeLists.txt +++ b/RobotVisionPlatform/device/CMakeLists.txt @@ -4,6 +4,7 @@ project(robot_vision_device VERSION 0.1.0 LANGUAGES CXX) option(RV_BUILD_TESTS "Build device unit tests" ON) option(RV_ENABLE_BOOST_HTTP "Enable Boost.Beast HTTP event sink when Boost is available" ON) +# rv_core contains portable pipeline logic; hardware/network adapters are optional sources. add_library(rv_core src/pipeline.cpp src/synthetic_adapters.cpp @@ -19,6 +20,7 @@ else() endif() if(RV_ENABLE_BOOST_HTTP) + # Keep the core build usable when Boost is absent, but add the HTTP smoke-test sink when found. find_package(Boost 1.74 QUIET COMPONENTS system) if(Boost_FOUND) target_sources(rv_core PRIVATE src/http_event_sink.cpp) @@ -37,6 +39,7 @@ install(TARGETS robot_vision_device RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES config/device.example.toml DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/robot-vision) if(RV_BUILD_TESTS) + # CTest runs this dependency-free executable on developer PCs and CI. enable_testing() add_executable(rv_core_tests tests/core_tests.cpp) target_link_libraries(rv_core_tests PRIVATE rv_core) diff --git a/RobotVisionPlatform/device/include/rv/bounded_queue.hpp b/RobotVisionPlatform/device/include/rv/bounded_queue.hpp index cd14ecb..46328cc 100644 --- a/RobotVisionPlatform/device/include/rv/bounded_queue.hpp +++ b/RobotVisionPlatform/device/include/rv/bounded_queue.hpp @@ -11,12 +11,18 @@ namespace rv { +/// 실시간 처리용 크기 제한 producer-consumer queue입니다. +/// +/// queue가 가득 차면 producer를 막지 않고 가장 오래된 항목을 제거합니다. +/// 이 정책은 모든 프레임 보존보다 최신 화면의 낮은 지연이 중요한 경우에 적합합니다. template class LatestQueue { public: + /// @param capacity 보관할 최대 항목 수입니다. 0은 안전하게 1로 보정됩니다. explicit LatestQueue(std::size_t capacity) : capacity_(capacity == 0 ? 1 : capacity) {} - // Real-time policy: discard the oldest work rather than accumulate latency. + /// 값을 queue 뒤에 추가합니다. 가득 찼으면 가장 오래된 값을 먼저 버립니다. + /// @return queue가 열려 있어 추가됐으면 true, Close() 이후면 false입니다. bool Push(T value) { std::scoped_lock lock(mutex_); if (closed_) return false; @@ -29,8 +35,11 @@ class LatestQueue { return true; } + /// 값이 생기거나 queue가 닫히거나 stop 요청이 올 때까지 기다립니다. + /// @return 값이 있으면 이동 반환하고, 정상 종료 조건이면 nullopt를 반환합니다. std::optional Pop(std::stop_token stop) { std::unique_lock lock(mutex_); + // condition_variable_any의 C++20 overload는 stop 요청도 wake-up 조건으로 처리합니다. ready_.wait(lock, stop, [this] { return closed_ || !items_.empty(); }); if (items_.empty()) return std::nullopt; T value = std::move(items_.front()); @@ -38,12 +47,14 @@ class LatestQueue { return value; } + /// 이후 Push를 거부하고 대기 중인 모든 consumer를 깨웁니다. void Close() { std::scoped_lock lock(mutex_); closed_ = true; ready_.notify_all(); } + /// queue 포화로 제거된 항목의 누적 수를 thread-safe하게 반환합니다. [[nodiscard]] std::uint64_t dropped() const { std::scoped_lock lock(mutex_); return dropped_; diff --git a/RobotVisionPlatform/device/include/rv/interfaces.hpp b/RobotVisionPlatform/device/include/rv/interfaces.hpp index 2243a45..f59d853 100644 --- a/RobotVisionPlatform/device/include/rv/interfaces.hpp +++ b/RobotVisionPlatform/device/include/rv/interfaces.hpp @@ -9,33 +9,52 @@ namespace rv { +/// 카메라 종류와 무관하게 파이프라인이 프레임을 읽는 계약입니다. class ICamera { public: virtual ~ICamera() = default; + + /// 다음 프레임이 준비될 때까지 기다렸다 반환합니다. + /// @param stop 종료 요청을 전달하는 C++20 stop token입니다. + /// @return 캡처된 한 프레임입니다. + /// @throws std::exception 카메라 연결이나 decode가 실패한 경우입니다. virtual Frame Read(std::stop_token stop) = 0; }; +/// TensorRT, DeepStream, test detector가 공통으로 구현할 추론 계약입니다. class IDetector { public: virtual ~IDetector() = default; + + /// 프레임 하나를 동기적으로 추론합니다. 호출이 끝날 때까지 frame은 유효합니다. virtual std::vector Infer(const Frame& frame) = 0; + + /// event와 metric에 기록할 모델 식별자/버전을 반환합니다. [[nodiscard]] virtual std::string Version() const = 0; }; +/// 탐지 이벤트의 최종 목적지를 추상화한 계약입니다. class IEventSink { public: virtual ~IEventSink() = default; + + /// 이벤트를 전송합니다. + /// @return 목적지가 이벤트를 받았으면 true, 재시도가 필요하면 false입니다. virtual bool Publish(const DetectionEvent& event) = 0; }; +/// 실제 카메라 없이 pipeline을 시험하는 일정 FPS의 synthetic camera를 만듭니다. std::unique_ptr MakeSyntheticCamera(int width, int height, int fps); +/// 세 프레임마다 예제 객체를 반환하는 test detector를 만듭니다. std::unique_ptr MakeDemoDetector(); +/// 탐지 개수와 모델 정보를 stdout에 쓰는 sink를 만듭니다. std::unique_ptr MakeConsoleEventSink(); #ifdef RV_HAS_BOOST_HTTP +/// Boost.Beast로 JSON detection event를 HTTP POST하는 MVP sink를 만듭니다. +/// production에서는 연결 재사용, TLS, disk spool이 있는 gRPC adapter로 교체합니다. std::unique_ptr MakeHttpEventSink(std::string host, std::string port, std::string target); #endif } // namespace rv - diff --git a/RobotVisionPlatform/device/include/rv/pipeline.hpp b/RobotVisionPlatform/device/include/rv/pipeline.hpp index c83f3b3..32769c3 100644 --- a/RobotVisionPlatform/device/include/rv/pipeline.hpp +++ b/RobotVisionPlatform/device/include/rv/pipeline.hpp @@ -11,27 +11,48 @@ namespace rv { +/// Pipeline의 장치 식별자와 실시간 처리 정책입니다. struct PipelineOptions { std::string device_id{"jetson-dev-001"}; + /// 작게 유지할수록 처리량보다 최신 프레임과 낮은 지연을 우선합니다. std::size_t frame_queue_capacity{2}; + /// 향후 health heartbeat worker가 사용할 전송 주기입니다. std::chrono::milliseconds heartbeat_interval{5000}; }; +/// camera, detector, event sink의 수명주기와 worker thread를 관리합니다. +/// +/// 사용 예: +/// @code +/// Pipeline pipeline(options, MakeSyntheticCamera(640, 480, 30), +/// MakeDemoDetector(), MakeConsoleEventSink()); +/// pipeline.Start(); +/// // ... application work ... +/// pipeline.Stop(); +/// @endcode class Pipeline { public: + /// adapter 소유권을 Pipeline로 이전합니다. null adapter를 전달하면 안 됩니다. Pipeline(PipelineOptions options, std::unique_ptr camera, std::unique_ptr detector, std::unique_ptr sink); + /// 실행 중이면 먼저 Stop()하여 worker가 adapter보다 먼저 종료되게 합니다. ~Pipeline(); + // Worker와 adapter는 단일 소유이므로 Pipeline 복사를 금지합니다. Pipeline(const Pipeline&) = delete; Pipeline& operator=(const Pipeline&) = delete; + /// capture/inference worker를 시작합니다. 이미 시작됐으면 아무 작업도 하지 않습니다. void Start(); + /// 두 worker에 종료를 요청하고 join이 끝날 때까지 기다립니다. void Stop(); + /// lock-free atomic counter와 queue drop 수를 한 시점의 값으로 반환합니다. [[nodiscard]] PipelineStats Stats() const; private: + /// 카메라를 읽어 latest-frame queue에 넣는 producer loop입니다. void CaptureLoop(std::stop_token stop); + /// queue에서 프레임을 꺼내 추론하고 event sink에 보내는 consumer loop입니다. void InferenceLoop(std::stop_token stop); PipelineOptions options_; @@ -47,4 +68,3 @@ class Pipeline { }; } // namespace rv - diff --git a/RobotVisionPlatform/device/include/rv/types.hpp b/RobotVisionPlatform/device/include/rv/types.hpp index 4d0ac67..717410e 100644 --- a/RobotVisionPlatform/device/include/rv/types.hpp +++ b/RobotVisionPlatform/device/include/rv/types.hpp @@ -7,18 +7,30 @@ namespace rv { +/// 시스템 간 timestamp에 사용하는 실제 시각 clock입니다. +/// 성능 측정에는 시간이 역행할 수 없는 steady_clock을 별도로 사용해야 합니다. using Clock = std::chrono::system_clock; +/// 카메라가 한 번 촬영한 영상 프레임과 메타데이터입니다. struct Frame { + /// 장치 프로세스 안에서 단조 증가하는 프레임 번호입니다. std::uint64_t sequence{}; + /// 카메라 adapter가 프레임 획득을 완료한 UTC 기준 시각입니다. Clock::time_point captured_at{}; + /// pixel buffer의 가로/세로 크기입니다. int width{}; int height{}; + /// MVP의 CPU BGR byte 배열입니다. Jetson adapter에서는 복사 비용을 피하기 위해 + /// 이 필드 대신 NVMM/CUDA buffer handle을 소유하는 payload로 확장합니다. std::vector pixels; // MVP: CPU BGR. Jetson adapter uses an NVMM handle. }; +/// 정규화 좌표로 표현한 객체 하나의 탐지 결과입니다. +/// x/y는 좌상단이며 모든 좌표 값은 원본 크기와 무관하게 0.0~1.0을 사용합니다. struct Detection { + /// labels.txt와 같은 순서를 사용하는 사람이 읽을 수 있는 class 이름입니다. std::string label; + /// 모델이 계산한 신뢰도입니다. 보통 0.0~1.0 범위입니다. float confidence{}; float x{}; float y{}; @@ -26,6 +38,7 @@ struct Detection { float height{}; }; +/// 한 프레임의 탐지 결과를 서버로 전송하기 위한 묶음입니다. struct DetectionEvent { std::string device_id; std::uint64_t sequence{}; @@ -34,12 +47,16 @@ struct DetectionEvent { std::vector detections; }; +/// 파이프라인 상태를 외부에서 읽을 수 있는 누적 counter snapshot입니다. struct PipelineStats { + /// 카메라에서 성공적으로 받은 전체 프레임 수입니다. std::uint64_t captured{}; + /// detector에 전달한 전체 프레임 수입니다. std::uint64_t inferred{}; + /// sink가 성공으로 응답한 전체 이벤트 수입니다. std::uint64_t published{}; + /// queue가 가득 차 오래된 프레임을 제거한 횟수입니다. std::uint64_t dropped{}; }; } // namespace rv - diff --git a/RobotVisionPlatform/device/src/http_event_sink.cpp b/RobotVisionPlatform/device/src/http_event_sink.cpp index 1be058c..a3aec33 100644 --- a/RobotVisionPlatform/device/src/http_event_sink.cpp +++ b/RobotVisionPlatform/device/src/http_event_sink.cpp @@ -17,6 +17,8 @@ namespace net = boost::asio; using tcp = net::ip::tcp; std::string Escape(std::string_view value) { + // 이 MVP JSON writer가 사용하는 문자열 필드에서 최소한의 quote/backslash escaping을 합니다. + // production에서는 검증된 JSON serializer를 사용해 control character까지 처리해야 합니다. std::string result; for (char ch : value) { if (ch == '"' || ch == '\\') result.push_back('\\'); @@ -32,12 +34,14 @@ class HttpEventSink final : public IEventSink { bool Publish(const DetectionEvent& event) override { try { + // MVP는 호출마다 연결합니다. production 구현은 keep-alive/HTTP2/gRPC를 재사용합니다. net::io_context context; tcp::resolver resolver(context); beast::tcp_stream stream(context); stream.expires_after(std::chrono::seconds(3)); stream.connect(resolver.resolve(host_, port_)); + // DetectionEvent를 서버의 DetectionEventDto camelCase JSON 계약으로 변환합니다. std::ostringstream body; body << "{\"deviceId\":\"" << Escape(event.device_id) << "\",\"sequence\":" << event.sequence << ",\"modelVersion\":\"" << Escape(event.model_version) @@ -63,6 +67,7 @@ class HttpEventSink final : public IEventSink { http::read(stream, buffer, response); beast::error_code ignored; stream.socket().shutdown(tcp::socket::shutdown_both, ignored); + // 2xx만 성공으로 계산하여 pipeline의 published counter 의미를 일관되게 유지합니다. return response.result_int() >= 200 && response.result_int() < 300; } catch (...) { return false; // Production adapter adds bounded disk spool + retry/backoff. @@ -78,7 +83,7 @@ class HttpEventSink final : public IEventSink { std::unique_ptr MakeHttpEventSink(std::string host, std::string port, std::string target) { + // unique_ptr로 반환해 sink 수명과 정리를 Pipeline 한 곳에서 관리합니다. return std::make_unique(std::move(host), std::move(port), std::move(target)); } } // namespace rv - diff --git a/RobotVisionPlatform/device/src/main.cpp b/RobotVisionPlatform/device/src/main.cpp index 64a1c42..6fd8054 100644 --- a/RobotVisionPlatform/device/src/main.cpp +++ b/RobotVisionPlatform/device/src/main.cpp @@ -9,17 +9,20 @@ #include namespace { +// signal handler에서는 async-signal-safe한 sig_atomic_t 값만 바꿉니다. volatile std::sig_atomic_t running = 1; void HandleSignal(int) { running = 0; } } // namespace int main(int argc, char** argv) { + // systemd SIGTERM과 터미널 Ctrl+C(SIGINT)를 같은 정상 종료 경로로 연결합니다. std::signal(SIGINT, HandleSignal); std::signal(SIGTERM, HandleSignal); std::string device_id = "jetson-dev-001"; std::string server_host; std::string server_port = "5080"; + // 현재 CLI는 --option value 쌍만 받는 작은 parser입니다. for (int index = 1; index + 1 < argc; index += 2) { const std::string_view option = argv[index]; if (option == "--device-id") device_id = argv[index + 1]; @@ -29,6 +32,7 @@ int main(int argc, char** argv) { std::unique_ptr sink; #ifdef RV_HAS_BOOST_HTTP + // Boost가 빌드에 포함되고 server host가 있을 때만 HTTP sink를 선택합니다. if (!server_host.empty()) { sink = rv::MakeHttpEventSink(server_host, server_port, "/api/events/detections"); } @@ -40,11 +44,13 @@ int main(int argc, char** argv) { sink = rv::MakeConsoleEventSink(); } + // adapter를 생성해 Pipeline에 소유권을 넘깁니다. 실제 Jetson에서는 이 세 factory를 교체합니다. rv::Pipeline pipeline({.device_id = device_id, .frame_queue_capacity = 2}, rv::MakeSyntheticCamera(1280, 720, 15), rv::MakeDemoDetector(), std::move(sink)); pipeline.Start(); std::cout << "robot-vision device started; Ctrl+C to stop\n"; + // main thread는 signal만 감시하고 실제 작업은 jthread worker가 담당합니다. while (running) std::this_thread::sleep_for(std::chrono::milliseconds(200)); pipeline.Stop(); const auto stats = pipeline.Stats(); diff --git a/RobotVisionPlatform/device/src/pipeline.cpp b/RobotVisionPlatform/device/src/pipeline.cpp index e1082af..7c9caac 100644 --- a/RobotVisionPlatform/device/src/pipeline.cpp +++ b/RobotVisionPlatform/device/src/pipeline.cpp @@ -17,20 +17,25 @@ Pipeline::Pipeline(PipelineOptions options, std::unique_ptr camera, Pipeline::~Pipeline() { Stop(); } void Pipeline::Start() { + // Start를 두 번 호출해 worker가 중복 생성되는 것을 방지합니다. if (capture_thread_.joinable() || inference_thread_.joinable()) return; + // std::jthread는 callable에 stop_token을 자동으로 전달합니다. capture_thread_ = std::jthread([this](std::stop_token stop) { CaptureLoop(stop); }); inference_thread_ = std::jthread([this](std::stop_token stop) { InferenceLoop(stop); }); } void Pipeline::Stop() { + // 먼저 종료 의사를 전달한 뒤 queue를 닫아 Pop에서 기다리는 consumer도 깨웁니다. capture_thread_.request_stop(); inference_thread_.request_stop(); frames_.Close(); + // join은 worker가 adapter를 더 이상 사용하지 않을 때까지 기다려 수명 문제를 막습니다. if (capture_thread_.joinable()) capture_thread_.join(); if (inference_thread_.joinable()) inference_thread_.join(); } PipelineStats Pipeline::Stats() const { + // 서로 다른 worker가 counter를 수정하므로 atomic load로 data race를 피합니다. return {.captured = captured_.load(), .inferred = inferred_.load(), .published = published_.load(), @@ -40,6 +45,7 @@ PipelineStats Pipeline::Stats() const { void Pipeline::CaptureLoop(std::stop_token stop) { try { while (!stop.stop_requested()) { + // 실제 adapter는 여기서 V4L2/NvArgus/GStreamer 프레임을 기다립니다. auto frame = camera_->Read(stop); if (stop.stop_requested()) break; ++captured_; @@ -53,18 +59,20 @@ void Pipeline::CaptureLoop(std::stop_token stop) { void Pipeline::InferenceLoop(std::stop_token stop) { while (!stop.stop_requested()) { + // Pop은 새 프레임, queue close, stop 요청 중 하나가 발생할 때 깨어납니다. auto frame = frames_.Pop(stop); if (!frame) break; auto detections = detector_->Infer(*frame); ++inferred_; + // model version과 원본 sequence를 함께 보내 서버가 결과의 출처를 추적하게 합니다. DetectionEvent event{.device_id = options_.device_id, .sequence = frame->sequence, .captured_at = frame->captured_at, .model_version = detector_->Version(), .detections = std::move(detections)}; + // false는 전송 실패를 의미합니다. production sink는 자체 retry/spool 정책을 가져야 합니다. if (sink_->Publish(event)) ++published_; } } } // namespace rv - diff --git a/RobotVisionPlatform/device/src/synthetic_adapters.cpp b/RobotVisionPlatform/device/src/synthetic_adapters.cpp index e3d5ef1..b4705a3 100644 --- a/RobotVisionPlatform/device/src/synthetic_adapters.cpp +++ b/RobotVisionPlatform/device/src/synthetic_adapters.cpp @@ -9,10 +9,12 @@ namespace { class SyntheticCamera final : public ICamera { public: + /// width/height/fps만 흉내 내는 개발용 카메라입니다. SyntheticCamera(int width, int height, int fps) : width_(width), height_(height), interval_(1000 / (fps <= 0 ? 1 : fps)) {} Frame Read(std::stop_token stop) override { + // 실제 카메라의 frame interval을 모사해 worker가 무한 속도로 돌지 않게 합니다. std::this_thread::sleep_for(interval_); if (stop.stop_requested()) return {}; Frame frame{.sequence = ++sequence_, @@ -20,7 +22,7 @@ class SyntheticCamera final : public ICamera { .width = width_, .height = height_, .pixels = {}}; - // Synthetic mode avoids allocating full images; real adapters retain NVMM buffers. + // synthetic mode는 pixel을 사용하지 않으므로 큰 영상 배열 할당을 생략합니다. return frame; } @@ -34,6 +36,7 @@ class SyntheticCamera final : public ICamera { class DemoDetector final : public IDetector { public: std::vector Infer(const Frame& frame) override { + // 테스트에서 빈 결과와 탐지 결과를 모두 경험하도록 3의 배수 프레임만 탐지합니다. if (frame.sequence % 3 != 0) return {}; const auto phase = static_cast(frame.sequence % 100) / 100.0F; return {{.label = "demo-object", @@ -49,6 +52,7 @@ class DemoDetector final : public IDetector { class ConsoleEventSink final : public IEventSink { public: bool Publish(const DetectionEvent& event) override { + // stdout 출력이 성공했다고 간주하므로 pipeline smoke test에서 network가 필요 없습니다. std::cout << "device=" << event.device_id << " sequence=" << event.sequence << " model=" << event.model_version << " detections=" << event.detections.size() << '\n'; @@ -59,6 +63,7 @@ class ConsoleEventSink final : public IEventSink { } // namespace std::unique_ptr MakeSyntheticCamera(int width, int height, int fps) { + // factory 함수는 concrete type을 감춰 main이 interface에만 의존하게 합니다. return std::make_unique(width, height, fps); } std::unique_ptr MakeDemoDetector() { return std::make_unique(); } diff --git a/RobotVisionPlatform/device/tests/core_tests.cpp b/RobotVisionPlatform/device/tests/core_tests.cpp index 8d3e537..e508859 100644 --- a/RobotVisionPlatform/device/tests/core_tests.cpp +++ b/RobotVisionPlatform/device/tests/core_tests.cpp @@ -6,6 +6,7 @@ #include int main() { + // 용량 2에 세 값을 넣으면 가장 오래된 1이 제거되어야 합니다. rv::LatestQueue queue(2); assert(queue.Push(1)); assert(queue.Push(2)); @@ -14,6 +15,7 @@ int main() { std::stop_source stop; assert(queue.Pop(stop.get_token()).value() == 2); + // 외부 카메라나 서버 없이 adapter 조합과 thread 종료를 smoke test합니다. rv::Pipeline pipeline({.device_id = "test", .frame_queue_capacity = 2}, rv::MakeSyntheticCamera(32, 32, 100), rv::MakeDemoDetector(), rv::MakeConsoleEventSink()); @@ -25,4 +27,3 @@ int main() { assert(stats.inferred > 0); return 0; } - diff --git a/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh b/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh index 642c358..212900d 100755 --- a/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh +++ b/RobotVisionPlatform/device/tools/model/build_tensorrt_engine.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash +# Build and benchmark a target-specific FP16 TensorRT engine from an ONNX model. +# Run this on the deployment Jetson so the engine matches its TensorRT/GPU versions. set -euo pipefail +# Arguments are kept separate from trtexec options to avoid accidental shell expansion. onnx_path="" engine_path="" input_name="" @@ -11,9 +14,11 @@ max_shape="" trtexec_bin="${TRTEXEC_BIN:-trtexec}" usage() { + # Print the two supported modes: one fixed shape or a dynamic min/opt/max profile. echo "Usage: $0 --onnx MODEL --engine ENGINE --input NAME [--shape 1x3x640x640 | --min-shape ... --opt-shape ... --max-shape ...]" } +# Parse `--name value` pairs. Unknown options fail instead of being forwarded silently. while [[ $# -gt 0 ]]; do case "$1" in --onnx) onnx_path="$2"; shift 2 ;; @@ -45,6 +50,7 @@ fi mkdir -p "$(dirname "$engine_path")" build_shape_args=() run_shape_args=() +# Dynamic profiles are builder options; benchmarking uses the most common opt shape. if [[ -n "$fixed_shape" ]]; then build_shape_args+=("--shapes=${input_name}:${fixed_shape}") run_shape_args+=("--shapes=${input_name}:${fixed_shape}") @@ -58,6 +64,7 @@ else exit 2 fi +# First pass parses ONNX and serializes the optimized engine without benchmarking. "$trtexec_bin" \ "--onnx=$onnx_path" \ "--saveEngine=$engine_path" \ @@ -65,6 +72,7 @@ fi --skipInference \ "${build_shape_args[@]}" +# Second pass loads exactly that engine, warms it up, and measures steady-state latency. "$trtexec_bin" \ "--loadEngine=$engine_path" \ --warmUp=1000 \ diff --git a/RobotVisionPlatform/device/tools/model/create_manifest.py b/RobotVisionPlatform/device/tools/model/create_manifest.py index eff5bf0..c10fbfb 100644 --- a/RobotVisionPlatform/device/tools/model/create_manifest.py +++ b/RobotVisionPlatform/device/tools/model/create_manifest.py @@ -10,6 +10,7 @@ def sha256(path: Path) -> str: + """Return a streaming SHA-256 digest without loading the whole model into RAM.""" digest = hashlib.sha256() with path.open("rb") as source: for chunk in iter(lambda: source.read(1024 * 1024), b""): @@ -18,6 +19,7 @@ def sha256(path: Path) -> str: def main() -> int: + """Validate CLI metadata and write a reproducible model bundle manifest.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--model", type=Path, required=True) parser.add_argument("--model-id", required=True) @@ -33,10 +35,12 @@ def main() -> int: dimensions = [int(value) for value in args.input_shape.split(",")] if len(dimensions) != 4 or any(value <= 0 for value in dimensions): parser.error("--input-shape must contain four positive dimensions, e.g. 1,3,640,640") + # Empty lines are ignored, while the remaining order is preserved as the class index contract. labels = [line.strip() for line in args.labels.read_text(encoding="utf-8").splitlines() if line.strip()] if not labels: parser.error("labels file is empty") + # Keep preprocessing and postprocessing beside the artifact hash so runtime settings cannot drift. manifest = { "schemaVersion": 1, "modelId": args.model_id, diff --git a/RobotVisionPlatform/device/tools/model/export_ultralytics.py b/RobotVisionPlatform/device/tools/model/export_ultralytics.py index 99cc510..73c2b9d 100644 --- a/RobotVisionPlatform/device/tools/model/export_ultralytics.py +++ b/RobotVisionPlatform/device/tools/model/export_ultralytics.py @@ -11,6 +11,7 @@ def main() -> int: + """Export a checkpoint and copy the generated ONNX file to a stable bundle path.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--weights", type=Path, required=True) parser.add_argument("--output", type=Path, required=True) @@ -20,6 +21,7 @@ def main() -> int: parser.add_argument("--simplify", action="store_true") args = parser.parse_args() + # Ultralytics selects the task from checkpoint metadata and returns the generated file path. model = YOLO(str(args.weights)) exported = Path(model.export( format="onnx", @@ -29,6 +31,7 @@ def main() -> int: simplify=args.simplify, )) args.output.parent.mkdir(parents=True, exist_ok=True) + # model.export writes beside the checkpoint by default; copy it into our versioned bundle. if exported.resolve() != args.output.resolve(): shutil.copy2(exported, args.output) print(f"onnx={args.output}") diff --git a/RobotVisionPlatform/device/tools/model/inspect_onnx.py b/RobotVisionPlatform/device/tools/model/inspect_onnx.py index b151809..b8dda3d 100644 --- a/RobotVisionPlatform/device/tools/model/inspect_onnx.py +++ b/RobotVisionPlatform/device/tools/model/inspect_onnx.py @@ -11,6 +11,7 @@ def tensor_shape(value: onnx.ValueInfoProto) -> str: + """Convert ONNX dimension metadata to a readable `1x3x640x640` string.""" tensor_type = value.type.tensor_type dimensions: list[str] = [] for dimension in tensor_type.shape.dim: @@ -24,24 +25,30 @@ def tensor_shape(value: onnx.ValueInfoProto) -> str: def describe(kind: str, values: list[onnx.ValueInfoProto]) -> None: + """Print a compact contract line for each graph input or output.""" for value in values: element_type = TensorProto.DataType.Name(value.type.tensor_type.elem_type) print(f"{kind}: name={value.name} shape={tensor_shape(value)} type={element_type}") def main() -> int: + """Parse CLI arguments, check the graph, infer shapes, and print metadata.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("model", type=Path) parser.add_argument("--save-inferred", type=Path, help="Write a shape-inferred ONNX copy") args = parser.parse_args() + # load_external_data=True also reads large weights stored beside the .onnx file. model = onnx.load(args.model, load_external_data=True) + # checker catches invalid node links, types, and graph structure before deployment. checker.check_model(model) + # Shape inference fills output dimensions that can be derived from graph operators. inferred = shape_inference.infer_shapes(model) print(f"model={args.model}") print("opsets=" + ",".join(f"{item.domain or 'ai.onnx'}:{item.version}" for item in model.opset_import)) print(f"nodes={len(model.graph.node)} initializers={len(model.graph.initializer)}") + # Initializers are weights/constants and should not be reported as runtime inputs. initializer_names = {item.name for item in inferred.graph.initializer} describe("input", [item for item in inferred.graph.input if item.name not in initializer_names]) describe("output", list(inferred.graph.output)) diff --git a/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py b/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py index 53b4b2f..a9f5140 100644 --- a/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py +++ b/RobotVisionPlatform/device/tools/model/tests/test_create_manifest.py @@ -10,7 +10,10 @@ class CreateManifestTests(unittest.TestCase): + """Black-box tests for the dependency-free manifest CLI.""" + def test_cli_writes_hash_labels_and_input_contract(self) -> None: + """The CLI must preserve labels/shape and hash the exact artifact bytes.""" tool = Path(__file__).parents[1] / "create_manifest.py" with tempfile.TemporaryDirectory() as directory: root = Path(directory) @@ -20,6 +23,7 @@ def test_cli_writes_hash_labels_and_input_contract(self) -> None: model.write_bytes(b"small-test-model") labels.write_text("person\n\nforklift\n", encoding="utf-8") + # Run the public CLI instead of internal helpers to cover argument parsing and file output. subprocess.run( [ sys.executable, str(tool), diff --git a/RobotVisionPlatform/device/tools/model/validate_onnx.py b/RobotVisionPlatform/device/tools/model/validate_onnx.py index 7256949..50521ff 100644 --- a/RobotVisionPlatform/device/tools/model/validate_onnx.py +++ b/RobotVisionPlatform/device/tools/model/validate_onnx.py @@ -12,6 +12,7 @@ def parse_shapes(items: list[str]) -> dict[str, tuple[int, ...]]: + """Parse repeated `name=1,3,640,640` CLI values into concrete dimensions.""" result: dict[str, tuple[int, ...]] = {} for item in items: try: @@ -26,6 +27,7 @@ def parse_shapes(items: list[str]) -> dict[str, tuple[int, ...]]: def providers_for(name: str) -> list[str | tuple[str, dict[str, object]]]: + """Return ONNX Runtime providers in preferred-to-fallback priority order.""" if name == "cpu": return ["CPUExecutionProvider"] if name == "cuda": @@ -38,6 +40,7 @@ def providers_for(name: str) -> list[str | tuple[str, dict[str, object]]]: def concrete_shape(name: str, model_shape: list[int | str | None], overrides: dict[str, tuple[int, ...]]) -> tuple[int, ...]: + """Resolve a static model shape or require a CLI override for dynamic inputs.""" if name in overrides: return overrides[name] if any(not isinstance(value, int) or value <= 0 for value in model_shape): @@ -46,6 +49,7 @@ def concrete_shape(name: str, model_shape: list[int | str | None], overrides: di def numpy_type(ort_type: str) -> np.dtype: + """Map common ONNX Runtime tensor type names to NumPy dtypes.""" supported = { "tensor(float)": np.dtype(np.float32), "tensor(float16)": np.dtype(np.float16), @@ -58,6 +62,7 @@ def numpy_type(ort_type: str) -> np.dtype: def main() -> int: + """Create an inference session, build inputs, warm up, and benchmark outputs.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("model", type=Path) parser.add_argument("--provider", choices=("cpu", "cuda", "tensorrt"), default="cpu") @@ -69,6 +74,7 @@ def main() -> int: if args.runs < 1: parser.error("--runs must be at least 1") + # ONNX Runtime can silently fall back; fail early so GPU tests are not mistaken for CPU tests. required_provider = { "cpu": "CPUExecutionProvider", "cuda": "CUDAExecutionProvider", @@ -90,6 +96,7 @@ def main() -> int: feeds: dict[str, np.ndarray] = {} for index, item in enumerate(session.get_inputs()): if args.input_npy and len(session.get_inputs()) == 1: + # A saved preprocessed tensor gives more meaningful parity than random input. value = np.load(args.input_npy) else: shape = concrete_shape(item.name, item.shape, overrides) @@ -98,6 +105,7 @@ def main() -> int: feeds[item.name] = value print(f"input[{index}] name={item.name} shape={value.shape} dtype={value.dtype}") + # Exclude one-time graph/session initialization effects from the measured runs. session.run(None, feeds) # warm-up started = time.perf_counter() outputs: list[np.ndarray] = [] @@ -107,6 +115,7 @@ def main() -> int: for index, value in enumerate(outputs): numeric = np.asarray(value) + # Ignore NaN/Inf when calculating readable output statistics. finite = numeric[np.isfinite(numeric)] if np.issubdtype(numeric.dtype, np.number) else np.array([]) stats = "non-numeric-or-empty" if finite.size: diff --git a/RobotVisionPlatform/docs/README.md b/RobotVisionPlatform/docs/README.md index 395f23a..61b1ee1 100644 --- a/RobotVisionPlatform/docs/README.md +++ b/RobotVisionPlatform/docs/README.md @@ -5,6 +5,7 @@ | [getting-started-for-beginners.md](getting-started-for-beginners.md) | 처음 실행하는 사람을 위한 순서별 실습 | | [glossary.md](glossary.md) | 프로젝트에서 사용하는 용어 설명 | | [troubleshooting.md](troubleshooting.md) | 자주 발생하는 설치·실행 문제 | +| [../reference/README.md](../reference/README.md) | 함수·타입·명령 사전과 실제 소스 위치 | | [architecture.md](architecture.md) | 전체 구성, 경계, 데이터 흐름 | | [initial-release-guide.md](initial-release-guide.md) | v0.1.0 빌드·설치·검증·롤백 | | [jetson-deployment.md](jetson-deployment.md) | Orin Nano Super 준비와 서비스 운영 | diff --git a/RobotVisionPlatform/docs/getting-started-for-beginners.md b/RobotVisionPlatform/docs/getting-started-for-beginners.md index c9820a6..b35e25a 100644 --- a/RobotVisionPlatform/docs/getting-started-for-beginners.md +++ b/RobotVisionPlatform/docs/getting-started-for-beginners.md @@ -100,3 +100,4 @@ ctest --test-dir device/build --output-on-failure 6. [ONNX/TensorRT 모델 배포](../device/guides/README.md) 문제가 생기면 [문제 해결 문서](troubleshooting.md)를 먼저 확인합니다. +코드에서 모르는 함수나 타입은 [Reference 사전](../reference/README.md)에서 이름으로 찾을 수 있습니다. diff --git a/RobotVisionPlatform/reference/README.md b/RobotVisionPlatform/reference/README.md new file mode 100644 index 0000000..a4125f5 --- /dev/null +++ b/RobotVisionPlatform/reference/README.md @@ -0,0 +1,38 @@ +# Robot Vision Reference + +프로젝트에서 보이는 함수·타입·명령을 이름으로 찾아보는 사전입니다. 처음부터 끝까지 읽기보다 편집기 검색(`Ctrl+F`)으로 필요한 항목을 찾으세요. + +## 언어별 사전 + +| 문서 | 찾을 수 있는 내용 | +|---|---| +| [C++20 장치 코드](cpp20-device-reference.md) | interface, smart pointer, move, jthread, stop_token, queue, atomic | +| [C# 서버 코드](csharp-server-reference.md) | record, DI, ConcurrentDictionary, Channel, BackgroundService, SignalR | +| [Python 모델 도구](python-model-tools-reference.md) | argparse, Path, hashing, ONNX, ONNX Runtime, NumPy | +| [스크립트·프로토콜](scripts-and-protocol-reference.md) | PowerShell, Bash, CMake, protobuf, gRPC 기본 사용법 | + +## 기능에서 역으로 찾기 + +| 하고 싶은 일 | 찾아볼 항목 | 실제 시작 파일 | +|---|---|---| +| 카메라 종류 추가 | `ICamera`, `override`, factory | [`interfaces.hpp`](../device/include/rv/interfaces.hpp) | +| TensorRT detector 추가 | `IDetector`, `unique_ptr` | [`interfaces.hpp`](../device/include/rv/interfaces.hpp) | +| 프레임 지연 제어 | `LatestQueue`, `condition_variable_any` | [`bounded_queue.hpp`](../device/include/rv/bounded_queue.hpp) | +| 장치 event 수신 | Minimal API `MapPost` | [`Program.cs`](../server/src/RobotVision.Server.Api/Program.cs) | +| 최신 장치 상태 저장 | `ConcurrentDictionary`, `AddOrUpdate` | [`DeviceRegistry.cs`](../server/src/RobotVision.Server.Api/DeviceRegistry.cs) | +| 화면에 실시간 알림 | `Channel`, `BackgroundService`, SignalR | [`DetectionFanoutWorker.cs`](../server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs) | +| ONNX 입출력 확인 | `onnx.load`, `checker`, shape inference | [`inspect_onnx.py`](../device/tools/model/inspect_onnx.py) | +| ONNX 추론 | `InferenceSession`, `session.run` | [`validate_onnx.py`](../device/tools/model/validate_onnx.py) | +| TensorRT engine 생성 | `trtexec`, dynamic shape | [`build_tensorrt_engine.sh`](../device/tools/model/build_tensorrt_engine.sh) | +| 통신 필드 추가 | protobuf `message`, `oneof` | [`device.proto`](../shared/proto/vision/v1/device.proto) | + +## 표기 규칙 + +- `입력`: 함수가 받는 값 +- `반환`: 함수가 호출자에게 돌려주는 값 +- `수명`: 객체가 언제까지 유효한지 +- `thread-safe`: 여러 thread에서 동시에 사용 가능한지 +- `주의`: 이 프로젝트에서 자주 발생할 수 있는 실수 + +코드 동작의 전체 흐름은 [시스템 아키텍처](../docs/architecture.md), 모델 배포 흐름은 [Device 모델 배포 가이드](../device/guides/README.md)를 함께 참고합니다. + diff --git a/RobotVisionPlatform/reference/cpp20-device-reference.md b/RobotVisionPlatform/reference/cpp20-device-reference.md new file mode 100644 index 0000000..2ba537b --- /dev/null +++ b/RobotVisionPlatform/reference/cpp20-device-reference.md @@ -0,0 +1,187 @@ +# C++20 장치 코드 사전 + +## `class`와 interface + +`ICamera`, `IDetector`, `IEventSink`는 구현이 반드시 제공해야 할 함수만 선언하는 interface 역할입니다. + +```cpp +class IDetector { + public: + virtual ~IDetector() = default; + virtual std::vector Infer(const Frame& frame) = 0; +}; +``` + +- `virtual`: 실제 객체의 override 함수를 호출하게 합니다. +- `= 0`: 구현이 없는 pure virtual 함수라는 뜻입니다. +- virtual destructor: interface pointer로 삭제해도 concrete destructor가 실행됩니다. +- 위치: [`interfaces.hpp`](../device/include/rv/interfaces.hpp) + +새 detector는 다음 형태입니다. + +```cpp +class TensorRtDetector final : public rv::IDetector { + public: + std::vector Infer(const rv::Frame& frame) override; + std::string Version() const override { return "person-detector/1.0.0"; } +}; +``` + +`final`은 더 이상 상속하지 않음을, `override`는 base 함수와 signature가 정확히 같은지 compiler가 검사함을 뜻합니다. + +## `struct`와 aggregate initialization + +단순 데이터 묶음은 `struct`로 선언하고 지정 초기화할 수 있습니다. + +```cpp +rv::Detection detection{ + .label = "person", + .confidence = 0.92F, + .x = 0.1F, + .y = 0.2F, + .width = 0.3F, + .height = 0.5F}; +``` + +- 멤버는 선언된 순서를 지켜 지정합니다. +- `0.92F`의 `F`는 float literal임을 나타냅니다. +- 위치: [`types.hpp`](../device/include/rv/types.hpp) + +## `std::unique_ptr` + +객체 소유자가 한 곳뿐임을 나타내는 smart pointer입니다. + +```cpp +auto detector = std::make_unique(); +std::unique_ptr base = std::move(detector); +``` + +- `std::make_unique`: heap 객체 생성과 pointer wrapping을 한 번에 합니다. +- `std::move`: 소유권을 복사하지 않고 이전합니다. 이전 pointer는 비게 됩니다. +- Pipeline이 adapter 소유권을 가지므로 raw `new/delete`가 필요 없습니다. +- 위치: [`pipeline.hpp`](../device/include/rv/pipeline.hpp) + +## `const T&` + +복사 없이 읽기 전용으로 전달하는 reference입니다. + +```cpp +std::vector Infer(const Frame& frame); +``` + +- `Frame` 전체 pixel buffer를 복사하지 않습니다. +- 함수가 반환되기 전까지만 `frame`을 사용해야 합니다. +- `const`이므로 함수 내부에서 frame을 수정할 수 없습니다. + +## `std::jthread` + +C++20의 자동 join thread입니다. + +```cpp +worker = std::jthread([this](std::stop_token stop) { + WorkLoop(stop); +}); +``` + +- 생성 즉시 lambda를 새 thread에서 실행합니다. +- destructor는 종료 요청 후 join합니다. +- 프로젝트는 명시적 `Stop()`으로 종료 순서를 분명하게 합니다. +- 위치: [`pipeline.cpp`](../device/src/pipeline.cpp) + +## `std::stop_token`과 `request_stop()` + +worker에게 강제 종료 대신 협력적 종료를 요청합니다. + +```cpp +worker.request_stop(); +while (!stop.stop_requested()) { + // 한 단위 작업 +} +``` + +stop 요청은 exception이나 thread kill이 아닙니다. loop와 blocking wait가 token을 확인해야 종료됩니다. + +## `std::condition_variable_any::wait` + +queue가 비어 있을 때 CPU를 소비하는 반복 확인 대신 thread를 잠재웁니다. + +```cpp +ready.wait(lock, stop, [this] { return closed || !items.empty(); }); +``` + +- `lock`: 기다리는 동안 mutex를 풀고, 깨어날 때 다시 잡습니다. +- `stop`: 종료 요청도 wake-up 조건입니다. +- predicate: spurious wake-up이 생겨도 실제 조건을 다시 검사합니다. +- 위치: [`bounded_queue.hpp`](../device/include/rv/bounded_queue.hpp) + +## `std::scoped_lock`과 `std::unique_lock` + +```cpp +std::scoped_lock lock(mutex); // scope 끝에서 자동 unlock +std::unique_lock lock(mutex); // wait처럼 unlock/relock이 필요한 경우 +``` + +직접 `mutex.lock()`/`unlock()`을 호출하면 중간 exception이나 return에서 unlock을 잊을 수 있습니다. + +## `std::optional` + +“값이 있음”과 “정상적으로 값이 없음”을 모두 표현합니다. + +```cpp +auto value = queue.Pop(stop); +if (!value) break; +Use(*value); +``` + +이 프로젝트에서 `nullopt`는 queue close 또는 stop으로 consumer가 끝나야 함을 뜻합니다. + +## `std::atomic_uint64_t` + +여러 thread가 counter를 수정할 때 data race를 막습니다. + +```cpp +std::atomic_uint64_t captured{}; +++captured; +auto snapshot = captured.load(); +``` + +여러 필드가 반드시 같은 순간의 값이어야 한다면 atomic 여러 개만으로는 부족하고 별도 lock/snapshot 설계가 필요합니다. + +## `std::move` + +큰 buffer 또는 vector의 내부 자원을 새 객체로 이전합니다. + +```cpp +frames.Push(std::move(frame)); +event.detections = std::move(detections); +``` + +move 이후 원본은 유효하지만 내용은 보장되지 않으므로 다시 읽지 않습니다. + +## factory 함수 + +```cpp +std::unique_ptr MakeSyntheticCamera(int width, int height, int fps); +``` + +호출자가 concrete class 이름을 몰라도 interface 객체를 만들 수 있습니다. 실제 adapter로 교체해도 `main`과 `Pipeline` 변경을 줄여 줍니다. + +## `[[nodiscard]]` + +반환값을 무시하면 compiler가 경고할 수 있게 합니다. + +```cpp +[[nodiscard]] PipelineStats Stats() const; +``` + +상태나 오류 결과를 실수로 버리는 것을 방지할 때 사용합니다. + +## 종료 signal + +```cpp +volatile std::sig_atomic_t running = 1; +void HandleSignal(int) { running = 0; } +``` + +signal handler 안에서는 logging, allocation, mutex 같은 일반 함수를 호출하면 안전하지 않을 수 있습니다. handler는 flag만 바꾸고 정상 thread가 `Pipeline::Stop()`을 호출합니다. + diff --git a/RobotVisionPlatform/reference/csharp-server-reference.md b/RobotVisionPlatform/reference/csharp-server-reference.md new file mode 100644 index 0000000..87b041b --- /dev/null +++ b/RobotVisionPlatform/reference/csharp-server-reference.md @@ -0,0 +1,158 @@ +# C# 서버 코드 사전 + +## `record` + +값 중심의 불변 데이터 계약에 사용합니다. + +```csharp +public sealed record BoundingBox(float X, float Y, float Width, float Height); +``` + +- 같은 필드 값을 가진 record끼리 값 비교가 가능합니다. +- request/response DTO와 snapshot에 적합합니다. +- `sealed`는 상속을 막아 계약을 단순하게 유지합니다. +- 위치: [`DeviceContracts.cs`](../server/src/RobotVision.Server.Contracts/DeviceContracts.cs) + +## Dependency Injection과 생성자 주입 + +ASP.NET Core가 필요한 객체를 constructor parameter로 제공합니다. + +```csharp +public sealed class Worker( + Channel channel, + ILogger logger) : BackgroundService +``` + +`Program.cs`의 `AddSingleton`, `AddHostedService` 등록을 보고 객체를 생성합니다. 코드에서 직접 `new Worker(...)`할 필요가 없습니다. + +## `AddSingleton()` + +application lifetime 동안 객체 하나를 공유합니다. + +```csharp +builder.Services.AddSingleton(); +``` + +장치 최신 상태처럼 모든 request가 같은 데이터를 봐야 할 때 적합합니다. request별 상태에는 scoped lifetime을 사용해야 합니다. + +## `ConcurrentDictionary` + +동시에 여러 request가 읽고 쓸 수 있는 thread-safe dictionary입니다. + +```csharp +private readonly ConcurrentDictionary _devices = new(); +``` + +여러 작업을 묶은 복합 불변식까지 자동으로 보호하는 것은 아닙니다. 이 프로젝트는 `AddOrUpdate` 한 번 안에서 최신 sequence를 선택합니다. + +## `AddOrUpdate` + +key가 없으면 추가하고, 있으면 현재 값을 받아 새 값을 계산합니다. + +```csharp +devices.AddOrUpdate(id, newValue, (_, current) => + sequence >= current.LastSequence ? newValue : current); +``` + +늦게 도착한 낮은 sequence event가 최신 상태를 덮어쓰지 못하게 합니다. + +## `TryGetValue` / `TryGet` + +exception 없이 값 존재 여부와 값을 함께 반환하는 패턴입니다. + +```csharp +if (registry.TryGet(deviceId, out var snapshot)) +{ + // snapshot 사용 +} +``` + +`out` parameter는 함수가 호출자 변수에 값을 채워 줍니다. + +## `Channel` + +비동기 producer-consumer queue입니다. + +```csharp +var channel = Channel.CreateBounded(1024); +channel.Writer.TryWrite(item); +await foreach (var item in channel.Reader.ReadAllAsync(token)) { } +``` + +- HTTP endpoint가 producer, `DetectionFanoutWorker`가 consumer입니다. +- bounded channel은 느린 UI 때문에 memory가 계속 늘어나는 것을 막습니다. +- `DropOldest`는 관제 화면에서 최신 상태를 우선하는 정책입니다. + +## `BackgroundService` + +ASP.NET Core application과 함께 시작·종료되는 장기 worker base class입니다. + +```csharp +protected override async Task ExecuteAsync(CancellationToken stoppingToken) +``` + +`stoppingToken`을 모든 비동기 wait/전송에 전달해야 application이 빠르게 종료됩니다. + +## `async`, `await`, `Task` + +I/O가 끝날 때까지 thread를 막지 않고 나중에 계속 실행합니다. + +```csharp +await hub.Clients.Group(group).SendAsync("detection", item, token); +``` + +`await`를 생략한 fire-and-forget 작업은 exception과 lifetime을 잃기 쉬우므로 background worker에서는 피합니다. + +## `CancellationToken` + +C++ `stop_token`과 비슷한 협력적 취소 신호입니다. + +```csharp +await operation(stoppingToken); +``` + +취소는 실패와 구분해야 하므로 worker는 `OperationCanceledException`을 일반 오류로 기록하지 않습니다. + +## Minimal API `MapGet` / `MapPost` + +route와 handler를 한 곳에 선언합니다. + +```csharp +app.MapGet("/api/devices", (DeviceRegistry registry) => Results.Ok(registry.List())); +app.MapPost("/api/events", (EventDto item) => Results.Accepted()); +``` + +parameter는 route, body 또는 DI에서 자동 binding됩니다. public API가 커지면 endpoint group과 validator로 분리합니다. + +## `Results.Ok`, `Accepted`, `BadRequest`, `NotFound` + +| 함수 | HTTP | 의미 | +|---|---:|---| +| `Results.Ok(value)` | 200 | 조회 성공 | +| `Results.Accepted(location)` | 202 | 수집했지만 후속 처리는 비동기 | +| `Results.BadRequest(value)` | 400 | client 입력 오류 | +| `Results.NotFound()` | 404 | 장치 없음 | + +## SignalR `Hub`와 group + +SignalR Hub는 서버가 연결된 client의 함수를 호출할 수 있게 합니다. + +```csharp +await Groups.AddToGroupAsync(Context.ConnectionId, "device:jetson-001"); +await hub.Clients.Group("device:jetson-001").SendAsync("detection", item); +``` + +group을 사용하면 모든 client가 아닌 특정 장치 화면에만 event를 보낼 수 있습니다. + +## `ILogger` 구조화 logging + +```csharp +logger.LogError(error, "Failed for {DeviceId}", deviceId); +``` + +문자열 보간보다 `{DeviceId}` template을 사용하면 log backend가 필드를 검색 가능한 값으로 보존합니다. + +## MAUI `MauiApp.CreateBuilder()`와 `BlazorWebView` + +`MauiApp.CreateBuilder()`는 native application, DI, logging을 구성합니다. `AddMauiBlazorWebView()`는 Razor component를 native WebView 안에서 실행할 service를 등록합니다. 현재 앱은 골격이며 SignalR/WebRTC 연결은 다음 단계입니다. + diff --git a/RobotVisionPlatform/reference/python-model-tools-reference.md b/RobotVisionPlatform/reference/python-model-tools-reference.md new file mode 100644 index 0000000..9a38f01 --- /dev/null +++ b/RobotVisionPlatform/reference/python-model-tools-reference.md @@ -0,0 +1,171 @@ +# Python 모델 도구 사전 + +## `argparse.ArgumentParser` + +CLI 옵션을 선언하고 자동 도움말과 타입 변환을 제공합니다. + +```python +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument("model", type=Path) +parser.add_argument("--runs", type=int, default=10) +args = parser.parse_args() +``` + +사용자는 `python tool.py --help`로 옵션을 확인합니다. 잘못된 필수 옵션은 실행 전에 오류가 됩니다. + +## `pathlib.Path` + +운영체제와 무관하게 파일 경로를 다룹니다. + +```python +model = Path("models/model.onnx") +model.exists() +model.parent.mkdir(parents=True, exist_ok=True) +data = model.read_bytes() +``` + +문자열 경로 결합보다 Windows와 Linux separator 차이를 줄여 줍니다. + +## type hint + +```python +def sha256(path: Path) -> str: +``` + +`path`는 Path, 반환값은 str이라는 설명입니다. Python runtime이 자동 강제하지 않으므로 editor와 type checker의 도움을 받습니다. + +## docstring + +함수 첫 줄의 문자열은 함수 사용 목적을 설명합니다. + +```python +def parse_shapes(items: list[str]) -> dict[str, tuple[int, ...]]: + """Parse repeated shape options into concrete dimensions.""" +``` + +`help(parse_shapes)`나 IDE hover에서 볼 수 있습니다. + +## `hashlib.sha256` + +파일 내용의 고정 길이 fingerprint를 계산합니다. + +```python +digest = hashlib.sha256() +with path.open("rb") as source: + for chunk in iter(lambda: source.read(1024 * 1024), b""): + digest.update(chunk) +checksum = digest.hexdigest() +``` + +큰 ONNX 파일을 한 번에 RAM에 올리지 않고 1 MiB씩 읽습니다. checksum은 손상 확인용이며 배포 신뢰성에는 별도 전자서명이 필요합니다. + +## `json.dumps` + +Python dictionary를 JSON 문자열로 변환합니다. + +```python +text = json.dumps(manifest, ensure_ascii=False, indent=2) + "\n" +``` + +- `ensure_ascii=False`: 한글을 읽을 수 있게 유지합니다. +- `indent=2`: 사람이 검토하기 쉬운 formatting입니다. + +## `onnx.load` + +```python +model = onnx.load(path, load_external_data=True) +``` + +ONNX graph와 weight를 읽습니다. `load_external_data=True`는 큰 weight가 외부 파일로 분리된 모델도 함께 읽습니다. + +## `onnx.checker.check_model` + +graph 연결, operator schema, type 등 ONNX 구조가 유효한지 확인합니다. + +```python +checker.check_model(model) +``` + +통과는 정확도가 좋다는 뜻이 아니라 “ONNX 구조가 규칙에 맞다”는 뜻입니다. + +## `shape_inference.infer_shapes` + +입력과 operator 정보로 계산 가능한 중간/output shape를 채웁니다. + +```python +inferred = shape_inference.infer_shapes(model) +``` + +custom operator나 data-dependent shape는 모두 추론되지 않을 수 있습니다. + +## `onnxruntime.InferenceSession` + +ONNX graph를 지정 execution provider에서 실행할 session으로 준비합니다. + +```python +session = ort.InferenceSession( + "model.onnx", + providers=["CUDAExecutionProvider", "CPUExecutionProvider"], +) +``` + +provider는 앞에서부터 우선합니다. 프로젝트 도구는 요청한 GPU provider가 없을 때 CPU로 조용히 대체하지 않고 오류를 냅니다. + +## `session.get_inputs()` / `get_outputs()` + +모델의 runtime tensor 계약을 읽습니다. + +```python +for item in session.get_inputs(): + print(item.name, item.shape, item.type) +``` + +manifest의 input 이름·shape·type과 비교할 때 사용합니다. + +## `session.run` + +```python +outputs = session.run(None, {"images": input_array}) +``` + +- 첫 인자 `None`: 모든 output을 반환합니다. +- dictionary key: 정확한 ONNX input tensor 이름입니다. +- value: shape/type이 맞는 NumPy array입니다. + +첫 실행에는 graph 최적화와 cache 준비 시간이 포함될 수 있어 benchmark 전에 warm-up합니다. + +## NumPy `default_rng`, `astype`, `isfinite` + +```python +rng = np.random.default_rng(42) +input_array = rng.random(shape).astype(np.float32) +finite = output[np.isfinite(output)] +``` + +- 고정 seed는 같은 random input을 재현합니다. +- `astype`은 ONNX input type에 맞춥니다. +- `isfinite`는 NaN/Inf를 통계에서 분리합니다. + +random input은 실행 가능성 검사에만 적합합니다. 모델 정확도 비교에는 실제 전처리를 적용한 고정 `.npy` tensor를 사용합니다. + +## `subprocess.run(check=True)` + +테스트가 CLI를 별도 process로 실행합니다. + +```python +subprocess.run([sys.executable, str(tool), "--help"], check=True) +``` + +`check=True`이면 command 실패가 exception이 되어 test도 실패합니다. 문자열 한 줄보다 argument list가 shell quoting에 안전합니다. + +## `tempfile.TemporaryDirectory` + +테스트 전용 파일을 만들고 scope가 끝나면 자동 정리합니다. + +```python +with tempfile.TemporaryDirectory() as directory: + root = Path(directory) +``` + +실제 model directory를 오염시키지 않고 file I/O를 검증할 수 있습니다. + diff --git a/RobotVisionPlatform/reference/scripts-and-protocol-reference.md b/RobotVisionPlatform/reference/scripts-and-protocol-reference.md new file mode 100644 index 0000000..1e8dac5 --- /dev/null +++ b/RobotVisionPlatform/reference/scripts-and-protocol-reference.md @@ -0,0 +1,138 @@ +# 스크립트·프로토콜 사전 + +## PowerShell `$ErrorActionPreference = 'Stop'` + +native가 아닌 PowerShell command 오류도 즉시 script 실패로 처리합니다. + +```powershell +$ErrorActionPreference = 'Stop' +``` + +실패한 build 이후 다음 배포 명령이 계속 실행되는 것을 막습니다. + +## PowerShell `$PSScriptRoot` + +현재 script 파일이 있는 폴더입니다. + +```powershell +$root = Split-Path -Parent $PSScriptRoot +``` + +사용자가 어느 폴더에서 script를 실행해도 프로젝트 경로를 찾을 수 있습니다. + +## `Invoke-WebRequest` + +```powershell +$response = Invoke-WebRequest -Uri $url -Method Post -ContentType 'application/json' -Body $body +``` + +HTTP 요청을 보내고 status/body/header를 반환합니다. [`send-demo-event.ps1`](../scripts/send-demo-event.ps1)은 수집 API smoke test에 사용합니다. + +## Bash `set -euo pipefail` + +```bash +set -euo pipefail +``` + +- `-e`: command 실패 시 종료 +- `-u`: 정의되지 않은 변수 사용 시 종료 +- `pipefail`: pipeline 중간 command 실패도 전체 실패 + +## Bash `[[ ... ]]`와 `case` + +```bash +while [[ $# -gt 0 ]]; do + case "$1" in + --onnx) onnx_path="$2"; shift 2 ;; + *) exit 2 ;; + esac +done +``` + +CLI option을 순서대로 읽습니다. 변수는 공백이 포함된 경로를 보호하기 위해 항상 `"$value"`로 인용합니다. + +## CMake `cmake -S`, `-B`, `--build` + +```bash +cmake -S device -B device/build -G Ninja -DCMAKE_BUILD_TYPE=Release +cmake --build device/build --parallel +``` + +- `-S`: source의 `CMakeLists.txt` 위치 +- `-B`: 생성 파일과 binary가 들어갈 build 폴더 +- `-G Ninja`: 사용할 build backend +- source tree와 build 결과를 분리합니다. + +## CTest + +```bash +ctest --test-dir device/build --output-on-failure +``` + +CMake의 `add_test`로 등록된 실행 파일을 수행합니다. `--output-on-failure`는 성공 로그는 줄이고 실패 원인을 보여 줍니다. + +## `trtexec` + +TensorRT에 포함된 model parser, engine builder, benchmark CLI입니다. + +```bash +trtexec --onnx=model.onnx --saveEngine=model.engine --fp16 --shapes=images:1x3x640x640 +trtexec --loadEngine=model.engine --shapes=images:1x3x640x640 +``` + +engine은 대상 Jetson에서 생성합니다. 자세한 설명은 [TensorRT 가이드](../device/guides/tensorrt-on-jetson.md)를 봅니다. + +## protobuf `message` + +```proto +message Detection { + string label = 1; + float confidence = 2; +} +``` + +- 각 field 번호는 wire format의 identity입니다. +- 이미 배포한 번호를 다른 의미로 재사용하면 안 됩니다. +- 이름 변경보다 번호/타입 호환성이 더 중요합니다. + +## protobuf `oneof` + +```proto +oneof payload { + Heartbeat heartbeat = 10; + DetectionBatch detections = 11; +} +``` + +한 envelope에 여러 종류 중 하나만 들어가게 합니다. 새 payload는 기존 번호를 건드리지 않고 새 번호로 추가합니다. + +## gRPC bidirectional streaming + +```proto +rpc Connect(stream DeviceEnvelope) returns (stream ServerCommand); +``` + +- request 앞 `stream`: 장치가 여러 메시지를 계속 전송 +- response 앞 `stream`: 서버도 여러 명령을 계속 반환 +- 장치가 연결을 시작하므로 방화벽/NAT 환경에서 관리하기 쉽습니다. +- 영상 binary를 이 stream에 계속 넣기보다 WebRTC media plane과 분리합니다. + +## `google.protobuf.Timestamp` + +언어별 문자열 timestamp 대신 protobuf 표준 UTC timestamp를 사용합니다. `captured_at`은 카메라 촬영 시각, `sent_at`은 envelope 전송 시각이므로 둘의 차이로 장치 내부 지연을 추정할 수 있습니다. + +## GitHub Actions job/step + +```yaml +jobs: + device: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - run: cmake --build device/build +``` + +- job은 독립 runner에서 실행되는 작업 묶음입니다. +- step은 job 안에서 순서대로 실행됩니다. +- 로컬 test script와 CI command를 같은 형태로 유지하면 환경 차이를 줄일 수 있습니다. + diff --git a/RobotVisionPlatform/scripts/build.ps1 b/RobotVisionPlatform/scripts/build.ps1 index efbae10..84e1472 100644 --- a/RobotVisionPlatform/scripts/build.ps1 +++ b/RobotVisionPlatform/scripts/build.ps1 @@ -1,14 +1,17 @@ $ErrorActionPreference = 'Stop' +# Resolve every path from the script location so the command works from any directory. $root = Split-Path -Parent $PSScriptRoot +# Build the dependency-light server solution first. dotnet build "$root/server/RobotVision.Server.slnx" --configuration Release +# A normal PowerShell may not expose MSVC; Developer PowerShell adds `cl` to PATH. $compiler = Get-Command cl, g++, clang++ -ErrorAction SilentlyContinue | Select-Object -First 1 if ($null -eq $compiler) { Write-Warning 'C++ compiler was not found. Run from a Visual Studio Developer PowerShell or install LLVM/GCC.' exit 0 } +# Disable optional Boost HTTP so the portable C++ core is always testable. cmake -S "$root/device" -B "$root/device/build" -DRV_ENABLE_BOOST_HTTP=OFF cmake --build "$root/device/build" --config Release --parallel - diff --git a/RobotVisionPlatform/scripts/build.sh b/RobotVisionPlatform/scripts/build.sh index 05e9663..1c5a7bf 100755 --- a/RobotVisionPlatform/scripts/build.sh +++ b/RobotVisionPlatform/scripts/build.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash +# Build both the .NET server and portable C++ device core on Linux/Jetson. set -euo pipefail +# BASH_SOURCE makes paths independent of the caller's current directory. repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" dotnet build "$repo_root/server/RobotVision.Server.slnx" --configuration Release cmake -S "$repo_root/device" -B "$repo_root/device/build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DRV_ENABLE_BOOST_HTTP=OFF cmake --build "$repo_root/device/build" --parallel - diff --git a/RobotVisionPlatform/scripts/send-demo-event.ps1 b/RobotVisionPlatform/scripts/send-demo-event.ps1 index acbc0f8..7f1c1c2 100644 --- a/RobotVisionPlatform/scripts/send-demo-event.ps1 +++ b/RobotVisionPlatform/scripts/send-demo-event.ps1 @@ -1,9 +1,12 @@ param( + # Base URL of the locally running ASP.NET Core ingest server. [string]$ServerUrl = 'http://localhost:5080', + # Device ID shown on the monitoring page. [string]$DeviceId = 'beginner-demo-001' ) $ErrorActionPreference = 'Stop' +# PowerShell object -> JSON avoids quote escaping mistakes in a handwritten JSON string. $body = @{ deviceId = $DeviceId sequence = 1 @@ -13,6 +16,7 @@ $body = @{ ) } | ConvertTo-Json -Depth 4 +# POST the same camelCase contract used by the C++ HTTP event sink. $response = Invoke-WebRequest ` -Uri "$($ServerUrl.TrimEnd('/'))/api/events/detections" ` -Method Post ` @@ -20,4 +24,3 @@ $response = Invoke-WebRequest ` -Body $body Write-Host "Demo event accepted: HTTP $($response.StatusCode), device=$DeviceId" - diff --git a/RobotVisionPlatform/scripts/test.ps1 b/RobotVisionPlatform/scripts/test.ps1 index d0a60ce..06f8392 100644 --- a/RobotVisionPlatform/scripts/test.ps1 +++ b/RobotVisionPlatform/scripts/test.ps1 @@ -1,8 +1,10 @@ $ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot +# Run fast server behavior and dependency-free model-manifest tests. dotnet run --project "$root/server/tests/RobotVision.Server.Tests" --configuration Release python -m unittest discover -s "$root/device/tools/model/tests" -v +# C++ tests are optional on Windows machines without a configured compiler/build tree. if (Test-Path "$root/device/build/CTestTestfile.cmake") { ctest --test-dir "$root/device/build" -C Release --output-on-failure } else { diff --git a/RobotVisionPlatform/scripts/test.sh b/RobotVisionPlatform/scripts/test.sh index 93c40a7..0f211ca 100755 --- a/RobotVisionPlatform/scripts/test.sh +++ b/RobotVisionPlatform/scripts/test.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# Run server, model-tool, and already-configured C++ tests; stop at the first failure. set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" dotnet run --project "$repo_root/server/tests/RobotVision.Server.Tests" --configuration Release diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml index 459f3d3..b132d65 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml @@ -2,6 +2,6 @@ + - diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs index 21cd909..5fb9fe5 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/App.xaml.cs @@ -2,10 +2,10 @@ namespace RobotVision.Dashboard.Maui; public partial class App : Application { + /// XAML resource를 초기화하고 첫 화면을 생성합니다. public App() { InitializeComponent(); MainPage = new MainPage(); } } - diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor index 747ec1e..382e737 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/Main.razor @@ -1,4 +1,4 @@ +@* This Razor component is the shared starting point for future SignalR/WebRTC monitoring UI. *@

Robot Vision

MAUI Blazor Hybrid 관제 클라이언트 골격입니다.

다음 단계에서 SignalR client와 WebRTC player를 이 공유 Razor UI에 연결합니다.

- diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml index 5e529b7..f2f9e13 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml @@ -3,10 +3,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:RobotVision.Dashboard.Maui" x:Class="RobotVision.Dashboard.Maui.MainPage"> + - diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs index 2a09766..aa09864 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MainPage.xaml.cs @@ -1,3 +1,8 @@ namespace RobotVision.Dashboard.Maui; -public partial class MainPage : ContentPage { public MainPage() => InitializeComponent(); } +/// BlazorWebView를 호스팅하는 MAUI native page입니다. +public partial class MainPage : ContentPage +{ + /// MainPage.xaml의 control tree를 생성합니다. + public MainPage() => InitializeComponent(); +} diff --git a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs index 97bd48b..0d65a4d 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Dashboard.Maui/MauiProgram.cs @@ -4,9 +4,12 @@ namespace RobotVision.Dashboard.Maui; public static class MauiProgram { + /// MAUI application과 BlazorWebView dependency를 구성합니다. public static MauiApp CreateMauiApp() { + // UseMauiApp은 App 수명주기를 등록하고 DI container를 준비합니다. var builder = MauiApp.CreateBuilder().UseMauiApp(); + // Razor component를 native WebView 안에서 실행하는 Blazor Hybrid service입니다. builder.Services.AddMauiBlazorWebView(); #if DEBUG builder.Services.AddBlazorWebViewDeveloperTools(); @@ -15,4 +18,3 @@ public static MauiApp CreateMauiApp() return builder.Build(); } } - diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs index 35348e0..6b6d765 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DetectionFanoutWorker.cs @@ -4,26 +4,33 @@ namespace RobotVision.Server.Api; +/// +/// HTTP ingest와 SignalR 전송을 분리하는 background consumer입니다. +/// 수집 request가 느린 client에게 직접 묶여 대기하지 않도록 bounded channel을 읽습니다. +/// public sealed class DetectionFanoutWorker( Channel channel, IHubContext hub, ILogger logger) : BackgroundService { + /// application 종료 token이 취소될 때까지 channel event를 client group에 전송합니다. protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + // ReadAllAsync는 새 item을 비동기로 기다리고 channel이 완료되면 loop를 종료합니다. await foreach (var item in channel.Reader.ReadAllAsync(stoppingToken)) { try { + // 상세 화면에는 해당 장치만, fleet 화면에는 전체 event를 보냅니다. await hub.Clients.Group(MonitoringHub.GroupName(item.DeviceId)) .SendAsync("detection", item, stoppingToken); await hub.Clients.Group("fleet").SendAsync("detection", item, stoppingToken); } catch (Exception error) when (error is not OperationCanceledException) { + // 하나의 전송 실패가 worker 전체를 종료하지 않도록 기록 후 다음 item을 처리합니다. logger.LogError(error, "Failed to fan out event for {DeviceId}", item.DeviceId); } } } } - diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs index cec3407..23fa1d2 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/DeviceRegistry.cs @@ -3,10 +3,17 @@ namespace RobotVision.Server.Api; +/// +/// 장치별 가장 최신 detection snapshot을 thread-safe하게 보관하는 MVP in-memory registry입니다. +/// 서버 재시작 후에도 보존하려면 이 클래스를 database repository로 교체합니다. +/// public sealed class DeviceRegistry { + // 여러 HTTP request가 동시에 접근하므로 일반 Dictionary 대신 ConcurrentDictionary를 사용합니다. private readonly ConcurrentDictionary _devices = new(StringComparer.Ordinal); + /// 새 event가 기존 sequence 이상일 때만 장치의 최신 상태를 갱신합니다. + /// 수집 API가 검증한 detection event입니다. public void Upsert(DetectionEventDto item) { var snapshot = new DeviceSnapshot( @@ -15,14 +22,16 @@ public void Upsert(DetectionEventDto item) item.Sequence, item.ModelVersion, item.Detections); + // 늦게 도착한 오래된 event가 최신 화면을 되돌리지 않게 sequence를 비교합니다. _devices.AddOrUpdate(item.DeviceId, snapshot, (_, current) => item.Sequence >= current.LastSequence ? snapshot : current); } + /// 장치 ID로 정렬된 현재 snapshot 복사본을 반환합니다. public IReadOnlyCollection List() => _devices.Values.OrderBy(x => x.DeviceId, StringComparer.Ordinal).ToArray(); + /// 장치 ID에 해당하는 최신 snapshot을 찾아 반환합니다. public bool TryGet(string deviceId, out DeviceSnapshot? snapshot) => _devices.TryGetValue(deviceId, out snapshot); } - diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs index 611e5d2..cba558b 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs @@ -2,14 +2,17 @@ namespace RobotVision.Server.Api; +/// 웹/MAUI client가 특정 장치의 실시간 event를 구독하는 SignalR endpoint입니다. public sealed class MonitoringHub : Hub { + /// 현재 연결을 지정 장치의 SignalR group에 추가합니다. public Task WatchDevice(string deviceId) => Groups.AddToGroupAsync(Context.ConnectionId, GroupName(deviceId)); + /// 현재 연결을 지정 장치 group에서 제거합니다. public Task StopWatchingDevice(string deviceId) => Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName(deviceId)); + /// 장치 ID를 서버 전체에서 일관된 group 이름으로 변환합니다. public static string GroupName(string deviceId) => $"device:{deviceId}"; } - diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs index 2020f32..1312da6 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs @@ -4,12 +4,15 @@ using RobotVision.Server.Contracts; var builder = WebApplication.CreateBuilder(args); +// JSON request/response를 C# PascalCase 대신 JavaScript 친화적인 camelCase로 통일합니다. builder.Services.Configure(options => options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.CamelCase); builder.Services.AddSignalR(); +// Registry와 channel은 application 전체에서 하나만 존재해야 하므로 singleton입니다. builder.Services.AddSingleton(); builder.Services.AddSingleton(Channel.CreateBounded(new BoundedChannelOptions(1024) { + // 순간 burst가 1024개를 넘으면 오래된 UI 알림을 버리고 최신 상태를 우선합니다. FullMode = BoundedChannelFullMode.DropOldest, SingleReader = true, SingleWriter = false @@ -18,11 +21,13 @@ builder.Services.AddHealthChecks(); var app = builder.Build(); +// wwwroot/index.html을 기본 관제 페이지로 제공합니다. app.UseDefaultFiles(); app.UseStaticFiles(); app.MapHealthChecks("/health"); app.MapHub("/hubs/monitoring"); +// 현재 장치 목록 또는 장치 하나의 최신 snapshot을 조회하는 read API입니다. app.MapGet("/api/devices", (DeviceRegistry registry) => Results.Ok(registry.List())); app.MapGet("/api/devices/{deviceId}", (string deviceId, DeviceRegistry registry) => registry.TryGet(deviceId, out var device) ? Results.Ok(device) : Results.NotFound()); @@ -33,9 +38,11 @@ Channel channel, CancellationToken cancellationToken) => { + // 잘못된 ID와 비정상적으로 큰 payload를 background queue에 넣기 전에 거부합니다. if (string.IsNullOrWhiteSpace(item.DeviceId) || item.Detections.Count > 1_000) return Results.BadRequest(new { error = "deviceId is required; max 1000 detections" }); + // 조회용 최신 상태를 먼저 갱신하고, 실시간 UI fan-out은 bounded channel에 위임합니다. registry.Upsert(item); if (!channel.Writer.TryWrite(item)) return Results.StatusCode(StatusCodes.Status503ServiceUnavailable); @@ -45,5 +52,5 @@ app.Run(); +// Exposing the generated top-level Program type lets integration-test projects reference the host. public partial class Program; - diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html b/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html index 5b9846d..0b4df30 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/wwwroot/index.html @@ -18,10 +18,13 @@

Robot Vision Monitor

MVP device registry · refreshes every 2 seconds
장치 이벤트를 기다리는 중…
- diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs index f38691e..a3783b6 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Contracts/DeviceContracts.cs @@ -1,7 +1,11 @@ namespace RobotVision.Server.Contracts; +/// 원본 영상 크기와 무관한 0~1 정규화 좌표의 객체 영역입니다. public sealed record BoundingBox(float X, float Y, float Width, float Height); +/// 장치 또는 HTTP client가 서버에 보내는 객체 하나의 탐지 결과입니다. +/// 모델 label 목록에 정의된 class 이름입니다. +/// 일반적으로 0~1 범위인 모델 신뢰도입니다. public sealed record DetectionDto( string Label, float Confidence, @@ -10,9 +14,12 @@ public sealed record DetectionDto( float Width, float Height) { + /// 개별 좌표 필드를 하나의 BoundingBox value object로 반환합니다. public BoundingBox Box => new(X, Y, Width, Height); } +/// 한 프레임의 탐지 결과를 수집 API로 보내는 request 계약입니다. +/// 장치별 단조 증가 번호로 중복/역순 event를 판별합니다. public sealed record DetectionEventDto( string DeviceId, ulong Sequence, @@ -20,10 +27,10 @@ public sealed record DetectionEventDto( IReadOnlyList Detections, DateTimeOffset? CapturedAt = null); +/// 관제 화면이 읽는 장치별 최신 상태의 불변 snapshot입니다. public sealed record DeviceSnapshot( string DeviceId, DateTimeOffset LastSeenAt, ulong LastSequence, string ModelVersion, IReadOnlyList Detections); - diff --git a/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs index 6579c45..aa27af7 100644 --- a/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs +++ b/RobotVisionPlatform/server/tests/RobotVision.Server.Tests/Program.cs @@ -2,9 +2,9 @@ using RobotVision.Server.Contracts; var registry = new DeviceRegistry(); +// 최신 sequence 2를 먼저 넣고 오래된 sequence 1이 상태를 덮어쓰지 못하는지 확인합니다. registry.Upsert(new DetectionEventDto("test-device", 2, "model/1", [])); registry.Upsert(new DetectionEventDto("test-device", 1, "stale", [])); if (!registry.TryGet("test-device", out var item) || item?.LastSequence != 2) throw new InvalidOperationException("Registry must reject out-of-order snapshots."); Console.WriteLine("Server core tests passed."); - diff --git a/RobotVisionPlatform/shared/proto/vision/v1/device.proto b/RobotVisionPlatform/shared/proto/vision/v1/device.proto index d1431ea..58dde13 100644 --- a/RobotVisionPlatform/shared/proto/vision/v1/device.proto +++ b/RobotVisionPlatform/shared/proto/vision/v1/device.proto @@ -5,14 +5,21 @@ option csharp_namespace = "RobotVision.Contracts.Grpc.V1"; import "google/protobuf/timestamp.proto"; +// DeviceControl keeps one long-lived, device-initiated control-plane connection. service DeviceControl { + // The device sends status/events while the server returns commands on the same stream. rpc Connect(stream DeviceEnvelope) returns (stream ServerCommand); } +// DeviceEnvelope wraps every device-to-server payload with ordering and identity fields. message DeviceEnvelope { + // Stable fleet identifier provisioned with the device certificate. string device_id = 1; + // Monotonically increasing per-device number used for duplicate/order handling. uint64 sequence = 2; + // UTC time when this envelope left the device process. google.protobuf.Timestamp sent_at = 3; + // Exactly one payload is present. New payload types can be added with new field numbers. oneof payload { Heartbeat heartbeat = 10; DetectionBatch detections = 11; @@ -20,6 +27,7 @@ message DeviceEnvelope { } } +// Heartbeat is a lightweight periodic health and performance report. message Heartbeat { double capture_fps = 1; double inference_fps = 2; @@ -28,12 +36,15 @@ message Heartbeat { string software_version = 5; } +// DetectionBatch contains all objects detected in one captured frame. message DetectionBatch { + // Camera capture time; different from the later envelope sent_at time. google.protobuf.Timestamp captured_at = 1; string model_version = 2; repeated Detection items = 3; } +// Detection uses normalized top-left x/y and width/height coordinates in the 0..1 range. message Detection { string label = 1; float confidence = 2; @@ -41,9 +52,11 @@ message Detection { float y = 4; float width = 5; float height = 6; + // Stable ID assigned by a tracker; zero means no tracking ID is available. uint64 track_id = 7; } +// ModelState reports which signed artifact is staged, active, or failed on the device. message ModelState { string model_id = 1; string version = 2; @@ -51,8 +64,11 @@ message ModelState { string status = 4; } +// ServerCommand wraps one expiring, auditable server-to-device request. message ServerCommand { + // Unique idempotency key; a reconnecting device must not execute it twice. string command_id = 1; + // The device rejects commands received after this UTC time. google.protobuf.Timestamp expires_at = 2; oneof payload { UpdateConfiguration update_configuration = 10; @@ -61,11 +77,15 @@ message ServerCommand { } } +// RFC 7396-style JSON merge patch for fields allowed by the device policy. message UpdateConfiguration { string json_merge_patch = 1; } + +// DeployModel points to a signed manifest rather than an unverified raw model URL. message DeployModel { string manifest_uri = 1; string manifest_sha256 = 2; string signature = 3; } -message RequestKeyframe { string reason = 1; } +// RequestKeyframe asks for a diagnostic image without enabling continuous upload. +message RequestKeyframe { string reason = 1; } From 2ccb80798e5ea9c77f935b69ebd44eb441791b37 Mon Sep 17 00:00:00 2001 From: hundong2 Date: Mon, 17 Aug 2026 20:19:46 +0900 Subject: [PATCH 6/7] clean reference formatting --- RobotVisionPlatform/reference/README.md | 1 - RobotVisionPlatform/reference/cpp20-device-reference.md | 1 - RobotVisionPlatform/reference/csharp-server-reference.md | 1 - RobotVisionPlatform/reference/python-model-tools-reference.md | 1 - RobotVisionPlatform/reference/scripts-and-protocol-reference.md | 1 - 5 files changed, 5 deletions(-) diff --git a/RobotVisionPlatform/reference/README.md b/RobotVisionPlatform/reference/README.md index a4125f5..0b1d2da 100644 --- a/RobotVisionPlatform/reference/README.md +++ b/RobotVisionPlatform/reference/README.md @@ -35,4 +35,3 @@ - `주의`: 이 프로젝트에서 자주 발생할 수 있는 실수 코드 동작의 전체 흐름은 [시스템 아키텍처](../docs/architecture.md), 모델 배포 흐름은 [Device 모델 배포 가이드](../device/guides/README.md)를 함께 참고합니다. - diff --git a/RobotVisionPlatform/reference/cpp20-device-reference.md b/RobotVisionPlatform/reference/cpp20-device-reference.md index 2ba537b..8af6a35 100644 --- a/RobotVisionPlatform/reference/cpp20-device-reference.md +++ b/RobotVisionPlatform/reference/cpp20-device-reference.md @@ -184,4 +184,3 @@ void HandleSignal(int) { running = 0; } ``` signal handler 안에서는 logging, allocation, mutex 같은 일반 함수를 호출하면 안전하지 않을 수 있습니다. handler는 flag만 바꾸고 정상 thread가 `Pipeline::Stop()`을 호출합니다. - diff --git a/RobotVisionPlatform/reference/csharp-server-reference.md b/RobotVisionPlatform/reference/csharp-server-reference.md index 87b041b..4b862e5 100644 --- a/RobotVisionPlatform/reference/csharp-server-reference.md +++ b/RobotVisionPlatform/reference/csharp-server-reference.md @@ -155,4 +155,3 @@ logger.LogError(error, "Failed for {DeviceId}", deviceId); ## MAUI `MauiApp.CreateBuilder()`와 `BlazorWebView` `MauiApp.CreateBuilder()`는 native application, DI, logging을 구성합니다. `AddMauiBlazorWebView()`는 Razor component를 native WebView 안에서 실행할 service를 등록합니다. 현재 앱은 골격이며 SignalR/WebRTC 연결은 다음 단계입니다. - diff --git a/RobotVisionPlatform/reference/python-model-tools-reference.md b/RobotVisionPlatform/reference/python-model-tools-reference.md index 9a38f01..3471ba5 100644 --- a/RobotVisionPlatform/reference/python-model-tools-reference.md +++ b/RobotVisionPlatform/reference/python-model-tools-reference.md @@ -168,4 +168,3 @@ with tempfile.TemporaryDirectory() as directory: ``` 실제 model directory를 오염시키지 않고 file I/O를 검증할 수 있습니다. - diff --git a/RobotVisionPlatform/reference/scripts-and-protocol-reference.md b/RobotVisionPlatform/reference/scripts-and-protocol-reference.md index 1e8dac5..2e78007 100644 --- a/RobotVisionPlatform/reference/scripts-and-protocol-reference.md +++ b/RobotVisionPlatform/reference/scripts-and-protocol-reference.md @@ -135,4 +135,3 @@ jobs: - job은 독립 runner에서 실행되는 작업 묶음입니다. - step은 job 안에서 순서대로 실행됩니다. - 로컬 test script와 CI command를 같은 형태로 유지하면 환경 차이를 줄일 수 있습니다. - From 4345649118a0776fa8814fbd90ad06909d8864b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 19 Aug 2026 05:03:08 +0000 Subject: [PATCH 7/7] Fix review issues: null-guard Detections, validate deviceId, InferenceLoop exception handling, control-char escaping, global engine/plan gitignore Co-authored-by: hundong2 <17015362+hundong2@users.noreply.github.com> --- RobotVisionPlatform/.gitignore | 3 +- .../device/src/http_event_sink.cpp | 18 +++++++--- RobotVisionPlatform/device/src/pipeline.cpp | 34 +++++++++++-------- .../RobotVision.Server.Api/MonitoringHub.cs | 8 ++++- .../src/RobotVision.Server.Api/Program.cs | 2 +- 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/RobotVisionPlatform/.gitignore b/RobotVisionPlatform/.gitignore index 017826d..b835185 100644 --- a/RobotVisionPlatform/.gitignore +++ b/RobotVisionPlatform/.gitignore @@ -10,5 +10,6 @@ device/stage/ .venv/ __pycache__/ secrets/ -models/*.engine +**/*.engine +**/*.plan device/artifacts/ diff --git a/RobotVisionPlatform/device/src/http_event_sink.cpp b/RobotVisionPlatform/device/src/http_event_sink.cpp index a3aec33..f4e394f 100644 --- a/RobotVisionPlatform/device/src/http_event_sink.cpp +++ b/RobotVisionPlatform/device/src/http_event_sink.cpp @@ -17,12 +17,20 @@ namespace net = boost::asio; using tcp = net::ip::tcp; std::string Escape(std::string_view value) { - // 이 MVP JSON writer가 사용하는 문자열 필드에서 최소한의 quote/backslash escaping을 합니다. - // production에서는 검증된 JSON serializer를 사용해 control character까지 처리해야 합니다. + // 이 MVP JSON writer가 사용하는 문자열 필드에서 최소한의 escaping을 합니다. + // production에서는 검증된 JSON serializer를 사용해야 합니다. std::string result; - for (char ch : value) { - if (ch == '"' || ch == '\\') result.push_back('\\'); - result.push_back(ch); + for (unsigned char ch : value) { + switch (ch) { + case '"': result += "\\\""; break; + case '\\': result += "\\\\"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + default: + if (ch < 0x20) continue; // drop other control characters + result.push_back(static_cast(ch)); + } } return result; } diff --git a/RobotVisionPlatform/device/src/pipeline.cpp b/RobotVisionPlatform/device/src/pipeline.cpp index 7c9caac..9062ffd 100644 --- a/RobotVisionPlatform/device/src/pipeline.cpp +++ b/RobotVisionPlatform/device/src/pipeline.cpp @@ -52,26 +52,30 @@ void Pipeline::CaptureLoop(std::stop_token stop) { if (!frames_.Push(std::move(frame))) break; } } catch (const std::exception& error) { - std::cerr << "capture_error=\"" << error.what() << "\"\n"; + std::cerr << "capture_error: " << error.what() << "\n"; } frames_.Close(); } void Pipeline::InferenceLoop(std::stop_token stop) { - while (!stop.stop_requested()) { - // Pop은 새 프레임, queue close, stop 요청 중 하나가 발생할 때 깨어납니다. - auto frame = frames_.Pop(stop); - if (!frame) break; - auto detections = detector_->Infer(*frame); - ++inferred_; - // model version과 원본 sequence를 함께 보내 서버가 결과의 출처를 추적하게 합니다. - DetectionEvent event{.device_id = options_.device_id, - .sequence = frame->sequence, - .captured_at = frame->captured_at, - .model_version = detector_->Version(), - .detections = std::move(detections)}; - // false는 전송 실패를 의미합니다. production sink는 자체 retry/spool 정책을 가져야 합니다. - if (sink_->Publish(event)) ++published_; + try { + while (!stop.stop_requested()) { + // Pop은 새 프레임, queue close, stop 요청 중 하나가 발생할 때 깨어납니다. + auto frame = frames_.Pop(stop); + if (!frame) break; + auto detections = detector_->Infer(*frame); + ++inferred_; + // model version과 원본 sequence를 함께 보내 서버가 결과의 출처를 추적하게 합니다. + DetectionEvent event{.device_id = options_.device_id, + .sequence = frame->sequence, + .captured_at = frame->captured_at, + .model_version = detector_->Version(), + .detections = std::move(detections)}; + // false는 전송 실패를 의미합니다. production sink는 자체 retry/spool 정책을 가져야 합니다. + if (sink_->Publish(event)) ++published_; + } + } catch (const std::exception& error) { + std::cerr << "inference_error: " << error.what() << "\n"; } } diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs index cba558b..76c7acf 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/MonitoringHub.cs @@ -14,5 +14,11 @@ public Task StopWatchingDevice(string deviceId) => Groups.RemoveFromGroupAsync(Context.ConnectionId, GroupName(deviceId)); /// 장치 ID를 서버 전체에서 일관된 group 이름으로 변환합니다. - public static string GroupName(string deviceId) => $"device:{deviceId}"; + /// deviceId가 비어 있거나 64자를 초과하면 발생합니다. + public static string GroupName(string deviceId) + { + if (string.IsNullOrWhiteSpace(deviceId) || deviceId.Length > 64) + throw new ArgumentException("deviceId must be 1–64 non-whitespace characters.", nameof(deviceId)); + return $"device:{deviceId}"; + } } diff --git a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs index 1312da6..3293ce2 100644 --- a/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs +++ b/RobotVisionPlatform/server/src/RobotVision.Server.Api/Program.cs @@ -39,7 +39,7 @@ CancellationToken cancellationToken) => { // 잘못된 ID와 비정상적으로 큰 payload를 background queue에 넣기 전에 거부합니다. - if (string.IsNullOrWhiteSpace(item.DeviceId) || item.Detections.Count > 1_000) + if (string.IsNullOrWhiteSpace(item.DeviceId) || item.Detections is null || item.Detections.Count > 1_000) return Results.BadRequest(new { error = "deviceId is required; max 1000 detections" }); // 조회용 최신 상태를 먼저 갱신하고, 실시간 UI fan-out은 bounded channel에 위임합니다.